Mercurial > public > mercurial-scm > hg-stable
view mercurial/utils/urlutil.py @ 46906:33524c46a092
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
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Sun, 11 Apr 2021 23:54:35 +0200 |
parents | |
children | ffd3e823a7e5 |
line wrap: on
line source
# 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