urlutil: extract `path` related code into a new module
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.
Differential Revision: https://phab.mercurial-scm.org/D10373
--- a/mercurial/ui.py Sat Apr 10 15:30:32 2021 +0200
+++ b/mercurial/ui.py Sun Apr 11 23:54:35 2021 +0200
@@ -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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/utils/urlutil.py Sun Apr 11 23:54:35 2021 +0200
@@ -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