mercurial/scmutil.py
changeset 13972 d1f4e7fd970a
parent 13971 bfeaa88b875d
child 13973 366fa83f9820
equal deleted inserted replaced
13971:bfeaa88b875d 13972:d1f4e7fd970a
     5 # This software may be used and distributed according to the terms of the
     5 # This software may be used and distributed according to the terms of the
     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 import util, error
     9 import util, error
    10 import os, errno
    10 import os, errno, stat
    11 
    11 
    12 def checkportable(ui, f):
    12 def checkportable(ui, f):
    13     '''Check if filename f is portable and warn or abort depending on config'''
    13     '''Check if filename f is portable and warn or abort depending on config'''
    14     util.checkfilename(f)
    14     util.checkfilename(f)
    15     val = ui.config('ui', 'portablefilenames', 'warn')
    15     val = ui.config('ui', 'portablefilenames', 'warn')
    24             ui.warn(_("warning: %s: %r\n") % (msg, f))
    24             ui.warn(_("warning: %s: %r\n") % (msg, f))
    25     elif bval is None and lval != 'ignore':
    25     elif bval is None and lval != 'ignore':
    26         raise error.ConfigError(
    26         raise error.ConfigError(
    27             _("ui.portablefilenames value is invalid ('%s')") % val)
    27             _("ui.portablefilenames value is invalid ('%s')") % val)
    28 
    28 
       
    29 class path_auditor(object):
       
    30     '''ensure that a filesystem path contains no banned components.
       
    31     the following properties of a path are checked:
       
    32 
       
    33     - ends with a directory separator
       
    34     - under top-level .hg
       
    35     - starts at the root of a windows drive
       
    36     - contains ".."
       
    37     - traverses a symlink (e.g. a/symlink_here/b)
       
    38     - inside a nested repository (a callback can be used to approve
       
    39       some nested repositories, e.g., subrepositories)
       
    40     '''
       
    41 
       
    42     def __init__(self, root, callback=None):
       
    43         self.audited = set()
       
    44         self.auditeddir = set()
       
    45         self.root = root
       
    46         self.callback = callback
       
    47 
       
    48     def __call__(self, path):
       
    49         '''Check the relative path.
       
    50         path may contain a pattern (e.g. foodir/**.txt)'''
       
    51 
       
    52         if path in self.audited:
       
    53             return
       
    54         # AIX ignores "/" at end of path, others raise EISDIR.
       
    55         if util.endswithsep(path):
       
    56             raise util.Abort(_("path ends in directory separator: %s") % path)
       
    57         normpath = os.path.normcase(path)
       
    58         parts = util.splitpath(normpath)
       
    59         if (os.path.splitdrive(path)[0]
       
    60             or parts[0].lower() in ('.hg', '.hg.', '')
       
    61             or os.pardir in parts):
       
    62             raise util.Abort(_("path contains illegal component: %s") % path)
       
    63         if '.hg' in path.lower():
       
    64             lparts = [p.lower() for p in parts]
       
    65             for p in '.hg', '.hg.':
       
    66                 if p in lparts[1:]:
       
    67                     pos = lparts.index(p)
       
    68                     base = os.path.join(*parts[:pos])
       
    69                     raise util.Abort(_('path %r is inside nested repo %r')
       
    70                                      % (path, base))
       
    71 
       
    72         parts.pop()
       
    73         prefixes = []
       
    74         while parts:
       
    75             prefix = os.sep.join(parts)
       
    76             if prefix in self.auditeddir:
       
    77                 break
       
    78             curpath = os.path.join(self.root, prefix)
       
    79             try:
       
    80                 st = os.lstat(curpath)
       
    81             except OSError, err:
       
    82                 # EINVAL can be raised as invalid path syntax under win32.
       
    83                 # They must be ignored for patterns can be checked too.
       
    84                 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
       
    85                     raise
       
    86             else:
       
    87                 if stat.S_ISLNK(st.st_mode):
       
    88                     raise util.Abort(
       
    89                         _('path %r traverses symbolic link %r')
       
    90                         % (path, prefix))
       
    91                 elif (stat.S_ISDIR(st.st_mode) and
       
    92                       os.path.isdir(os.path.join(curpath, '.hg'))):
       
    93                     if not self.callback or not self.callback(curpath):
       
    94                         raise util.Abort(_('path %r is inside nested repo %r') %
       
    95                                          (path, prefix))
       
    96             prefixes.append(prefix)
       
    97             parts.pop()
       
    98 
       
    99         self.audited.add(path)
       
   100         # only add prefixes to the cache after checking everything: we don't
       
   101         # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
       
   102         self.auditeddir.update(prefixes)
       
   103 
    29 class opener(object):
   104 class opener(object):
    30     '''Open files relative to a base directory
   105     '''Open files relative to a base directory
    31 
   106 
    32     This class is used to hide the details of COW semantics and
   107     This class is used to hide the details of COW semantics and
    33     remote file access from higher level code.
   108     remote file access from higher level code.
    34     '''
   109     '''
    35     def __init__(self, base, audit=True):
   110     def __init__(self, base, audit=True):
    36         self.base = base
   111         self.base = base
    37         if audit:
   112         if audit:
    38             self.auditor = util.path_auditor(base)
   113             self.auditor = path_auditor(base)
    39         else:
   114         else:
    40             self.auditor = util.always
   115             self.auditor = util.always
    41         self.createmode = None
   116         self.createmode = None
    42         self._trustnlink = None
   117         self._trustnlink = None
    43 
   118 
   130     name = myname
   205     name = myname
   131     if not os.path.isabs(name):
   206     if not os.path.isabs(name):
   132         name = os.path.join(root, cwd, name)
   207         name = os.path.join(root, cwd, name)
   133     name = os.path.normpath(name)
   208     name = os.path.normpath(name)
   134     if auditor is None:
   209     if auditor is None:
   135         auditor = util.path_auditor(root)
   210         auditor = path_auditor(root)
   136     if name != rootsep and name.startswith(rootsep):
   211     if name != rootsep and name.startswith(rootsep):
   137         name = name[len(rootsep):]
   212         name = name[len(rootsep):]
   138         auditor(name)
   213         auditor(name)
   139         return util.pconvert(name)
   214         return util.pconvert(name)
   140     elif name == root:
   215     elif name == root: