Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/scmutil.py @ 13972:d1f4e7fd970a
move path_auditor from util to scmutil
author | Adrian Buehlmann <adrian@cadifra.com> |
---|---|
date | Wed, 20 Apr 2011 22:43:31 +0200 |
parents | bfeaa88b875d |
children | 366fa83f9820 |
comparison
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: |