Patchwork D10373: urlutil: extract `path` related code into a new module

login
register
mail settings
Submitter phabricator
Date April 12, 2021, 3:30 p.m.
Message ID <differential-rev-PHID-DREV-jzw7t43r5yw6b67ukdaz-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/48690/
State Superseded
Headers show

Comments

phabricator - April 12, 2021, 3:30 p.m.
marmoute created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  They are a lot of code related to url and path handling scattering into various
  large module. To consolidate the code before doing more change (for defining
  "multi-path"), we gather it together.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D10373

AFFECTED FILES
  mercurial/ui.py
  mercurial/utils/urlutil.py

CHANGE DETAILS




To: marmoute, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/mercurial/utils/urlutil.py b/mercurial/utils/urlutil.py
new file mode 100644
--- /dev/null
+++ b/mercurial/utils/urlutil.py
@@ -0,0 +1,249 @@ 
+# utils.urlutil - code related to [paths] management
+#
+# Copyright 2005-2021 Olivia Mackall <olivia@selenic.com> and others
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+import os
+
+from ..i18n import _
+from ..pycompat import (
+    getattr,
+    setattr,
+)
+from .. import (
+    error,
+    pycompat,
+    util,
+)
+
+
+class paths(dict):
+    """Represents a collection of paths and their configs.
+
+    Data is initially derived from ui instances and the config files they have
+    loaded.
+    """
+
+    def __init__(self, ui):
+        dict.__init__(self)
+
+        for name, loc in ui.configitems(b'paths', ignoresub=True):
+            # No location is the same as not existing.
+            if not loc:
+                continue
+            loc, sub_opts = ui.configsuboptions(b'paths', name)
+            self[name] = path(ui, name, rawloc=loc, suboptions=sub_opts)
+
+        for name, p in sorted(self.items()):
+            p.chain_path(ui, self)
+
+    def getpath(self, ui, name, default=None):
+        """Return a ``path`` from a string, falling back to default.
+
+        ``name`` can be a named path or locations. Locations are filesystem
+        paths or URIs.
+
+        Returns None if ``name`` is not a registered path, a URI, or a local
+        path to a repo.
+        """
+        # Only fall back to default if no path was requested.
+        if name is None:
+            if not default:
+                default = ()
+            elif not isinstance(default, (tuple, list)):
+                default = (default,)
+            for k in default:
+                try:
+                    return self[k]
+                except KeyError:
+                    continue
+            return None
+
+        # Most likely empty string.
+        # This may need to raise in the future.
+        if not name:
+            return None
+
+        try:
+            return self[name]
+        except KeyError:
+            # Try to resolve as a local path or URI.
+            try:
+                # we pass the ui instance are warning might need to be issued
+                return path(ui, None, rawloc=name)
+            except ValueError:
+                raise error.RepoError(_(b'repository %s does not exist') % name)
+
+
+_pathsuboptions = {}
+
+
+def pathsuboption(option, attr):
+    """Decorator used to declare a path sub-option.
+
+    Arguments are the sub-option name and the attribute it should set on
+    ``path`` instances.
+
+    The decorated function will receive as arguments a ``ui`` instance,
+    ``path`` instance, and the string value of this option from the config.
+    The function should return the value that will be set on the ``path``
+    instance.
+
+    This decorator can be used to perform additional verification of
+    sub-options and to change the type of sub-options.
+    """
+
+    def register(func):
+        _pathsuboptions[option] = (attr, func)
+        return func
+
+    return register
+
+
+@pathsuboption(b'pushurl', b'pushloc')
+def pushurlpathoption(ui, path, value):
+    u = util.url(value)
+    # Actually require a URL.
+    if not u.scheme:
+        ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
+        return None
+
+    # Don't support the #foo syntax in the push URL to declare branch to
+    # push.
+    if u.fragment:
+        ui.warn(
+            _(
+                b'("#fragment" in paths.%s:pushurl not supported; '
+                b'ignoring)\n'
+            )
+            % path.name
+        )
+        u.fragment = None
+
+    return bytes(u)
+
+
+@pathsuboption(b'pushrev', b'pushrev')
+def pushrevpathoption(ui, path, value):
+    return value
+
+
+class path(object):
+    """Represents an individual path and its configuration."""
+
+    def __init__(self, ui, name, rawloc=None, suboptions=None):
+        """Construct a path from its config options.
+
+        ``ui`` is the ``ui`` instance the path is coming from.
+        ``name`` is the symbolic name of the path.
+        ``rawloc`` is the raw location, as defined in the config.
+        ``pushloc`` is the raw locations pushes should be made to.
+
+        If ``name`` is not defined, we require that the location be a) a local
+        filesystem path with a .hg directory or b) a URL. If not,
+        ``ValueError`` is raised.
+        """
+        if not rawloc:
+            raise ValueError(b'rawloc must be defined')
+
+        # Locations may define branches via syntax <base>#<branch>.
+        u = util.url(rawloc)
+        branch = None
+        if u.fragment:
+            branch = u.fragment
+            u.fragment = None
+
+        self.url = u
+        # the url from the config/command line before dealing with `path://`
+        self.raw_url = u.copy()
+        self.branch = branch
+
+        self.name = name
+        self.rawloc = rawloc
+        self.loc = b'%s' % u
+
+        self._validate_path()
+
+        _path, sub_opts = ui.configsuboptions(b'paths', b'*')
+        self._own_sub_opts = {}
+        if suboptions is not None:
+            self._own_sub_opts = suboptions.copy()
+            sub_opts.update(suboptions)
+        self._all_sub_opts = sub_opts.copy()
+
+        self._apply_suboptions(ui, sub_opts)
+
+    def chain_path(self, ui, paths):
+        if self.url.scheme == b'path':
+            assert self.url.path is None
+            try:
+                subpath = paths[self.url.host]
+            except KeyError:
+                m = _('cannot use `%s`, "%s" is not a known path')
+                m %= (self.rawloc, self.url.host)
+                raise error.Abort(m)
+            if subpath.raw_url.scheme == b'path':
+                m = _('cannot use `%s`, "%s" is also define as a `path://`')
+                m %= (self.rawloc, self.url.host)
+                raise error.Abort(m)
+            self.url = subpath.url
+            self.rawloc = subpath.rawloc
+            self.loc = subpath.loc
+            if self.branch is None:
+                self.branch = subpath.branch
+            else:
+                base = self.rawloc.rsplit(b'#', 1)[0]
+                self.rawloc = b'%s#%s' % (base, self.branch)
+            suboptions = subpath._all_sub_opts.copy()
+            suboptions.update(self._own_sub_opts)
+            self._apply_suboptions(ui, suboptions)
+
+    def _validate_path(self):
+        # When given a raw location but not a symbolic name, validate the
+        # location is valid.
+        if (
+            not self.name
+            and not self.url.scheme
+            and not self._isvalidlocalpath(self.loc)
+        ):
+            raise ValueError(
+                b'location is not a URL or path to a local '
+                b'repo: %s' % self.rawloc
+            )
+
+    def _apply_suboptions(self, ui, sub_options):
+        # Now process the sub-options. If a sub-option is registered, its
+        # attribute will always be present. The value will be None if there
+        # was no valid sub-option.
+        for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
+            if suboption not in sub_options:
+                setattr(self, attr, None)
+                continue
+
+            value = func(ui, self, sub_options[suboption])
+            setattr(self, attr, value)
+
+    def _isvalidlocalpath(self, path):
+        """Returns True if the given path is a potentially valid repository.
+        This is its own function so that extensions can change the definition of
+        'valid' in this case (like when pulling from a git repo into a hg
+        one)."""
+        try:
+            return os.path.isdir(os.path.join(path, b'.hg'))
+        # Python 2 may return TypeError. Python 3, ValueError.
+        except (TypeError, ValueError):
+            return False
+
+    @property
+    def suboptions(self):
+        """Return sub-options and their values for this path.
+
+        This is intended to be used for presentation purposes.
+        """
+        d = {}
+        for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
+            value = getattr(self, attr)
+            if value is not None:
+                d[subopt] = value
+        return d
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -26,7 +26,6 @@ 
 from .pycompat import (
     getattr,
     open,
-    setattr,
 )
 
 from . import (
@@ -48,6 +47,7 @@ 
     procutil,
     resourceutil,
     stringutil,
+    urlutil,
 )
 
 urlreq = util.urlreq
