urlutil: make `paths` class old list of `path`
authorPierre-Yves David <pierre-yves.david@octobus.net>
Thu, 15 Apr 2021 16:58:20 +0200
changeset 47198 7531cc34713c
parent 47197 26b3953ba1b0
child 47199 353718f741a8
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
mercurial/templatekw.py
mercurial/utils/urlutil.py
--- a/mercurial/templatekw.py	Thu Apr 15 17:15:43 2021 +0200
+++ b/mercurial/templatekw.py	Thu Apr 15 16:58:20 2021 +0200
@@ -667,14 +667,19 @@
     urls = util.sortdict((k, p.rawloc) for k, p in all_paths)
 
     def makemap(k):
-        p = paths[k]
-        d = {b'name': k, b'url': p.rawloc}
-        sub_opts = util.sortdict(sorted(pycompat.iteritems(p.suboptions)))
-        d.update(sub_opts)
+        ps = paths[k]
+        d = {b'name': k}
+        if len(ps) == 1:
+            d[b'url'] = ps[0].rawloc
+            sub_opts = pycompat.iteritems(ps[0].suboptions)
+            sub_opts = util.sortdict(sorted(sub_opts))
+            d.update(sub_opts)
         path_dict = util.sortdict()
-        path_dict[b'url'] = p.rawloc
-        path_dict.update(sub_opts)
-        d[b'urls'] = [path_dict]
+        for p in ps:
+            sub_opts = util.sortdict(sorted(pycompat.iteritems(p.suboptions)))
+            path_dict[b'url'] = p.rawloc
+            path_dict.update(sub_opts)
+            d[b'urls'] = [path_dict]
         return d
 
     def format_one(k):
--- a/mercurial/utils/urlutil.py	Thu Apr 15 17:15:43 2021 +0200
+++ b/mercurial/utils/urlutil.py	Thu Apr 15 16:58:20 2021 +0200
@@ -447,14 +447,16 @@
 
 def list_paths(ui, target_path=None):
     """list all the (name, paths) in the passed ui"""
+    result = []
     if target_path is None:
-        return sorted(pycompat.iteritems(ui.paths))
+        for name, paths in sorted(pycompat.iteritems(ui.paths)):
+            for p in paths:
+                result.append((name, p))
+
     else:
-        path = ui.paths.get(target_path)
-        if path is None:
-            return []
-        else:
-            return [(target_path, path)]
+        for path in ui.paths.get(target_path, []):
+            result.append((target_path, path))
+    return result
 
 
 def try_path(ui, url):
@@ -473,9 +475,11 @@
     """yields all the `path` selected as push destination by `dests`"""
     if not dests:
         if b'default-push' in ui.paths:
-            yield ui.paths[b'default-push']
+            for p in ui.paths[b'default-push']:
+                yield p
         elif b'default' in ui.paths:
