Mercurial > hg
view mercurial/policy.py @ 35777:0c0689a7565e
subrepo: handle 'C:' style paths on the command line (issue5770)
If you think 'C:' and 'C:\' are equivalent paths, see the inline comment before
proceeding.
The problem here was that several commands that take a URL argument (incoming,
outgoing, pull, and push) will use that value to set 'repo._subtoppath' on the
repository object after command specific manipulation of it, but before
converting it to an absolute path. When an operation is performed on a relative
subrepo, subrepo._abssource() will posixpath.join() this value with the relative
subrepo path. That adds a '/' after the drive letter, changing how it is
evaluated by abspath()/realpath() in vfsmod.vfs(..., realpath=True) as the
subrepo is instantiated.
I initially tried sanitizing the path in url.localpath(), because url.isabs()
only checks that it starts with a drive letter. By the sample behavior, this is
clearly not an absolute path. (Though the comment in isabs() is weasely- this
style path can't be joined either.) But not everything funnels through there,
and it required explicitly calling localpath() in hg.parseurl() and assigning to
url.path to fix. But then tests failed with urls like 'a#0'.
Next up was sanitizing the path in the url constructor. That caused doctest
failures, because there are drive letter tests, so those got expanded in system
specific ways. Yuya correctly pointed out that util.url is a parser, and
shouldn't be substituting the path too.
Rather than fixing every command call site, just convert it in the common
subrepo location. I don't see any sanitizing on the path config options, so I
fixed those too. Note that while the behavior is fixed here, there are still
places where 'comparing with C:' gets printed out, and that's not great for
debugging purposes. (Specifically I saw it in `hg incoming -B C:`, without
subrepos.) While clone will write out an absolute default path, I wonder what
would happen if a user edited that path to be 'C:'. (I don't think supporting
relative paths in .hgrc is a sane thing to do, but while we're poking holes in
things...)
Since this is such an oddball case, it still leaks through in places, and there
seems to be a lot of duplicate url parsing, maybe the url parsing should be
moved to dispatch, and provide the command with a url object? Then we could
convert this to an absolute path once, and not have to worry about it in the
rest of the code.
I also checked '--cwd C:' on the command line, and it was previously working
because os.chdir() will DTRT.
Finally, one other note from the url.localpath() experimenting. I don't see any
cases where 'self._hostport' can hold a drive letter. So I'm wondering if that
is wrong/old code.
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Sun, 21 Jan 2018 13:54:05 -0500 |
parents | e01549a7bf0a |
children | 29dd37a418aa |
line wrap: on
line source
# policy.py - module policy logic for Mercurial. # # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import import os import sys # Rules for how modules can be loaded. Values are: # # c - require C extensions # allow - allow pure Python implementation when C loading fails # cffi - required cffi versions (implemented within pure module) # cffi-allow - allow pure Python implementation if cffi version is missing # py - only load pure Python modules # # By default, fall back to the pure modules so the in-place build can # run without recompiling the C extensions. This will be overridden by # __modulepolicy__ generated by setup.py. policy = b'allow' _packageprefs = { # policy: (versioned package, pure package) b'c': (r'cext', None), b'allow': (r'cext', r'pure'), b'cffi': (r'cffi', None), b'cffi-allow': (r'cffi', r'pure'), b'py': (None, r'pure'), } try: from . import __modulepolicy__ policy = __modulepolicy__.modulepolicy except ImportError: pass # PyPy doesn't load C extensions. # # The canonical way to do this is to test platform.python_implementation(). # But we don't import platform and don't bloat for it here. if r'__pypy__' in sys.builtin_module_names: policy = b'cffi' # Our C extensions aren't yet compatible with Python 3. So use pure Python # on Python 3 for now. if sys.version_info[0] >= 3: policy = b'py' # Environment variable can always force settings. if sys.version_info[0] >= 3: if r'HGMODULEPOLICY' in os.environ: policy = os.environ[r'HGMODULEPOLICY'].encode(r'utf-8') else: policy = os.environ.get(r'HGMODULEPOLICY', policy) def _importfrom(pkgname, modname): # from .<pkgname> import <modname> (where . is looked through this module) fakelocals = {} pkg = __import__(pkgname, globals(), fakelocals, [modname], level=1) try: fakelocals[modname] = mod = getattr(pkg, modname) except AttributeError: raise ImportError(r'cannot import name %s' % modname) # force import; fakelocals[modname] may be replaced with the real module getattr(mod, r'__doc__', None) return fakelocals[modname] # keep in sync with "version" in C modules _cextversions = { (r'cext', r'base85'): 1, (r'cext', r'bdiff'): 1, (r'cext', r'diffhelpers'): 1, (r'cext', r'mpatch'): 1, (r'cext', r'osutil'): 3, (r'cext', r'parsers'): 4, } # map import request to other package or module _modredirects = { (r'cext', r'charencode'): (r'cext', r'parsers'), (r'cffi', r'base85'): (r'pure', r'base85'), (r'cffi', r'charencode'): (r'pure', r'charencode'), (r'cffi', r'diffhelpers'): (r'pure', r'diffhelpers'), (r'cffi', r'parsers'): (r'pure', r'parsers'), } def _checkmod(pkgname, modname, mod): expected = _cextversions.get((pkgname, modname)) actual = getattr(mod, r'version', None) if actual != expected: raise ImportError(r'cannot import module %s.%s ' r'(expected version: %d, actual: %r)' % (pkgname, modname, expected, actual)) def importmod(modname): """Import module according to policy and check API version""" try: verpkg, purepkg = _packageprefs[policy] except KeyError: raise ImportError(r'invalid HGMODULEPOLICY %r' % policy) assert verpkg or purepkg if verpkg: pn, mn = _modredirects.get((verpkg, modname), (verpkg, modname)) try: mod = _importfrom(pn, mn) if pn == verpkg: _checkmod(pn, mn, mod) return mod except ImportError: if not purepkg: raise pn, mn = _modredirects.get((purepkg, modname), (purepkg, modname)) return _importfrom(pn, mn)