diff options
Diffstat (limited to 'mercurial/scmutil.py')
-rw-r--r-- | mercurial/scmutil.py | 318 |
1 files changed, 54 insertions, 264 deletions
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py index 96acfff..fcb89e6 100644 --- a/mercurial/scmutil.py +++ b/mercurial/scmutil.py @@ -6,27 +6,10 @@ # GNU General Public License version 2 or any later version. from i18n import _ -import util, error, osutil, revset, similar, encoding, phases +import util, error, osutil, revset, similar, encoding import match as matchmod import os, errno, re, stat, sys, glob -def nochangesfound(ui, repo, excluded=None): - '''Report no changes for push/pull, excluded is None or a list of - nodes excluded from the push/pull. - ''' - secretlist = [] - if excluded: - for n in excluded: - ctx = repo[n] - if ctx.phase() >= phases.secret and not ctx.extinct(): - secretlist.append(n) - - if secretlist: - ui.status(_("no changes found (ignored %d secret changesets)\n") - % len(secretlist)) - else: - ui.status(_("no changes found\n")) - def checkfilename(f): '''Check that the filename f is an acceptable filename for a tracked file''' if '\r' in f or '\n' in f: @@ -58,27 +41,22 @@ def checkportabilityalert(ui): return abort, warn class casecollisionauditor(object): - def __init__(self, ui, abort, dirstate): + def __init__(self, ui, abort, existingiter): self._ui = ui self._abort = abort - allfiles = '\0'.join(dirstate._map) - self._loweredfiles = set(encoding.lower(allfiles).split('\0')) - self._dirstate = dirstate - # The purpose of _newfiles is so that we don't complain about - # case collisions if someone were to call this object with the - # same filename twice. - self._newfiles = set() + self._map = {} + for f in existingiter: + self._map[encoding.lower(f)] = f def __call__(self, f): fl = encoding.lower(f) - if (fl in self._loweredfiles and f not in self._dirstate and - f not in self._newfiles): + map = self._map + if fl in map and map[fl] != f: msg = _('possible case-folding collision for %s') % f if self._abort: raise util.Abort(msg) self._ui.warn(_("warning: %s\n") % msg) - self._loweredfiles.add(fl) - self._newfiles.add(f) + map[fl] = f class pathauditor(object): '''ensure that a filesystem path contains no banned components. @@ -98,23 +76,18 @@ class pathauditor(object): self.auditeddir = set() self.root = root self.callback = callback - if os.path.lexists(root) and not util.checkcase(root): - self.normcase = util.normcase - else: - self.normcase = lambda x: x def __call__(self, path): '''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: + if path in self.audited: return # AIX ignores "/" at end of path, others raise EISDIR. if util.endswithsep(path): raise util.Abort(_("path ends in directory separator: %s") % path) - parts = util.splitpath(path) + normpath = os.path.normcase(path) + parts = util.splitpath(normpath) if (os.path.splitdrive(path)[0] or parts[0].lower() in ('.hg', '.hg.', '') or os.pardir in parts): @@ -125,19 +98,14 @@ class pathauditor(object): if p in lparts[1:]: pos = lparts.index(p) base = os.path.join(*parts[:pos]) - raise util.Abort(_("path '%s' is inside nested repo %r") + raise util.Abort(_('path %r is inside nested repo %r') % (path, base)) - normparts = util.splitpath(normpath) - assert len(parts) == len(normparts) - parts.pop() - normparts.pop() prefixes = [] while parts: prefix = os.sep.join(parts) - normprefix = os.sep.join(normparts) - if normprefix in self.auditeddir: + if prefix in self.auditeddir: break curpath = os.path.join(self.root, prefix) try: @@ -155,14 +123,12 @@ class pathauditor(object): 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): - raise util.Abort(_("path '%s' is inside nested " - "repo %r") - % (path, prefix)) - prefixes.append(normprefix) + raise util.Abort(_('path %r is inside nested repo %r') % + (path, prefix)) + prefixes.append(prefix) parts.pop() - normparts.pop() - self.audited.add(normpath) + self.audited.add(path) # 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) @@ -174,15 +140,6 @@ class abstractopener(object): '''Prevent instantiation; don't call this from subclasses.''' raise NotImplementedError('attempted instantiating ' + str(type(self))) - def tryread(self, path): - '''gracefully return an empty string for missing files''' - try: - return self.read(path) - except IOError, inst: - if inst.errno != errno.ENOENT: - raise - return "" - def read(self, path): fp = self(path, 'rb') try: @@ -204,30 +161,13 @@ class abstractopener(object): finally: fp.close() - def mkdir(self, path=None): - return os.mkdir(self.join(path)) - - def exists(self, path=None): - return os.path.exists(self.join(path)) - - def isdir(self, path=None): - return os.path.isdir(self.join(path)) - - def makedir(self, path=None, notindexed=True): - return util.makedir(self.join(path), notindexed) - - def makedirs(self, path=None, mode=None): - return util.makedirs(self.join(path), mode) - class opener(abstractopener): '''Open files relative to a base directory This class is used to hide the details of COW semantics and remote file access from higher level code. ''' - def __init__(self, base, audit=True, expand=False): - if expand: - base = os.path.realpath(util.expandpath(base)) + def __init__(self, base, audit=True): self.base = base self._audit = audit if audit: @@ -252,7 +192,7 @@ class opener(abstractopener): if r: raise util.Abort("%s: %r" % (r, path)) self.auditor(path) - f = self.join(path) + f = os.path.join(self.base, path) if not text and "b" not in mode: mode += "b" # for that other OS @@ -296,7 +236,7 @@ class opener(abstractopener): def symlink(self, src, dst): self.auditor(dst) - linkname = self.join(dst) + linkname = os.path.join(self.base, dst) try: os.unlink(linkname) except OSError: @@ -321,12 +261,6 @@ class opener(abstractopener): def audit(self, path): self.auditor(path) - def join(self, path): - if path: - return os.path.join(self.base, path) - else: - return self.base - class filteropener(abstractopener): '''Wrapper opener for filtering filenames with a function.''' @@ -358,16 +292,18 @@ def canonpath(root, cwd, myname, auditor=None): 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. + # check name == '/', because that doesn't work on windows). For each + # `name', compare dev/inode numbers. If they match, the list `rel' + # holds the reversed list of components making up the relative file + # name we want. + root_st = os.stat(root) rel = [] while True: try: - s = util.samefile(name, root) + name_st = os.stat(name) except OSError: - s = False - if s: + break + if util.samestat(name_st, root_st): if not rel: # name was actually the same as root (maybe a symlink) return '' @@ -384,15 +320,14 @@ def canonpath(root, cwd, myname, auditor=None): raise util.Abort('%s not under root' % myname) def walkrepos(path, followsym=False, seen_dirs=None, recurse=False): - '''yield every hg repository under path, always recursively. - The recurse flag will only control recursion into repo working dirs''' + '''yield every hg repository under path, recursively.''' def errhandler(err): if err.filename == path: raise err - samestat = getattr(os.path, 'samestat', None) - if followsym and samestat is not None: + if followsym and hasattr(os.path, 'samestat'): def adddir(dirlst, dirname): match = False + samestat = os.path.samestat dirstat = os.stat(dirname) for lstdirstat in dirlst: if samestat(dirstat, lstdirstat): @@ -479,26 +414,19 @@ if os.name != 'nt': def systemrcpath(): path = [] - if sys.platform == 'plan9': - root = 'lib/mercurial' - else: - root = 'etc/mercurial' # old mod_python does not set sys.argv if len(getattr(sys, 'argv', [])) > 0: p = os.path.dirname(os.path.dirname(sys.argv[0])) - path.extend(rcfiles(os.path.join(p, root))) - path.extend(rcfiles('/' + root)) + path.extend(rcfiles(os.path.join(p, 'etc/mercurial'))) + path.extend(rcfiles('/etc/mercurial')) return path def userrcpath(): - if sys.platform == 'plan9': - return [os.environ['home'] + '/lib/hgrc'] - else: - return [os.path.expanduser('~/.hgrc')] + return [os.path.expanduser('~/.hgrc')] else: - import _winreg + _HKEY_LOCAL_MACHINE = 0x80000002L def systemrcpath(): '''return default os-specific hgrc search path''' @@ -518,10 +446,10 @@ else: return rcpath # else look for a system rcpath in the registry value = util.lookupreg('SOFTWARE\\Mercurial', None, - _winreg.HKEY_LOCAL_MACHINE) + _HKEY_LOCAL_MACHINE) if not isinstance(value, str) or not value: return rcpath - value = util.localpath(value) + value = value.replace('/', os.sep) for p in value.split(os.pathsep): if p.lower().endswith('mercurial.ini'): rcpath.append(p) @@ -558,11 +486,9 @@ def revpair(repo, revs): l = revrange(repo, revs) if len(l) == 0: - if revs: - raise util.Abort(_('empty revision range')) return repo.dirstate.p1(), None - if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]: + if len(l) == 1: return repo.lookup(l[0]), None return repo.lookup(l[0]), repo.lookup(l[-1]) @@ -575,12 +501,10 @@ def revrange(repo, revs): def revfix(repo, val, defval): if not val and val != 0 and defval is not None: return defval - return repo[val].rev() + return repo.changelog.rev(repo.lookup(val)) seen, l = set(), [] for spec in revs: - if l and not seen: - seen = set(l) # attempt to parse old-style ranges first to deal with # things like old-tag which contain query metacharacters try: @@ -594,18 +518,11 @@ def revrange(repo, revs): start = revfix(repo, start, 0) end = revfix(repo, end, len(repo) - 1) step = start > end and -1 or 1 - if not seen and not l: - # by far the most common case: revs = ["-1:0"] - l = range(start, end + step, step) - # defer syncing seen until next iteration - continue - newrevs = set(xrange(start, end + step, step)) - if seen: - newrevs.difference_update(seen) - seen.update(newrevs) - else: - seen = newrevs - l.extend(sorted(newrevs, reverse=start > end)) + for rev in xrange(start, end + step, step): + if rev in seen: + continue + seen.add(rev) + l.append(rev) continue elif spec and spec in repo: # single unquoted rev rev = revfix(repo, spec, None) @@ -619,9 +536,10 @@ def revrange(repo, revs): # fall through to new-style queries if old-style fails m = revset.match(repo.ui, spec) - dl = [r for r in m(repo, xrange(len(repo))) if r not in seen] - l.extend(dl) - seen.update(dl) + for r in m(repo, range(len(repo))): + if r not in seen: + l.append(r) + seen.update(l) return l @@ -642,7 +560,7 @@ def expandpats(pats): ret.append(p) return ret -def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'): +def match(ctx, pats=[], opts={}, globbed=False, default='relpath'): if pats == ("",): pats = [] if not globbed and default == 'relpath': @@ -653,10 +571,7 @@ def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'): def badfn(f, msg): ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg)) m.bad = badfn - return m, pats - -def match(ctx, pats=[], opts={}, globbed=False, default='relpath'): - return matchandpats(ctx, pats, opts, globbed, default)[0] + return m def matchall(repo): return matchmod.always(repo.root, repo.getcwd()) @@ -673,9 +588,6 @@ def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None): added, unknown, deleted, removed = [], [], [], [] audit_path = pathauditor(repo.root) m = match(repo[None], pats, opts) - rejected = [] - m.bad = lambda x, y: rejected.append(x) - for abs in repo.walk(m): target = repo.wjoin(abs) good = True @@ -689,9 +601,8 @@ def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None): unknown.append(abs) if repo.ui.verbose or not exact: repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) - elif (repo.dirstate[abs] != 'r' and - (not good or not os.path.lexists(target) or - (os.path.isdir(target) and not os.path.islink(target)))): + elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target) + or (os.path.isdir(target) and not os.path.islink(target))): deleted.append(abs) if repo.ui.verbose or not exact: repo.ui.status(_('removing %s\n') % ((pats and rel) or abs)) @@ -721,11 +632,6 @@ def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None): finally: wlock.release() - for f in rejected: - if f in m.files(): - return 1 - return 0 - def updatedir(ui, repo, patches, similarity=0): '''Update dirstate after patch application according to metadata''' if not patches: @@ -800,122 +706,6 @@ def readrequires(opener, supported): missings.append(r) missings.sort() if missings: - raise error.RequirementError( - _("unknown repository format: requires features '%s' (upgrade " - "Mercurial)") % "', '".join(missings)) + raise error.RequirementError(_("unknown repository format: " + "requires features '%s' (upgrade Mercurial)") % "', '".join(missings)) return requirements - -class filecacheentry(object): - def __init__(self, path): - self.path = path - self.cachestat = filecacheentry.stat(self.path) - - if self.cachestat: - self._cacheable = self.cachestat.cacheable() - else: - # None means we don't know yet - self._cacheable = None - - def refresh(self): - if self.cacheable(): - self.cachestat = filecacheentry.stat(self.path) - - def cacheable(self): - if self._cacheable is not None: - return self._cacheable - - # we don't know yet, assume it is for now - return True - - def changed(self): - # no point in going further if we can't cache it - if not self.cacheable(): - return True - - newstat = filecacheentry.stat(self.path) - - # we may not know if it's cacheable yet, check again now - if newstat and self._cacheable is None: - self._cacheable = newstat.cacheable() - - # check again - if not self._cacheable: - return True - - if self.cachestat != newstat: - self.cachestat = newstat - return True - else: - return False - - @staticmethod - def stat(path): - try: - return util.cachestat(path) - except OSError, e: - if e.errno != errno.ENOENT: - raise - -class filecache(object): - '''A property like decorator that tracks a file under .hg/ for updates. - - Records stat info when called in _filecache. - - On subsequent calls, compares old stat info with new info, and recreates - the object when needed, updating the new stat info in _filecache. - - Mercurial either atomic renames or appends for files under .hg, - so to ensure the cache is reliable we need the filesystem to be able - to tell us if a file has been replaced. If it can't, we fallback to - recreating the object on every call (essentially the same behaviour as - propertycache).''' - def __init__(self, path): - self.path = path - - def join(self, obj, fname): - """Used to compute the runtime path of the cached file. - - Users should subclass filecache and provide their own version of this - function to call the appropriate join function on 'obj' (an instance - of the class that its member function was decorated). - """ - return obj.join(fname) - - def __call__(self, func): - self.func = func - self.name = func.__name__ - return self - - def __get__(self, obj, type=None): - # do we need to check if the file changed? - if self.name in obj.__dict__: - return obj.__dict__[self.name] - - entry = obj._filecache.get(self.name) - - if entry: - if entry.changed(): - entry.obj = self.func(obj) - else: - path = self.join(obj, self.path) - - # We stat -before- creating the object so our cache doesn't lie if - # a writer modified between the time we read and stat - entry = filecacheentry(path) - entry.obj = self.func(obj) - - obj._filecache[self.name] = entry - - obj.__dict__[self.name] = entry.obj - return entry.obj - - def __set__(self, obj, value): - if self.name in obj._filecache: - obj._filecache[self.name].obj = value # update cached copy - obj.__dict__[self.name] = value # update copy returned by obj.x - - def __delete__(self, obj): - try: - del obj.__dict__[self.name] - except KeyError: - raise AttributeError, self.name |