-            yield ui.paths[b'default']
+            for p in ui.paths[b'default']:
+                yield p
         else:
             raise error.ConfigError(
                 _(b'default repository not configured!'),
@@ -484,7 +488,8 @@
     else:
         for dest in dests:
             if dest in ui.paths:
-                yield ui.paths[dest]
+                for p in ui.paths[dest]:
+                    yield p
             else:
                 path = try_path(ui, dest)
                 if path is None:
@@ -500,7 +505,8 @@
         sources = [b'default']
     for source in sources:
         if source in ui.paths:
-            url = ui.paths[source].rawloc
+            for p in ui.paths[source]:
+                yield parseurl(p.rawloc, default_branches)
         else:
             # Try to resolve as a local path or URI.
             path = try_path(ui, source)
@@ -508,7 +514,7 @@
                 url = path.rawloc
             else:
                 url = source
-        yield parseurl(url, default_branches)
+            yield parseurl(url, default_branches)
 
 
 def get_unique_push_path(action, repo, ui, dest=None):
@@ -526,7 +532,14 @@
     else:
         dests = [dest]
     dests = list(get_push_paths(repo, ui, dests))
-    assert len(dests) == 1
+    if len(dests) != 1:
+        if dest is None:
+            msg = _("default path points to %d urls while %s only supports one")
+            msg %= (len(dests), action)
+        else:
+            msg = _("path points to %d urls while %s only supports one: %s")
+            msg %= (len(dests), action, dest)
+        raise error.Abort(msg)
     return dests[0]
 
 
@@ -540,45 +553,66 @@
 
     The `action` parameter will be used for the error message.
     """
+    urls = []
     if source is None:
         if b'default' in ui.paths:
-            url = ui.paths[b'default'].rawloc
+            urls.extend(p.rawloc for p in ui.paths[b'default'])
         else:
             # XXX this is the historical default behavior, but that is not
             # great, consider breaking BC on this.
-            url = b'default'
+            urls.append(b'default')
     else:
         if source in ui.paths:
-            url = ui.paths[source].rawloc
+            urls.extend(p.rawloc for p in ui.paths[source])
         else:
             # Try to resolve as a local path or URI.
             path = try_path(ui, source)
             if path is not None:
-                url = path.rawloc
+                urls.append(path.rawloc)
             else:
-                url = source
-    return parseurl(url, default_branches)
+                urls.append(source)
+    if len(urls) != 1:
+        if source is None:
+            msg = _("default path points to %d urls while %s only supports one")
+            msg %= (len(urls), action)
+        else:
+            msg = _("path points to %d urls while %s only supports one: %s")
+            msg %= (len(urls), action, source)
+        raise error.Abort(msg)
+    return parseurl(urls[0], default_branches)
 
 
 def get_clone_path(ui, source, default_branches=()):
     """return the `(origsource, path, branch)` selected as clone source"""
+    urls = []
     if source is None:
         if b'default' in ui.paths:
-            url = ui.paths[b'default'].rawloc
+            urls.extend(p.rawloc for p in ui.paths[b'default'])
         else:
             # XXX this is the historical default behavior, but that is not
             # great, consider breaking BC on this.
-            url = b'default'
+            urls.append(b'default')
     else:
         if source in ui.paths:
-            url = ui.paths[source].rawloc
+            urls.extend(p.rawloc for p in ui.paths[source])
         else:
             # Try to resolve as a local path or URI.
             path = try_path(ui, source)
             if path is not None:
-                url = path.rawloc
+                urls.append(path.rawloc)
             else:
-                url = source
+                urls.append(source)
+    if len(urls) != 1:
+        if source is None:
+            msg = _(
+                "default path points to %d urls while only one is supported"
+            )
+            msg %= len(urls)
+        else:
+            msg = _("path points to %d urls while only one is supported: %s")
+            msg %= (len(urls), source)
+        raise error.Abort(msg)
+    url = urls[0]
     clone_path, branch = parseurl(url, default_branches)
     return url, clone_path, branch
 
@@ -608,10 +642,13 @@
             if not loc:
                 continue
             loc, sub_opts = ui.configsuboptions(b'paths', name)
-            self[name] = path(ui, name, rawloc=loc, suboptions=sub_opts)
+            self[name] = [path(ui, name, rawloc=loc, suboptions=sub_opts)]
 
-        for name, p in sorted(self.items()):
-            self[name] = _chain_path(p, ui, self)
+        for name, old_paths in sorted(self.items()):
+            new_paths = []
+            for p in old_paths:
+                new_paths.extend(_chain_path(p, ui, self))
+            self[name] = new_paths
 
     def getpath(self, ui, name, default=None):
         """Return a ``path`` from a string, falling back to default.
@@ -632,7 +669,7 @@
                 default = (default,)
             for k in default:
                 try:
-                    return self[k]
+                    return self[k][0]
                 except KeyError:
                     continue
             return None
@@ -642,7 +679,7 @@
         if not name:
             return None
         if name in self:
-            return self[name]
+            return self[name][0]
         else:
             # Try to resolve as a local path or URI.
             path = try_path(ui, name)
@@ -704,31 +741,37 @@
     return value
 
 
-def _chain_path(path, ui, paths):
+def _chain_path(base_path, ui, paths):
     """return the result of "path://" logic applied on a given path"""
-    if path.url.scheme == b'path':
-        assert path.url.path is None
-        subpath = paths.get(path.url.host)
-        if subpath is None:
+    new_paths = []
+    if base_path.url.scheme != b'path':
+        new_paths.append(base_path)
+    else:
+        assert base_path.url.path is None
+        sub_paths = paths.get(base_path.url.host)
+        if sub_paths is None:
             m = _(b'cannot use `%s`, "%s" is not a known path')
-            m %= (path.rawloc, path.url.host)
-            raise error.Abort(m)
-        if subpath.raw_url.scheme == b'path':
-            m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
-            m %= (path.rawloc, path.url.host)
+            m %= (base_path.rawloc, base_path.url.host)
             raise error.Abort(m)
-        path.url = subpath.url
-        path.rawloc = subpath.rawloc
-        path.loc = subpath.loc
-        if path.branch is None:
-            path.branch = subpath.branch
-        else:
-            base = path.rawloc.rsplit(b'#', 1)[0]
-            path.rawloc = b'%s#%s' % (base, path.branch)
-        suboptions = subpath._all_sub_opts.copy()
-        suboptions.update(path._own_sub_opts)
-        path._apply_suboptions(ui, suboptions)
-    return path
+        for subpath in sub_paths:
+            path = base_path.copy()
+            if subpath.raw_url.scheme == b'path':
+                m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
+                m %= (path.rawloc, path.url.host)
+                raise error.Abort(m)
+            path.url = subpath.url
+            path.rawloc = subpath.rawloc
+            path.loc = subpath.loc
+            if path.branch is None:
+                path.branch = subpath.branch
+            else:
+                base = path.rawloc.rsplit(b'#', 1)[0]
+                path.rawloc = b'%s#%s' % (base, path.branch)
+            suboptions = subpath._all_sub_opts.copy()
+            suboptions.update(path._own_sub_opts)
+            path._apply_suboptions(ui, suboptions)
+            new_paths.append(path)
+    return new_paths
 
 
 class path(object):