Mercurial > public > mercurial-scm > hg
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 |