mercurial/scmutil.py
changeset 20033 f962870712da
parent 20006 9276014db865
child 20042 9a72d3886888
equal deleted inserted replaced
20032:175c6fd8cacc 20033:f962870712da
     6 # GNU General Public License version 2 or any later version.
     6 # GNU General Public License version 2 or any later version.
     7 
     7 
     8 from i18n import _
     8 from i18n import _
     9 from mercurial.node import nullrev
     9 from mercurial.node import nullrev
    10 import util, error, osutil, revset, similar, encoding, phases, parsers
    10 import util, error, osutil, revset, similar, encoding, phases, parsers
       
    11 import pathutil
    11 import match as matchmod
    12 import match as matchmod
    12 import os, errno, re, stat, glob
    13 import os, errno, re, glob
    13 
    14 
    14 if os.name == 'nt':
    15 if os.name == 'nt':
    15     import scmwindows as scmplatform
    16     import scmwindows as scmplatform
    16 else:
    17 else:
    17     import scmposix as scmplatform
    18     import scmposix as scmplatform
   106                 raise util.Abort(msg)
   107                 raise util.Abort(msg)
   107             self._ui.warn(_("warning: %s\n") % msg)
   108             self._ui.warn(_("warning: %s\n") % msg)
   108         self._loweredfiles.add(fl)
   109         self._loweredfiles.add(fl)
   109         self._newfiles.add(f)
   110         self._newfiles.add(f)
   110 
   111 
   111 class pathauditor(object):
       
   112     '''ensure that a filesystem path contains no banned components.
       
   113     the following properties of a path are checked:
       
   114 
       
   115     - ends with a directory separator
       
   116     - under top-level .hg
       
   117     - starts at the root of a windows drive
       
   118     - contains ".."
       
   119     - traverses a symlink (e.g. a/symlink_here/b)
       
   120     - inside a nested repository (a callback can be used to approve
       
   121       some nested repositories, e.g., subrepositories)
       
   122     '''
       
   123 
       
   124     def __init__(self, root, callback=None):
       
   125         self.audited = set()
       
   126         self.auditeddir = set()
       
   127         self.root = root
       
   128         self.callback = callback
       
   129         if os.path.lexists(root) and not util.checkcase(root):
       
   130             self.normcase = util.normcase
       
   131         else:
       
   132             self.normcase = lambda x: x
       
   133 
       
   134     def __call__(self, path):
       
   135         '''Check the relative path.
       
   136         path may contain a pattern (e.g. foodir/**.txt)'''
       
   137 
       
   138         path = util.localpath(path)
       
   139         normpath = self.normcase(path)
       
   140         if normpath in self.audited:
       
   141             return
       
   142         # AIX ignores "/" at end of path, others raise EISDIR.
       
   143         if util.endswithsep(path):
       
   144             raise util.Abort(_("path ends in directory separator: %s") % path)
       
   145         parts = util.splitpath(path)
       
   146         if (os.path.splitdrive(path)[0]
       
   147             or parts[0].lower() in ('.hg', '.hg.', '')
       
   148             or os.pardir in parts):
       
   149             raise util.Abort(_("path contains illegal component: %s") % path)
       
   150         if '.hg' in path.lower():
       
   151             lparts = [p.lower() for p in parts]
       
   152             for p in '.hg', '.hg.':
       
   153                 if p in lparts[1:]:
       
   154                     pos = lparts.index(p)
       
   155                     base = os.path.join(*parts[:pos])
       
   156                     raise util.Abort(_("path '%s' is inside nested repo %r")
       
   157                                      % (path, base))
       
   158 
       
   159         normparts = util.splitpath(normpath)
       
   160         assert len(parts) == len(normparts)
       
   161 
       
   162         parts.pop()
       
   163         normparts.pop()
       
   164         prefixes = []
       
   165         while parts:
       
   166             prefix = os.sep.join(parts)
       
   167             normprefix = os.sep.join(normparts)
       
   168             if normprefix in self.auditeddir:
       
   169                 break
       
   170             curpath = os.path.join(self.root, prefix)
       
   171             try:
       
   172                 st = os.lstat(curpath)
       
   173             except OSError, err:
       
   174                 # EINVAL can be raised as invalid path syntax under win32.
       
   175                 # They must be ignored for patterns can be checked too.
       
   176                 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
       
   177                     raise
       
   178             else:
       
   179                 if stat.S_ISLNK(st.st_mode):
       
   180                     raise util.Abort(
       
   181                         _('path %r traverses symbolic link %r')
       
   182                         % (path, prefix))
       
   183                 elif (stat.S_ISDIR(st.st_mode) and
       
   184                       os.path.isdir(os.path.join(curpath, '.hg'))):
       
   185                     if not self.callback or not self.callback(curpath):
       
   186                         raise util.Abort(_("path '%s' is inside nested "
       
   187                                            "repo %r")
       
   188                                          % (path, prefix))
       
   189             prefixes.append(normprefix)
       
   190             parts.pop()
       
   191             normparts.pop()
       
   192 
       
   193         self.audited.add(normpath)
       
   194         # only add prefixes to the cache after checking everything: we don't
       
   195         # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
       
   196         self.auditeddir.update(prefixes)
       
   197 
       
   198     def check(self, path):
       
   199         try:
       
   200             self(path)
       
   201             return True
       
   202         except (OSError, util.Abort):
       
   203             return False
       
   204 
       
   205 class abstractvfs(object):
   112 class abstractvfs(object):
   206     """Abstract base class; cannot be instantiated"""
   113     """Abstract base class; cannot be instantiated"""
   207 
   114 
   208     def __init__(self, *args, **kwargs):
   115     def __init__(self, *args, **kwargs):
   209         '''Prevent instantiation; don't call this from subclasses.'''
   116         '''Prevent instantiation; don't call this from subclasses.'''
   308         return self._audit
   215         return self._audit
   309 
   216 
   310     def _setmustaudit(self, onoff):
   217     def _setmustaudit(self, onoff):
   311         self._audit = onoff
   218         self._audit = onoff
   312         if onoff:
   219         if onoff:
   313             self.audit = pathauditor(self.base)
   220             self.audit = pathutil.pathauditor(self.base)
   314         else:
   221         else:
   315             self.audit = util.always
   222             self.audit = util.always
   316 
   223 
   317     mustaudit = property(_getmustaudit, _setmustaudit)
   224     mustaudit = property(_getmustaudit, _setmustaudit)
   318 
   225 
   443         if mode not in ('r', 'rb'):
   350         if mode not in ('r', 'rb'):
   444             raise util.Abort('this vfs is read only')
   351             raise util.Abort('this vfs is read only')
   445         return self.vfs(path, mode, *args, **kw)
   352         return self.vfs(path, mode, *args, **kw)
   446 
   353 
   447 
   354 
   448 def canonpath(root, cwd, myname, auditor=None):
       
   449     '''return the canonical path of myname, given cwd and root'''
       
   450     if util.endswithsep(root):
       
   451         rootsep = root
       
   452     else:
       
   453         rootsep = root + os.sep
       
   454     name = myname
       
   455     if not os.path.isabs(name):
       
   456         name = os.path.join(root, cwd, name)
       
   457     name = os.path.normpath(name)
       
   458     if auditor is None:
       
   459         auditor = pathauditor(root)
       
   460     if name != rootsep and name.startswith(rootsep):
       
   461         name = name[len(rootsep):]
       
   462         auditor(name)
       
   463         return util.pconvert(name)
       
   464     elif name == root:
       
   465         return ''
       
   466     else:
       
   467         # Determine whether `name' is in the hierarchy at or beneath `root',
       
   468         # by iterating name=dirname(name) until that causes no change (can't
       
   469         # check name == '/', because that doesn't work on windows). The list
       
   470         # `rel' holds the reversed list of components making up the relative
       
   471         # file name we want.
       
   472         rel = []
       
   473         while True:
       
   474             try:
       
   475                 s = util.samefile(name, root)
       
   476             except OSError:
       
   477                 s = False
       
   478             if s:
       
   479                 if not rel:
       
   480                     # name was actually the same as root (maybe a symlink)
       
   481                     return ''
       
   482                 rel.reverse()
       
   483                 name = os.path.join(*rel)
       
   484                 auditor(name)
       
   485                 return util.pconvert(name)
       
   486             dirname, basename = util.split(name)
       
   487             rel.append(basename)
       
   488             if dirname == name:
       
   489                 break
       
   490             name = dirname
       
   491 
       
   492         raise util.Abort(_("%s not under root '%s'") % (myname, root))
       
   493 
       
   494 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
   355 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
   495     '''yield every hg repository under path, always recursively.
   356     '''yield every hg repository under path, always recursively.
   496     The recurse flag will only control recursion into repo working dirs'''
   357     The recurse flag will only control recursion into repo working dirs'''
   497     def errhandler(err):
   358     def errhandler(err):
   498         if err.filename == path:
   359         if err.filename == path:
   766     about.
   627     about.
   767 
   628 
   768     This is different from dirstate.status because it doesn't care about
   629     This is different from dirstate.status because it doesn't care about
   769     whether files are modified or clean.'''
   630     whether files are modified or clean.'''
   770     added, unknown, deleted, removed = [], [], [], []
   631     added, unknown, deleted, removed = [], [], [], []
   771     audit_path = pathauditor(repo.root)
   632     audit_path = pathutil.pathauditor(repo.root)
   772 
   633 
   773     ctx = repo[None]
   634     ctx = repo[None]
   774     dirstate = repo.dirstate
   635     dirstate = repo.dirstate
   775     walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
   636     walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
   776                                 full=False)
   637                                 full=False)