##// END OF EJS Templates
run-test: use byte for 'common-pattern.py' path...
run-test: use byte for 'common-pattern.py' path This is required for Python 3.

File last commit:

r34981:705d0f2b stable
r35090:6a8e8570 default
Show More
pathutil.py
263 lines | 9.4 KiB | text/x-python | PythonLexer
Gregory Szorc
pathutil: use absolute_import
r25964 from __future__ import absolute_import
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033
Gregory Szorc
pathutil: use absolute_import
r25964 import errno
import os
import posixpath
import stat
from .i18n import _
from . import (
encoding,
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 error,
Pulkit Goyal
py3: replace os.sep with pycompat.ossep (part 2 of 4)...
r30614 pycompat,
Gregory Szorc
pathutil: use absolute_import
r25964 util,
)
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033
Augie Fackler
pathauditor: check for codepoints ignored on OS X
r23598 def _lowerclean(s):
return encoding.hfsignoreclean(s.lower())
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 class pathauditor(object):
'''ensure that a filesystem path contains no banned components.
the following properties of a path are checked:
- ends with a directory separator
- under top-level .hg
- starts at the root of a windows drive
- contains ".."
Pierre-Yves David
pathauditor: add a way to skip file system check...
r27232
More check are also done about the file system states:
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 - traverses a symlink (e.g. a/symlink_here/b)
- inside a nested repository (a callback can be used to approve
some nested repositories, e.g., subrepositories)
Pierre-Yves David
pathauditor: add a way to skip file system check...
r27232
The file system checks are only done when 'realfs' is set to True (the
default). They should be disable then we are auditing path for operation on
stored history.
Yuya Nishihara
pathauditor: disable cache of audited paths by default (issue5628)...
r33722
If 'cached' is set to True, audited paths and sub-directories are cached.
Be careful to not keep the cache of unmanaged directories for long because
audited paths may be replaced with symlinks.
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 '''
Yuya Nishihara
pathauditor: disable cache of audited paths by default (issue5628)...
r33722 def __init__(self, root, callback=None, realfs=True, cached=False):
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 self.audited = set()
self.auditeddir = set()
self.root = root
Pierre-Yves David
pathauditor: add a way to skip file system check...
r27232 self._realfs = realfs
Yuya Nishihara
pathauditor: disable cache of audited paths by default (issue5628)...
r33722 self._cached = cached
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 self.callback = callback
Martin von Zweigbergk
util: rename checkcase() to fscasesensitive() (API)...
r29889 if os.path.lexists(root) and not util.fscasesensitive(root):
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 self.normcase = util.normcase
else:
self.normcase = lambda x: x
Boris Feld
vfs: allow to pass more argument to audit...
r33435 def __call__(self, path, mode=None):
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 '''Check the relative path.
path may contain a pattern (e.g. foodir/**.txt)'''
path = util.localpath(path)
normpath = self.normcase(path)
if normpath in self.audited:
return
# AIX ignores "/" at end of path, others raise EISDIR.
if util.endswithsep(path):
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("path ends in directory separator: %s") % path)
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 parts = util.splitpath(path)
if (os.path.splitdrive(path)[0]
Augie Fackler
pathauditor: check for codepoints ignored on OS X
r23598 or _lowerclean(parts[0]) in ('.hg', '.hg.', '')
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 or os.pardir in parts):
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("path contains illegal component: %s") % path)
Matt Mackall
pathauditor: check for Windows shortname aliases
r23599 # Windows shortname aliases
for p in parts:
if "~" in p:
first, last = p.split("~", 1)
if last.isdigit() and first.upper() in ["HG", "HG8B6C"]:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("path contains illegal component: %s")
Matt Mackall
pathauditor: check for Windows shortname aliases
r23599 % path)
Augie Fackler
pathauditor: check for codepoints ignored on OS X
r23598 if '.hg' in _lowerclean(path):
lparts = [_lowerclean(p.lower()) for p in parts]
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 for p in '.hg', '.hg.':
if p in lparts[1:]:
pos = lparts.index(p)
base = os.path.join(*parts[:pos])
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("path '%s' is inside nested repo %r")
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 % (path, base))
normparts = util.splitpath(normpath)
assert len(parts) == len(normparts)
parts.pop()
normparts.pop()
prefixes = []
Durham Goode
pathauditor: change parts verification order to be root first...
r28087 # It's important that we check the path parts starting from the root.
Mads Kiilerich
spelling: fixes of non-dictionary words
r30332 # This means we won't accidentally traverse a symlink into some other
Durham Goode
pathauditor: change parts verification order to be root first...
r28087 # filesystem (which is potentially expensive to access).
for i in range(len(parts)):
Pulkit Goyal
py3: replace os.sep with pycompat.ossep (part 2 of 4)...
r30614 prefix = pycompat.ossep.join(parts[:i + 1])
normprefix = pycompat.ossep.join(normparts[:i + 1])
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 if normprefix in self.auditeddir:
Durham Goode
pathauditor: change parts verification order to be root first...
r28087 continue
Pierre-Yves David
pathauditor: add a way to skip file system check...
r27232 if self._realfs:
self._checkfs(prefix, path)
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 prefixes.append(normprefix)
Yuya Nishihara
pathauditor: disable cache of audited paths by default (issue5628)...
r33722 if self._cached:
self.audited.add(normpath)
# only add prefixes to the cache after checking everything: we don't
# want to add "foo/bar/baz" before checking if there's a "foo/.hg"
self.auditeddir.update(prefixes)
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033
Pierre-Yves David
pathauditor: move file system specific check in their own function...
r27231 def _checkfs(self, prefix, path):
"""raise exception if a file system backed check fails"""
curpath = os.path.join(self.root, prefix)
try:
st = os.lstat(curpath)
except OSError as err:
# EINVAL can be raised as invalid path syntax under win32.
# They must be ignored for patterns can be checked too.
if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
raise
else:
if stat.S_ISLNK(st.st_mode):
Pierre-Yves David
pathutil: use temporary variables instead of complicated wrapping...
r27235 msg = _('path %r traverses symbolic link %r') % (path, prefix)
raise error.Abort(msg)
Pierre-Yves David
pathauditor: move file system specific check in their own function...
r27231 elif (stat.S_ISDIR(st.st_mode) and
os.path.isdir(os.path.join(curpath, '.hg'))):
if not self.callback or not self.callback(curpath):
Pierre-Yves David
pathutil: use temporary variables instead of complicated wrapping...
r27235 msg = _("path '%s' is inside nested repo %r")
raise error.Abort(msg % (path, prefix))
Pierre-Yves David
pathauditor: move file system specific check in their own function...
r27231
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 def check(self, path):
try:
self(path)
return True
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 except (OSError, error.Abort):
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 return False
def canonpath(root, cwd, myname, auditor=None):
Matt Harbison
pathutil: add doctests for canonpath()...
r34981 '''return the canonical path of myname, given cwd and root
>>> def check(root, cwd, myname):
... a = pathauditor(root, realfs=False)
... try:
... return canonpath(root, cwd, myname, a)
... except error.Abort:
... return 'aborted'
>>> def unixonly(root, cwd, myname, expected='aborted'):
... if pycompat.iswindows:
... return expected
... return check(root, cwd, myname)
>>> def winonly(root, cwd, myname, expected='aborted'):
... if not pycompat.iswindows:
... return expected
... return check(root, cwd, myname)
>>> winonly(b'd:\\\\repo', b'c:\\\\dir', b'filename')
'aborted'
>>> winonly(b'c:\\\\repo', b'c:\\\\dir', b'filename')
'aborted'
>>> winonly(b'c:\\\\repo', b'c:\\\\', b'filename')
'aborted'
>>> winonly(b'c:\\\\repo', b'c:\\\\', b'repo\\\\filename',
... b'filename')
'filename'
>>> winonly(b'c:\\\\repo', b'c:\\\\repo', b'filename', b'filename')
'filename'
>>> winonly(b'c:\\\\repo', b'c:\\\\repo\\\\subdir', b'filename',
... b'subdir/filename')
'subdir/filename'
>>> unixonly(b'/repo', b'/dir', b'filename')
'aborted'
>>> unixonly(b'/repo', b'/', b'filename')
'aborted'
>>> unixonly(b'/repo', b'/', b'repo/filename', b'filename')
'filename'
>>> unixonly(b'/repo', b'/repo', b'filename', b'filename')
'filename'
>>> unixonly(b'/repo', b'/repo/subdir', b'filename', b'subdir/filename')
'subdir/filename'
'''
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 if util.endswithsep(root):
rootsep = root
else:
Pulkit Goyal
py3: replace os.sep with pycompat.ossep (part 2 of 4)...
r30614 rootsep = root + pycompat.ossep
Augie Fackler
pathutil: tease out a new library to break an import cycle from canonpath use
r20033 name = myname
if not os.path.isabs(name):
name = os.path.join(root, cwd, name)
name = os.path.normpath(name)
if auditor is None:
auditor = pathauditor(root)
if name != rootsep and name.startswith(rootsep):
name = name[len(rootsep):]
auditor(name)
return util.pconvert(name)
elif name == root:
return ''
else:
# Determine whether `name' is in the hierarchy at or beneath `root',
# by iterating name=dirname(name) until that causes no change (can't
# check name == '/', because that doesn't work on windows). The list
# `rel' holds the reversed list of components making up the relative
# file name we want.
rel = []
while True:
try:
s = util.samefile(name, root)
except OSError:
s = False
if s:
if not rel:
# name was actually the same as root (maybe a symlink)
return ''
rel.reverse()
name = os.path.join(*rel)
auditor(name)
return util.pconvert(name)
dirname, basename = util.split(name)
rel.append(basename)
if dirname == name:
break
name = dirname
Matt Harbison
pathutil: hint if a path is root relative instead of cwd relative (issue4663)...
r25011 # A common mistake is to use -R, but specify a file relative to the repo
# instead of cwd. Detect that case, and provide a hint to the user.
hint = None
try:
Matt Mackall
canonpath: fix infinite recursion
r25022 if cwd != root:
canonpath(root, root, myname, auditor)
Matt Harbison
pathutil: use util.pathto() to calculate relative cwd in canonpath()...
r34966 relpath = util.pathto(root, cwd, '')
if relpath[-1] == pycompat.ossep:
relpath = relpath[:-1]
hint = (_("consider using '--cwd %s'") % relpath)
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 except error.Abort:
Matt Harbison
pathutil: hint if a path is root relative instead of cwd relative (issue4663)...
r25011 pass
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("%s not under root '%s'") % (myname, root),
Matt Harbison
pathutil: hint if a path is root relative instead of cwd relative (issue4663)...
r25011 hint=hint)
FUJIWARA Katsunori
subrepo: normalize path in the specific way for problematic encodings...
r21568
def normasprefix(path):
'''normalize the specified path as path prefix
Mads Kiilerich
spelling: fixes from proofreading of spell checker issues
r23139 Returned value can be used safely for "p.startswith(prefix)",
FUJIWARA Katsunori
subrepo: normalize path in the specific way for problematic encodings...
r21568 "p[len(prefix):]", and so on.
For efficiency, this expects "path" argument to be already
normalized by "os.path.normpath", "os.path.realpath", and so on.
See also issue3033 for detail about need of this function.
Yuya Nishihara
py3: use bytes os.sep in doctest of pathutil.py
r34255 >>> normasprefix(b'/foo/bar').replace(pycompat.ossep, b'/')
FUJIWARA Katsunori
subrepo: normalize path in the specific way for problematic encodings...
r21568 '/foo/bar/'
Yuya Nishihara
py3: use bytes os.sep in doctest of pathutil.py
r34255 >>> normasprefix(b'/').replace(pycompat.ossep, b'/')
FUJIWARA Katsunori
subrepo: normalize path in the specific way for problematic encodings...
r21568 '/'
'''
d, p = os.path.splitdrive(path)
Pulkit Goyal
py3: replace os.sep with pycompat.ossep (part 2 of 4)...
r30614 if len(p) != len(pycompat.ossep):
return path + pycompat.ossep
FUJIWARA Katsunori
subrepo: normalize path in the specific way for problematic encodings...
r21568 else:
return path
Durham Goode
pathutil: add dirname and join functions...
r25281
Augie Fackler
pathutil: demote two local functions to just be forwards...
r25286 # forward two methods from posixpath that do what we need, but we'd
# rather not let our internals know that we're thinking in posix terms
# - instead we'll let them be oblivious.
join = posixpath.join
dirname = posixpath.dirname