comparison mercurial/utils/urlutil.py @ 47187:7531cc34713c

urlutil: make `paths` class old list of `path` We move from a `{name ? path}` mapping to a `{name ? [path]}` mapping. And update all user code accordingly. For now, all the list contains exactly one element, but we are now in a good place to make the config understand a list of url. Differential Revision: https://phab.mercurial-scm.org/D10447
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Thu, 15 Apr 2021 16:58:20 +0200
parents 26b3953ba1b0
children a671832a8e41
comparison
equal deleted inserted replaced
47186:26b3953ba1b0 47187:7531cc34713c
445 return bytes(u) 445 return bytes(u)
446 446
447 447
448 def list_paths(ui, target_path=None): 448 def list_paths(ui, target_path=None):
449 """list all the (name, paths) in the passed ui""" 449 """list all the (name, paths) in the passed ui"""
450 result = []
450 if target_path is None: 451 if target_path is None:
451 return sorted(pycompat.iteritems(ui.paths)) 452 for name, paths in sorted(pycompat.iteritems(ui.paths)):
453 for p in paths:
454 result.append((name, p))
455
452 else: 456 else:
453 path = ui.paths.get(target_path) 457 for path in ui.paths.get(target_path, []):
454 if path is None: 458 result.append((target_path, path))
455 return [] 459 return result
456 else:
457 return [(target_path, path)]
458 460
459 461
460 def try_path(ui, url): 462 def try_path(ui, url):
461 """try to build a path from a url 463 """try to build a path from a url
462 464
471 473
472 def get_push_paths(repo, ui, dests): 474 def get_push_paths(repo, ui, dests):
473 """yields all the `path` selected as push destination by `dests`""" 475 """yields all the `path` selected as push destination by `dests`"""
474 if not dests: 476 if not dests:
475 if b'default-push' in ui.paths: 477 if b'default-push' in ui.paths:
476 yield ui.paths[b'default-push'] 478 for p in ui.paths[b'default-push']:
479 yield p
477 elif b'default' in ui.paths: 480 elif b'default' in ui.paths:
478 yield ui.paths[b'default'] 481 for p in ui.paths[b'default']:
482 yield p
479 else: 483 else:
480 raise error.ConfigError( 484 raise error.ConfigError(
481 _(b'default repository not configured!'), 485 _(b'default repository not configured!'),
482 hint=_(b"see 'hg help config.paths'"), 486 hint=_(b"see 'hg help config.paths'"),
483 ) 487 )
484 else: 488 else:
485 for dest in dests: 489 for dest in dests:
486 if dest in ui.paths: 490 if dest in ui.paths:
487 yield ui.paths[dest] 491 for p in ui.paths[dest]:
492 yield p
488 else: 493 else:
489 path = try_path(ui, dest) 494 path = try_path(ui, dest)
490 if path is None: 495 if path is None:
491 msg = _(b'repository %s does not exist') 496 msg = _(b'repository %s does not exist')
492 msg %= dest 497 msg %= dest
498 """yields all the `(path, branch)` selected as pull source by `sources`""" 503 """yields all the `(path, branch)` selected as pull source by `sources`"""
499 if not sources: 504 if not sources:
500 sources = [b'default'] 505 sources = [b'default']
501 for source in sources: 506 for source in sources:
502 if source in ui.paths: 507 if source in ui.paths:
503 url = ui.paths[source].rawloc 508 for p in ui.paths[source]:
509 yield parseurl(p.rawloc, default_branches)
504 else: 510 else:
505 # Try to resolve as a local path or URI. 511 # Try to resolve as a local path or URI.
506 path = try_path(ui, source) 512 path = try_path(ui, source)
507 if path is not None: 513 if path is not None:
508 url = path.rawloc 514 url = path.rawloc
509 else: 515 else:
510 url = source 516 url = source
511 yield parseurl(url, default_branches) 517 yield parseurl(url, default_branches)
512 518
513 519
514 def get_unique_push_path(action, repo, ui, dest=None): 520 def get_unique_push_path(action, repo, ui, dest=None):
515 """return a unique `path` or abort if multiple are found 521 """return a unique `path` or abort if multiple are found
516 522
524 if dest is None: 530 if dest is None:
525 dests = [] 531 dests = []
526 else: 532 else:
527 dests = [dest] 533 dests = [dest]
528 dests = list(get_push_paths(repo, ui, dests)) 534 dests = list(get_push_paths(repo, ui, dests))
529 assert len(dests) == 1 535 if len(dests) != 1:
536 if dest is None:
537 msg = _("default path points to %d urls while %s only supports one")
538 msg %= (len(dests), action)
539 else:
540 msg = _("path points to %d urls while %s only supports one: %s")
541 msg %= (len(dests), action, dest)
542 raise error.Abort(msg)
530 return dests[0] 543 return dests[0]
531 544
532 545
533 def get_unique_pull_path(action, repo, ui, source=None, default_branches=()): 546 def get_unique_pull_path(action, repo, ui, source=None, default_branches=()):
534 """return a unique `(path, branch)` or abort if multiple are found 547 """return a unique `(path, branch)` or abort if multiple are found
538 551
539 Note that for now, we cannot get multiple destination so this function is "trivial". 552 Note that for now, we cannot get multiple destination so this function is "trivial".
540 553
541 The `action` parameter will be used for the error message. 554 The `action` parameter will be used for the error message.
542 """ 555 """
556 urls = []
543 if source is None: 557 if source is None:
544 if b'default' in ui.paths: 558 if b'default' in ui.paths:
545 url = ui.paths[b'default'].rawloc 559 urls.extend(p.rawloc for p in ui.paths[b'default'])
546 else: 560 else:
547 # XXX this is the historical default behavior, but that is not 561 # XXX this is the historical default behavior, but that is not
548 # great, consider breaking BC on this. 562 # great, consider breaking BC on this.
549 url = b'default' 563 urls.append(b'default')
550 else: 564 else:
551 if source in ui.paths: 565 if source in ui.paths:
552 url = ui.paths[source].rawloc 566 urls.extend(p.rawloc for p in ui.paths[source])
553 else: 567 else:
554 # Try to resolve as a local path or URI. 568 # Try to resolve as a local path or URI.
555 path = try_path(ui, source) 569 path = try_path(ui, source)
556 if path is not None: 570 if path is not None:
557 url = path.rawloc 571 urls.append(path.rawloc)
558 else: 572 else:
559 url = source 573 urls.append(source)
560 return parseurl(url, default_branches) 574 if len(urls) != 1:
575 if source is None:
576 msg = _("default path points to %d urls while %s only supports one")
577 msg %= (len(urls), action)
578 else:
579 msg = _("path points to %d urls while %s only supports one: %s")
580 msg %= (len(urls), action, source)
581 raise error.Abort(msg)
582 return parseurl(urls[0], default_branches)
561 583
562 584
563 def get_clone_path(ui, source, default_branches=()): 585 def get_clone_path(ui, source, default_branches=()):
564 """return the `(origsource, path, branch)` selected as clone source""" 586 """return the `(origsource, path, branch)` selected as clone source"""
587 urls = []
565 if source is None: 588 if source is None:
566 if b'default' in ui.paths: 589 if b'default' in ui.paths:
567 url = ui.paths[b'default'].rawloc 590 urls.extend(p.rawloc for p in ui.paths[b'default'])
568 else: 591 else:
569 # XXX this is the historical default behavior, but that is not 592 # XXX this is the historical default behavior, but that is not
570 # great, consider breaking BC on this. 593 # great, consider breaking BC on this.
571 url = b'default' 594 urls.append(b'default')
572 else: 595 else:
573 if source in ui.paths: 596 if source in ui.paths:
574 url = ui.paths[source].rawloc 597 urls.extend(p.rawloc for p in ui.paths[source])
575 else: 598 else:
576 # Try to resolve as a local path or URI. 599 # Try to resolve as a local path or URI.
577 path = try_path(ui, source) 600 path = try_path(ui, source)
578 if path is not None: 601 if path is not None:
579 url = path.rawloc 602 urls.append(path.rawloc)
580 else: 603 else:
581 url = source 604 urls.append(source)
605 if len(urls) != 1:
606 if source is None:
607 msg = _(
608 "default path points to %d urls while only one is supported"
609 )
610 msg %= len(urls)
611 else:
612 msg = _("path points to %d urls while only one is supported: %s")
613 msg %= (len(urls), source)
614 raise error.Abort(msg)
615 url = urls[0]
582 clone_path, branch = parseurl(url, default_branches) 616 clone_path, branch = parseurl(url, default_branches)
583 return url, clone_path, branch 617 return url, clone_path, branch
584 618
585 619
586 def parseurl(path, branches=None): 620 def parseurl(path, branches=None):
606 for name, loc in ui.configitems(b'paths', ignoresub=True): 640 for name, loc in ui.configitems(b'paths', ignoresub=True):
607 # No location is the same as not existing. 641 # No location is the same as not existing.
608 if not loc: 642 if not loc:
609 continue 643 continue
610 loc, sub_opts = ui.configsuboptions(b'paths', name) 644 loc, sub_opts = ui.configsuboptions(b'paths', name)
611 self[name] = path(ui, name, rawloc=loc, suboptions=sub_opts) 645 self[name] = [path(ui, name, rawloc=loc, suboptions=sub_opts)]
612 646
613 for name, p in sorted(self.items()): 647 for name, old_paths in sorted(self.items()):
614 self[name] = _chain_path(p, ui, self) 648 new_paths = []
649 for p in old_paths:
650 new_paths.extend(_chain_path(p, ui, self))
651 self[name] = new_paths
615 652
616 def getpath(self, ui, name, default=None): 653 def getpath(self, ui, name, default=None):
617 """Return a ``path`` from a string, falling back to default. 654 """Return a ``path`` from a string, falling back to default.
618 655
619 ``name`` can be a named path or locations. Locations are filesystem 656 ``name`` can be a named path or locations. Locations are filesystem
630 default = () 667 default = ()
631 elif not isinstance(default, (tuple, list)): 668 elif not isinstance(default, (tuple, list)):
632 default = (default,) 669 default = (default,)
633 for k in default: 670 for k in default:
634 try: 671 try:
635 return self[k] 672 return self[k][0]
636 except KeyError: 673 except KeyError:
637 continue 674 continue
638 return None 675 return None
639 676
640 # Most likely empty string. 677 # Most likely empty string.
641 # This may need to raise in the future. 678 # This may need to raise in the future.
642 if not name: 679 if not name:
643 return None 680 return None
644 if name in self: 681 if name in self:
645 return self[name] 682 return self[name][0]
646 else: 683 else:
647 # Try to resolve as a local path or URI. 684 # Try to resolve as a local path or URI.
648 path = try_path(ui, name) 685 path = try_path(ui, name)
649 if path is None: 686 if path is None:
650 raise error.RepoError(_(b'repository %s does not exist') % name) 687 raise error.RepoError(_(b'repository %s does not exist') % name)
702 @pathsuboption(b'pushrev', b'pushrev') 739 @pathsuboption(b'pushrev', b'pushrev')
703 def pushrevpathoption(ui, path, value): 740 def pushrevpathoption(ui, path, value):
704 return value 741 return value
705 742
706 743
707 def _chain_path(path, ui, paths): 744 def _chain_path(base_path, ui, paths):
708 """return the result of "path://" logic applied on a given path""" 745 """return the result of "path://" logic applied on a given path"""
709 if path.url.scheme == b'path': 746 new_paths = []
710 assert path.url.path is None 747 if base_path.url.scheme != b'path':
711 subpath = paths.get(path.url.host) 748 new_paths.append(base_path)
712 if subpath is None: 749 else:
750 assert base_path.url.path is None
751 sub_paths = paths.get(base_path.url.host)
752 if sub_paths is None:
713 m = _(b'cannot use `%s`, "%s" is not a known path') 753 m = _(b'cannot use `%s`, "%s" is not a known path')
714 m %= (path.rawloc, path.url.host) 754 m %= (base_path.rawloc, base_path.url.host)
715 raise error.Abort(m) 755 raise error.Abort(m)
716 if subpath.raw_url.scheme == b'path': 756 for subpath in sub_paths:
717 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`') 757 path = base_path.copy()
718 m %= (path.rawloc, path.url.host) 758 if subpath.raw_url.scheme == b'path':
719 raise error.Abort(m) 759 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
720 path.url = subpath.url 760 m %= (path.rawloc, path.url.host)
721 path.rawloc = subpath.rawloc 761 raise error.Abort(m)
722 path.loc = subpath.loc 762 path.url = subpath.url
723 if path.branch is None: 763 path.rawloc = subpath.rawloc
724 path.branch = subpath.branch 764 path.loc = subpath.loc
725 else: 765 if path.branch is None:
726 base = path.rawloc.rsplit(b'#', 1)[0] 766 path.branch = subpath.branch
727 path.rawloc = b'%s#%s' % (base, path.branch) 767 else:
728 suboptions = subpath._all_sub_opts.copy() 768 base = path.rawloc.rsplit(b'#', 1)[0]
729 suboptions.update(path._own_sub_opts) 769 path.rawloc = b'%s#%s' % (base, path.branch)
730 path._apply_suboptions(ui, suboptions) 770 suboptions = subpath._all_sub_opts.copy()
731 return path 771 suboptions.update(path._own_sub_opts)
772 path._apply_suboptions(ui, suboptions)
773 new_paths.append(path)
774 return new_paths
732 775
733 776
734 class path(object): 777 class path(object):
735 """Represents an individual path and its configuration.""" 778 """Represents an individual path and its configuration."""
736 779