@@ -1049,7 +1049,7 @@ 
 
     @util.propertycache
     def paths(self):
-        return paths(self)
+        return urlutil.paths(self)
 
     def getpath(self, *args, **kwargs):
         """see paths.getpath for details
@@ -2180,237 +2180,6 @@ 
         return util._estimatememory()
 
 
-class paths(dict):
-    """Represents a collection of paths and their configs.
-
-    Data is initially derived from ui instances and the config files they have
-    loaded.
-    """
-
-    def __init__(self, ui):
-        dict.__init__(self)
-
-        for name, loc in ui.configitems(b'paths', ignoresub=True):
-            # No location is the same as not existing.
-            if not loc:
-                continue
-            loc, sub_opts = ui.configsuboptions(b'paths', name)
-            self[name] = path(ui, name, rawloc=loc, suboptions=sub_opts)
-
-        for name, p in sorted(self.items()):
-            p.chain_path(ui, self)
-
-    def getpath(self, ui, name, default=None):
-        """Return a ``path`` from a string, falling back to default.
-
-        ``name`` can be a named path or locations. Locations are filesystem
-        paths or URIs.
-
-        Returns None if ``name`` is not a registered path, a URI, or a local
-        path to a repo.
-        """
-        # Only fall back to default if no path was requested.
-        if name is None:
-            if not default:
-                default = ()
-            elif not isinstance(default, (tuple, list)):
-                default = (default,)
-            for k in default:
-                try:
-                    return self[k]
-                except KeyError:
-                    continue
-            return None
-
-        # Most likely empty string.
-        # This may need to raise in the future.
-        if not name:
-            return None
-
-        try:
-            return self[name]
-        except KeyError:
-            # Try to resolve as a local path or URI.
-            try:
-                # we pass the ui instance are warning might need to be issued
-                return path(ui, None, rawloc=name)
-            except ValueError:
-                raise error.RepoError(_(b'repository %s does not exist') % name)
-
-
-_pathsuboptions = {}
-
-
-def pathsuboption(option, attr):
-    """Decorator used to declare a path sub-option.
-
-    Arguments are the sub-option name and the attribute it should set on
-    ``path`` instances.
-
-    The decorated function will receive as arguments a ``ui`` instance,
-    ``path`` instance, and the string value of this option from the config.
-    The function should return the value that will be set on the ``path``
-    instance.
-
-    This decorator can be used to perform additional verification of
-    sub-options and to change the type of sub-options.
-    """
-
-    def register(func):
-        _pathsuboptions[option] = (attr, func)
-        return func
-
-    return register
-
-
-@pathsuboption(b'pushurl', b'pushloc')
-def pushurlpathoption(ui, path, value):
-    u = util.url(value)
-    # Actually require a URL.
-    if not u.scheme:
-        ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
-        return None
-
-    # Don't support the #foo syntax in the push URL to declare branch to
-    # push.
-    if u.fragment:
-        ui.warn(
-            _(
-                b'("#fragment" in paths.%s:pushurl not supported; '
-                b'ignoring)\n'
-            )
-            % path.name
-        )
-        u.fragment = None
-
-    return bytes(u)
-
-
-@pathsuboption(b'pushrev', b'pushrev')
-def pushrevpathoption(ui, path, value):
-    return value
-
-
-class path(object):
-    """Represents an individual path and its configuration."""
-
-    def __init__(self, ui, name, rawloc=None, suboptions=None):
-        """Construct a path from its config options.
-
-        ``ui`` is the ``ui`` instance the path is coming from.
-        ``name`` is the symbolic name of the path.
-        ``rawloc`` is the raw location, as defined in the config.
-        ``pushloc`` is the raw locations pushes should be made to.
-
-        If ``name`` is not defined, we require that the location be a) a local
-        filesystem path with a .hg directory or b) a URL. If not,
-        ``ValueError`` is raised.
-        """
-        if not rawloc:
-            raise ValueError(b'rawloc must be defined')
-
-        # Locations may define branches via syntax <base>#<branch>.
-        u = util.url(rawloc)
-        branch = None
-        if u.fragment:
-            branch = u.fragment
-            u.fragment = None
-
-        self.url = u
-        # the url from the config/command line before dealing with `path://`
-        self.raw_url = u.copy()
-        self.branch = branch
-
-        self.name = name
-        self.rawloc = rawloc
-        self.loc = b'%s' % u
-
-        self._validate_path()
-
-        _path, sub_opts = ui.configsuboptions(b'paths', b'*')
-        self._own_sub_opts = {}
-        if suboptions is not None:
-            self._own_sub_opts = suboptions.copy()
-            sub_opts.update(suboptions)
-        self._all_sub_opts = sub_opts.copy()
-
-        self._apply_suboptions(ui, sub_opts)
-
-    def chain_path(self, ui, paths):
-        if self.url.scheme == b'path':
-            assert self.url.path is None
-            try:
-                subpath = paths[self.url.host]
-            except KeyError:
-                m = _('cannot use `%s`, "%s" is not a known path')
-                m %= (self.rawloc, self.url.host)
-                raise error.Abort(m)
-            if subpath.raw_url.scheme == b'path':
-                m = _('cannot use `%s`, "%s" is also define as a `path://`')
-                m %= (self.rawloc, self.url.host)
-                raise error.Abort(m)
-            self.url = subpath.url
-            self.rawloc = subpath.rawloc
-            self.loc = subpath.loc
-            if self.branch is None:
-                self.branch = subpath.branch
-            else:
-                base = self.rawloc.rsplit(b'#', 1)[0]
-                self.rawloc = b'%s#%s' % (base, self.branch)
-            suboptions = subpath._all_sub_opts.copy()
-            suboptions.update(self._own_sub_opts)
-            self._apply_suboptions(ui, suboptions)
-
-    def _validate_path(self):
-        # When given a raw location but not a symbolic name, validate the
-        # location is valid.
-        if (
-            not self.name
-            and not self.url.scheme
-            and not self._isvalidlocalpath(self.loc)
-        ):
-            raise ValueError(
-                b'location is not a URL or path to a local '
-                b'repo: %s' % self.rawloc
-            )
-
-    def _apply_suboptions(self, ui, sub_options):
-        # Now process the sub-options. If a sub-option is registered, its
-        # attribute will always be present. The value will be None if there
-        # was no valid sub-option.
-        for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
-            if suboption not in sub_options:
-                setattr(self, attr, None)
-                continue
-
-            value = func(ui, self, sub_options[suboption])
-            setattr(self, attr, value)
-
-    def _isvalidlocalpath(self, path):
-        """Returns True if the given path is a potentially valid repository.
-        This is its own function so that extensions can change the definition of
-        'valid' in this case (like when pulling from a git repo into a hg
-        one)."""
-        try:
-            return os.path.isdir(os.path.join(path, b'.hg'))
-        # Python 2 may return TypeError. Python 3, ValueError.
-        except (TypeError, ValueError):
-            return False
-
-    @property
-    def suboptions(self):
-        """Return sub-options and their values for this path.
-
-        This is intended to be used for presentation purposes.
-        """
-        d = {}
-        for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
-            value = getattr(self, attr)
-            if value is not None:
-                d[subopt] = value
-        return d
-
-
 # we instantiate one globally shared progress bar to avoid
 # competing progress bars when multiple UI objects get created
 _progresssingleton = None