summaryrefslogtreecommitdiff
path: root/mercurial/scmutil.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/scmutil.py')
-rw-r--r--mercurial/scmutil.py318
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