summaryrefslogtreecommitdiff
path: root/mercurial/context.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/context.py')
-rw-r--r--mercurial/context.py330
1 files changed, 66 insertions, 264 deletions
diff --git a/mercurial/context.py b/mercurial/context.py
index 88ea3e4..d1c195b 100644
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -5,10 +5,9 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-from node import nullid, nullrev, short, hex, bin
+from node import nullid, nullrev, short, hex
from i18n import _
-import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
-import copies
+import ancestor, bdiff, error, util, scmutil, subrepo, patch, encoding
import match as matchmod
import os, errno, stat
@@ -22,88 +21,12 @@ class changectx(object):
if changeid == '':
changeid = '.'
self._repo = repo
-
- if isinstance(changeid, int):
+ if isinstance(changeid, (long, int)):
self._rev = changeid
- self._node = repo.changelog.node(changeid)
- return
- if isinstance(changeid, long):
- changeid = str(changeid)
- if changeid == '.':
- self._node = repo.dirstate.p1()
- self._rev = repo.changelog.rev(self._node)
- return
- if changeid == 'null':
- self._node = nullid
- self._rev = nullrev
- return
- if changeid == 'tip':
- self._rev = len(repo.changelog) - 1
- self._node = repo.changelog.node(self._rev)
- return
- if len(changeid) == 20:
- try:
- self._node = changeid
- self._rev = repo.changelog.rev(changeid)
- return
- except LookupError:
- pass
-
- try:
- r = int(changeid)
- if str(r) != changeid:
- raise ValueError
- l = len(repo.changelog)
- if r < 0:
- r += l
- if r < 0 or r >= l:
- raise ValueError
- self._rev = r
- self._node = repo.changelog.node(r)
- return
- except (ValueError, OverflowError):
- pass
-
- if len(changeid) == 40:
- try:
- self._node = bin(changeid)
- self._rev = repo.changelog.rev(self._node)
- return
- except (TypeError, LookupError):
- pass
-
- if changeid in repo._bookmarks:
- self._node = repo._bookmarks[changeid]
- self._rev = repo.changelog.rev(self._node)
- return
- if changeid in repo._tagscache.tags:
- self._node = repo._tagscache.tags[changeid]
- self._rev = repo.changelog.rev(self._node)
- return
- try:
- self._node = repo.branchtip(changeid)
- self._rev = repo.changelog.rev(self._node)
- return
- except error.RepoLookupError:
- pass
-
- self._node = repo.changelog._partialmatch(changeid)
- if self._node is not None:
- self._rev = repo.changelog.rev(self._node)
- return
-
- # lookup failed
- # check if it might have come from damaged dirstate
- if changeid in repo.dirstate.parents():
- raise error.Abort(_("working directory has unknown parent '%s'!")
- % short(changeid))
- try:
- if len(changeid) == 20:
- changeid = hex(changeid)
- except TypeError:
- pass
- raise error.RepoLookupError(
- _("unknown revision '%s'") % changeid)
+ self._node = self._repo.changelog.node(changeid)
+ else:
+ self._node = self._repo.lookup(changeid)
+ self._rev = self._repo.changelog.rev(self._node)
def __str__(self):
return short(self.node())
@@ -134,7 +57,7 @@ class changectx(object):
@propertycache
def _changeset(self):
- return self._repo.changelog.read(self.rev())
+ return self._repo.changelog.read(self.node())
@propertycache
def _manifest(self):
@@ -188,22 +111,14 @@ class changectx(object):
return self._changeset[4]
def branch(self):
return encoding.tolocal(self._changeset[5].get("branch"))
- def closesbranch(self):
- return 'close' in self._changeset[5]
def extra(self):
return self._changeset[5]
def tags(self):
return self._repo.nodetags(self._node)
def bookmarks(self):
return self._repo.nodebookmarks(self._node)
- def phase(self):
- return self._repo._phasecache.phase(self._repo, self._rev)
- def phasestr(self):
- return phases.phasenames[self.phase()]
- def mutable(self):
- return self.phase() > phases.public
def hidden(self):
- return self._rev in self._repo.hiddenrevs
+ return self._rev in self._repo.changelog.hiddenrevs
def parents(self):
"""return contexts for each parent changeset"""
@@ -223,48 +138,13 @@ class changectx(object):
return [changectx(self._repo, x) for x in c]
def ancestors(self):
- for a in self._repo.changelog.ancestors([self._rev]):
+ for a in self._repo.changelog.ancestors(self._rev):
yield changectx(self._repo, a)
def descendants(self):
- for d in self._repo.changelog.descendants([self._rev]):
+ for d in self._repo.changelog.descendants(self._rev):
yield changectx(self._repo, d)
- def obsolete(self):
- """True if the changeset is obsolete"""
- return (self.node() in self._repo.obsstore.precursors
- and self.phase() > phases.public)
-
- def extinct(self):
- """True if the changeset is extinct"""
- # We should just compute a cache a check againts it.
- # see revset implementation for details
- #
- # But this naive implementation does not require cache
- if self.phase() <= phases.public:
- return False
- if not self.obsolete():
- return False
- for desc in self.descendants():
- if not desc.obsolete():
- return False
- return True
-
- def unstable(self):
- """True if the changeset is not obsolete but it's ancestor are"""
- # We should just compute /(obsolete()::) - obsolete()/
- # and keep it in a cache.
- #
- # But this naive implementation does not require cache
- if self.phase() <= phases.public:
- return False
- if self.obsolete():
- return False
- for anc in self.ancestors():
- if anc.obsolete():
- return True
- return False
-
def _fileinfo(self, path):
if '_manifest' in self.__dict__:
try:
@@ -274,8 +154,7 @@ class changectx(object):
_('not found in manifest'))
if '_manifestdelta' in self.__dict__ or path in self.files():
if path in self._manifestdelta:
- return (self._manifestdelta[path],
- self._manifestdelta.flags(path))
+ return self._manifestdelta[path], self._manifestdelta.flags(path)
node, flag = self._repo.manifest.find(self._changeset[0], path)
if not node:
raise error.LookupError(self._node, path,
@@ -316,15 +195,14 @@ class changectx(object):
# follow that here, too
fset.discard('.')
for fn in self:
- if fn in fset:
- # specified pattern is the exact name
- fset.remove(fn)
+ for ffn in fset:
+ # match if the file is the exact name or a directory
+ if ffn == fn or fn.startswith("%s/" % ffn):
+ fset.remove(ffn)
+ break
if match(fn):
yield fn
for fn in sorted(fset):
- if fn in self._dirs:
- # specified pattern is a directory
- continue
if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
yield fn
@@ -347,22 +225,6 @@ class changectx(object):
return patch.diff(self._repo, ctx2.node(), self.node(),
match=match, opts=diffopts)
- @propertycache
- def _dirs(self):
- dirs = set()
- for f in self._manifest:
- pos = f.rfind('/')
- while pos != -1:
- f = f[:pos]
- if f in dirs:
- break # dirs already contains this and above
- dirs.add(f)
- pos = f.rfind('/')
- return dirs
-
- def dirs(self):
- return self._dirs
-
class filectx(object):
"""A filecontext object makes access to data related to a particular
filerevision convenient."""
@@ -501,22 +363,12 @@ class filectx(object):
def size(self):
return self._filelog.size(self._filerev)
- def isbinary(self):
- try:
- return util.binary(self.data())
- except IOError:
- return False
-
def cmp(self, fctx):
"""compare with other file context
returns True if different than fctx.
"""
- if (fctx._filerev is None
- and (self._repo._encodefilterpats
- # if file data starts with '\1\n', empty metadata block is
- # prepended, which adds 4 bytes to filelog.size().
- or self.size() - 4 == fctx.size())
+ if (fctx._filerev is None and self._repo._encodefilterpats
or self.size() == fctx.size()):
return self._filelog.cmp(self._filenode, fctx.data())
@@ -574,7 +426,7 @@ class filectx(object):
return [filectx(self._repo, self._path, fileid=x,
filelog=self._filelog) for x in c]
- def annotate(self, follow=False, linenumber=None, diffopts=None):
+ def annotate(self, follow=False, linenumber=None):
'''returns a list of tuples of (ctx, line) for each line
in the file, where ctx is the filectx of the node where
that line was last changed.
@@ -601,13 +453,8 @@ class filectx(object):
without_linenumber)
def pair(parent, child):
- blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
- refine=True)
- for (a1, a2, b1, b2), t in blocks:
- # Changed blocks ('!') or blocks made only of blank lines ('~')
- # belong to the child.
- if t == '=':
- child[0][b1:b2] = parent[0][a1:a2]
+ for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
+ child[0][b1:b2] = parent[0][a1:a2]
return child
getlog = util.lrucachefunc(lambda x: self._repo.file(x))
@@ -672,27 +519,27 @@ class filectx(object):
return zip(hist[base][0], hist[base][1].splitlines(True))
- def ancestor(self, fc2, actx):
+ def ancestor(self, fc2, actx=None):
"""
find the common ancestor file context, if any, of self, and fc2
- actx must be the changectx of the common ancestor
+ If actx is given, it must be the changectx of the common ancestor
of self's and fc2's respective changesets.
"""
+ if actx is None:
+ actx = self.changectx().ancestor(fc2.changectx())
+
+ # the trivial case: changesets are unrelated, files must be too
+ if not actx:
+ return None
+
# the easy case: no (relevant) renames
if fc2.path() == self.path() and self.path() in actx:
return actx[self.path()]
-
- # the next easiest cases: unambiguous predecessor (name trumps
- # history)
- if self.path() in actx and fc2.path() not in actx:
- return actx[self.path()]
- if fc2.path() in actx and self.path() not in actx:
- return actx[fc2.path()]
+ acache = {}
# prime the ancestor cache for the working directory
- acache = {}
for c in (self, fc2):
if c._filerev is None:
pl = [(n.path(), n.filenode()) for n in c.parents()]
@@ -721,26 +568,17 @@ class filectx(object):
return None
- def ancestors(self, followfirst=False):
+ def ancestors(self):
visit = {}
c = self
- cut = followfirst and 1 or None
while True:
- for parent in c.parents()[:cut]:
+ for parent in c.parents():
visit[(parent.rev(), parent.node())] = parent
if not visit:
break
c = visit.pop(max(visit))
yield c
- def copies(self, c2):
- if not util.safehasattr(self, "_copycache"):
- self._copycache = {}
- sc2 = str(c2)
- if sc2 not in self._copycache:
- self._copycache[sc2] = copies.pathcopies(c2)
- return self._copycache[sc2]
-
class workingctx(changectx):
"""A workingctx object makes access to data related to
the current working directory convenient.
@@ -794,47 +632,15 @@ class workingctx(changectx):
def __contains__(self, key):
return self._repo.dirstate[key] not in "?r"
- def _buildflagfunc(self):
- # Create a fallback function for getting file flags when the
- # filesystem doesn't support them
-
- copiesget = self._repo.dirstate.copies().get
-
- if len(self._parents) < 2:
- # when we have one parent, it's easy: copy from parent
- man = self._parents[0].manifest()
- def func(f):
- f = copiesget(f, f)
- return man.flags(f)
- else:
- # merges are tricky: we try to reconstruct the unstored
- # result from the merge (issue1802)
- p1, p2 = self._parents
- pa = p1.ancestor(p2)
- m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
-
- def func(f):
- f = copiesget(f, f) # may be wrong for merges with copies
- fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
- if fl1 == fl2:
- return fl1
- if fl1 == fla:
- return fl2
- if fl2 == fla:
- return fl1
- return '' # punt for conflicts
-
- return func
-
- @propertycache
- def _flagfunc(self):
- return self._repo.dirstate.flagfunc(self._buildflagfunc)
-
@propertycache
def _manifest(self):
"""generate a manifest corresponding to the working directory"""
+ if self._unknown is None:
+ self.status(unknown=True)
+
man = self._parents[0].manifest().copy()
+ copied = self._repo.dirstate.copies()
if len(self._parents) > 1:
man2 = self.p2().manifest()
def getman(f):
@@ -843,11 +649,13 @@ class workingctx(changectx):
return man2
else:
getman = lambda f: man
-
- copied = self._repo.dirstate.copies()
- ff = self._flagfunc
+ def cf(f):
+ f = copied.get(f, f)
+ return getman(f).flags(f)
+ ff = self._repo.dirstate.flagfunc(cf)
modified, added, removed, deleted = self._status
- for i, l in (("a", added), ("m", modified)):
+ unknown = self._unknown
+ for i, l in (("a", added), ("m", modified), ("u", unknown)):
for f in l:
orig = copied.get(f, f)
man[f] = getman(orig).get(orig, nullid) + i
@@ -934,8 +742,6 @@ class workingctx(changectx):
return self._clean
def branch(self):
return encoding.tolocal(self._extra['branch'])
- def closesbranch(self):
- return 'close' in self._extra
def extra(self):
return self._extra
@@ -951,15 +757,6 @@ class workingctx(changectx):
b.extend(p.bookmarks())
return b
- def phase(self):
- phase = phases.draft # default phase to draft
- for p in self.parents():
- phase = max(phase, p.phase())
- return phase
-
- def hidden(self):
- return False
-
def children(self):
return []
@@ -970,10 +767,23 @@ class workingctx(changectx):
except KeyError:
return ''
- try:
- return self._flagfunc(path)
- except OSError:
+ orig = self._repo.dirstate.copies().get(path, path)
+
+ def findflag(ctx):
+ mnode = ctx.changeset()[0]
+ node, flag = self._repo.manifest.find(mnode, orig)
+ ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
+ try:
+ return ff(path)
+ except OSError:
+ pass
+
+ flag = findflag(self._parents[0])
+ if flag is None and len(self.parents()) > 1:
+ flag = findflag(self._parents[1])
+ if flag is None or self._repo.dirstate[path] == 'r':
return ''
+ return flag
def filectx(self, path, filelog=None):
"""get a file context from the working directory"""
@@ -988,15 +798,14 @@ class workingctx(changectx):
return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
True, False))
- def dirty(self, missing=False, merge=True, branch=True):
+ def dirty(self, missing=False):
"check whether a working directory is modified"
# check subrepos first
for s in self.substate:
if self.sub(s).dirty():
return True
# check current working dir
- return ((merge and self.p2()) or
- (branch and self.branch() != self.p1().branch()) or
+ return (self.p2() or self.branch() != self.p1().branch() or
self.modified() or self.added() or self.removed() or
(missing and self.deleted()))
@@ -1035,26 +844,22 @@ class workingctx(changectx):
finally:
wlock.release()
- def forget(self, files, prefix=""):
- join = lambda f: os.path.join(prefix, f)
+ def forget(self, files):
wlock = self._repo.wlock()
try:
- rejected = []
for f in files:
- if f not in self._repo.dirstate:
- self._repo.ui.warn(_("%s not tracked!\n") % join(f))
- rejected.append(f)
- elif self._repo.dirstate[f] != 'a':
+ if self._repo.dirstate[f] != 'a':
self._repo.dirstate.remove(f)
+ elif f not in self._repo.dirstate:
+ self._repo.ui.warn(_("%s not tracked!\n") % f)
else:
self._repo.dirstate.drop(f)
- return rejected
finally:
wlock.release()
def ancestors(self):
for a in self._repo.changelog.ancestors(
- [p.rev() for p in self._parents]):
+ *[p.rev() for p in self._parents]):
yield changectx(self._repo, a)
def undelete(self, list):
@@ -1088,9 +893,6 @@ class workingctx(changectx):
finally:
wlock.release()
- def dirs(self):
- return set(self._repo.dirstate.dirs())
-
class workingfilectx(filectx):
"""A workingfilectx object makes access to data related to a particular
file in the working directory convenient."""