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.''' |
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) |