diff mercurial/context.py @ 6876:077f1e637cd8

Merge with stable Simplify the copy search algorithm
author Matt Mackall <mpm@selenic.com>
date Sun, 10 Aug 2008 18:38:43 -0500
parents 54b7c79575fa
children 8fee8ff13d37
line wrap: on
line diff
--- a/mercurial/context.py	Sun Aug 10 18:01:03 2008 -0500
+++ b/mercurial/context.py	Sun Aug 10 18:38:43 2008 -0500
@@ -5,35 +5,36 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from node import nullid, nullrev, short
+from node import nullid, nullrev, short, hex
 from i18n import _
 import ancestor, bdiff, revlog, util, os, errno
 
 class changectx(object):
     """A changecontext object makes access to data related to a particular
     changeset convenient."""
-    def __init__(self, repo, changeid=None):
+    def __init__(self, repo, changeid=''):
         """changeid is a revision number, node, or tag"""
+        if changeid == '':
+            changeid = '.'
         self._repo = repo
-
-        if not changeid and changeid != 0:
-            p1, p2 = self._repo.dirstate.parents()
-            self._rev = self._repo.changelog.rev(p1)
-            if self._rev == -1:
-                changeid = 'tip'
-            else:
-                self._node = p1
-                return
-
         self._node = self._repo.lookup(changeid)
         self._rev = self._repo.changelog.rev(self._node)
 
     def __str__(self):
         return short(self.node())
 
+    def __int__(self):
+        return self.rev()
+
     def __repr__(self):
         return "<changectx %s>" % str(self)
 
+    def __hash__(self):
+        try:
+            return hash(self._rev)
+        except AttributeError:
+            return id(self)
+
     def __eq__(self, other):
         try:
             return self._rev == other._rev
@@ -57,6 +58,12 @@
             md = self._repo.manifest.readdelta(self._changeset[0])
             self._manifestdelta = md
             return self._manifestdelta
+        elif name == '_parents':
+            p = self._repo.changelog.parents(self._node)
+            if p[1] == nullid:
+                p = p[:-1]
+            self._parents = [changectx(self._repo, x) for x in p]
+            return self._parents
         else:
             raise AttributeError, name
 
@@ -67,9 +74,7 @@
         return self.filectx(key)
 
     def __iter__(self):
-        a = self._manifest.keys()
-        a.sort()
-        for f in a:
+        for f in util.sort(self._manifest):
             yield f
 
     def changeset(self): return self._changeset
@@ -77,6 +82,7 @@
 
     def rev(self): return self._rev
     def node(self): return self._node
+    def hex(self): return hex(self._node)
     def user(self): return self._changeset[1]
     def date(self): return self._changeset[2]
     def files(self): return self._changeset[3]
@@ -87,14 +93,21 @@
 
     def parents(self):
         """return contexts for each parent changeset"""
-        p = self._repo.changelog.parents(self._node)
-        return [changectx(self._repo, x) for x in p]
+        return self._parents
 
     def children(self):
         """return contexts for each child changeset"""
         c = self._repo.changelog.children(self._node)
         return [changectx(self._repo, x) for x in c]
 
+    def ancestors(self):
+        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):
+            yield changectx(self._repo, d)
+
     def _fileinfo(self, path):
         if '_manifest' in self.__dict__:
             try:
@@ -115,7 +128,7 @@
     def filenode(self, path):
         return self._fileinfo(path)[0]
 
-    def fileflags(self, path):
+    def flags(self, path):
         try:
             return self._fileinfo(path)[1]
         except revlog.LookupError:
@@ -128,15 +141,6 @@
         return filectx(self._repo, path, fileid=fileid,
                        changectx=self, filelog=filelog)
 
-    def filectxs(self):
-        """generate a file context for each file in this changeset's
-           manifest"""
-        mf = self.manifest()
-        m = mf.keys()
-        m.sort()
-        for f in m:
-            yield self.filectx(f, fileid=mf[f])
-
     def ancestor(self, c2):
         """
         return the ancestor context of self and c2
@@ -144,6 +148,23 @@
         n = self._repo.changelog.ancestor(self._node, c2._node)
         return changectx(self._repo, n)
 
+    def walk(self, match):
+        fdict = dict.fromkeys(match.files())
+        # for dirstate.walk, files=['.'] means "walk the whole tree".
+        # follow that here, too
+        fdict.pop('.', None)
+        for fn in self:
+            for ffn in fdict:
+                # match if the file is the exact name or a directory
+                if ffn == fn or fn.startswith("%s/" % ffn):
+                    del fdict[ffn]
+                    break
+            if match(fn):
+                yield fn
+        for fn in util.sort(fdict):
+            if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
+                yield fn
+
 class filectx(object):
     """A filecontext object makes access to data related to a particular
        filerevision convenient."""
@@ -210,6 +231,12 @@
     def __repr__(self):
         return "<filectx %s>" % str(self)
 
+    def __hash__(self):
+        try:
+            return hash((self._path, self._fileid))
+        except AttributeError:
+            return id(self)
+
     def __eq__(self, other):
         try:
             return (self._path == other._path
@@ -228,9 +255,7 @@
 
     def filerev(self): return self._filerev
     def filenode(self): return self._filenode
-    def fileflags(self): return self._changectx.fileflags(self._path)
-    def isexec(self): return 'x' in self.fileflags()
-    def islink(self): return 'l' in self.fileflags()
+    def flags(self): return self._changectx.flags(self._path)
     def filelog(self): return self._filelog
 
     def rev(self):
@@ -376,12 +401,11 @@
         # sort by revision (per file) which is a topological order
         visit = []
         for f in files:
-            fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
+            fn = [(n.rev(), n) for n in needed if n._path == f]
             visit.extend(fn)
-        visit.sort()
+
         hist = {}
-
-        for r, f in visit:
+        for r, f in util.sort(visit):
             curr = decorate(f.data(), f)
             for p in parents(f):
                 if p != nullid:
@@ -432,11 +456,41 @@
 
 class workingctx(changectx):
     """A workingctx object makes access to data related to
-    the current working directory convenient."""
-    def __init__(self, repo):
+    the current working directory convenient.
+    parents - a pair of parent nodeids, or None to use the dirstate.
+    date - any valid date string or (unixtime, offset), or None.
+    user - username string, or None.
+    extra - a dictionary of extra values, or None.
+    changes - a list of file lists as returned by localrepo.status()
+               or None to use the repository status.
+    """
+    def __init__(self, repo, parents=None, text="", user=None, date=None,
+                 extra=None, changes=None):
         self._repo = repo
         self._rev = None
         self._node = None
+        self._text = text
+        if date:
+            self._date = util.parsedate(date)
+        if user:
+            self._user = user
+        if parents:
+            self._parents = [changectx(self._repo, p) for p in parents]
+        if changes:
+            self._status = list(changes)
+
+        self._extra = {}
+        if extra:
+            self._extra = extra.copy()
+        if 'branch' not in self._extra:
+            branch = self._repo.dirstate.branch()
+            try:
+                branch = branch.decode('UTF-8').encode('UTF-8')
+            except UnicodeDecodeError:
+                raise util.Abort(_('branch name not in UTF-8!'))
+            self._extra['branch'] = branch
+        if self._extra['branch'] == '':
+            self._extra['branch'] = 'default'
 
     def __str__(self):
         return str(self._parents[0]) + "+"
@@ -444,16 +498,28 @@
     def __nonzero__(self):
         return True
 
+    def __contains__(self, key):
+        return self._dirstate[key] not in "?r"
+
     def __getattr__(self, name):
-        if name == '_parents':
-            self._parents = self._repo.parents()
-            return self._parents
         if name == '_status':
-            self._status = self._repo.status()
+            self._status = self._repo.status(unknown=True)
             return self._status
+        elif name == '_user':
+            self._user = self._repo.ui.username()
+            return self._user
+        elif name == '_date':
+            self._date = util.makedate()
+            return self._date
         if name == '_manifest':
             self._buildmanifest()
             return self._manifest
+        elif name == '_parents':
+            p = self._repo.dirstate.parents()
+            if p[1] == nullid:
+                p = p[:-1]
+            self._parents = [changectx(self._repo, x) for x in p]
+            return self._parents
         else:
             raise AttributeError, name
 
@@ -462,16 +528,14 @@
 
         man = self._parents[0].manifest().copy()
         copied = self._repo.dirstate.copies()
-        is_exec = util.execfunc(self._repo.root,
-                                lambda p: man.execf(copied.get(p,p)))
-        is_link = util.linkfunc(self._repo.root,
-                                lambda p: man.linkf(copied.get(p,p)))
+        cf = lambda x: man.flags(copied.get(x, x))
+        ff = self._repo.dirstate.flagfunc(cf)
         modified, added, removed, deleted, unknown = self._status[:5]
         for i, l in (("a", added), ("m", modified), ("u", unknown)):
             for f in l:
                 man[f] = man.get(copied.get(f, f), nullid) + i
                 try:
-                    man.set(f, is_exec(f), is_link(f))
+                    man.set(f, ff(f))
                 except OSError:
                     pass
 
@@ -483,13 +547,11 @@
 
     def manifest(self): return self._manifest
 
-    def user(self): return self._repo.ui.username()
-    def date(self): return util.makedate()
-    def description(self): return ""
+    def user(self): return self._user or self._repo.ui.username()
+    def date(self): return self._date
+    def description(self): return self._text
     def files(self):
-        f = self.modified() + self.added() + self.removed()
-        f.sort()
-        return f
+        return util.sort(self._status[0] + self._status[1] + self._status[2])
 
     def modified(self): return self._status[0]
     def added(self): return self._status[1]
@@ -497,21 +559,18 @@
     def deleted(self): return self._status[3]
     def unknown(self): return self._status[4]
     def clean(self): return self._status[5]
-    def branch(self): return self._repo.dirstate.branch()
+    def branch(self): return self._extra['branch']
+    def extra(self): return self._extra
 
     def tags(self):
         t = []
         [t.extend(p.tags()) for p in self.parents()]
         return t
 
-    def parents(self):
-        """return contexts for each parent changeset"""
-        return self._parents
-
     def children(self):
         return []
 
-    def fileflags(self, path):
+    def flags(self, path):
         if '_manifest' in self.__dict__:
             try:
                 return self._manifest.flags(path)
@@ -521,12 +580,9 @@
         pnode = self._parents[0].changeset()[0]
         orig = self._repo.dirstate.copies().get(path, path)
         node, flag = self._repo.manifest.find(pnode, orig)
-        is_link = util.linkfunc(self._repo.root,
-                                lambda p: flag and 'l' in flag)
-        is_exec = util.execfunc(self._repo.root,
-                                lambda p: flag and 'x' in flag)
         try:
-            return (is_link(path) and 'l' or '') + (is_exec(path) and 'x' or '')
+            ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
+            return ff(path)
         except OSError:
             pass
 
@@ -543,6 +599,9 @@
         """return the ancestor context of self and c2"""
         return self._parents[0].ancestor(c2) # punt on two parents for now
 
+    def walk(self, match):
+        return util.sort(self._repo.dirstate.walk(match, True, False).keys())
+
 class workingfilectx(filectx):
     """A workingfilectx object makes access to data related to a particular
        file in the working directory convenient."""
@@ -625,3 +684,92 @@
             return (t, tz)
 
     def cmp(self, text): return self._repo.wread(self._path) == text
+
+class memctx(object):
+    """A memctx is a subset of changectx supposed to be built on memory
+    and passed to commit functions.
+
+    NOTE: this interface and the related memfilectx are experimental and
+    may change without notice.
+
+    parents - a pair of parent nodeids.
+    filectxfn - a callable taking (repo, memctx, path) arguments and
+    returning a memctx object.
+    date - any valid date string or (unixtime, offset), or None.
+    user - username string, or None.
+    extra - a dictionary of extra values, or None.
+    """
+    def __init__(self, repo, parents, text, files, filectxfn, user=None,
+                 date=None, extra=None):
+        self._repo = repo
+        self._rev = None
+        self._node = None
+        self._text = text
+        self._date = date and util.parsedate(date) or util.makedate()
+        self._user = user
+        parents = [(p or nullid) for p in parents]
+        p1, p2 = parents
+        self._parents = [changectx(self._repo, p) for p in (p1, p2)]
+        files = util.sort(list(files))
+        self._status = [files, [], [], [], []]
+        self._filectxfn = filectxfn
+
+        self._extra = extra and extra.copy() or {}
+        if 'branch' not in self._extra:
+            self._extra['branch'] = 'default'
+        elif self._extra.get('branch') == '':
+            self._extra['branch'] = 'default'
+
+    def __str__(self):
+        return str(self._parents[0]) + "+"
+
+    def __int__(self):
+        return self._rev
+
+    def __nonzero__(self):
+        return True
+
+    def user(self): return self._user or self._repo.ui.username()
+    def date(self): return self._date
+    def description(self): return self._text
+    def files(self): return self.modified()
+    def modified(self): return self._status[0]
+    def added(self): return self._status[1]
+    def removed(self): return self._status[2]
+    def deleted(self): return self._status[3]
+    def unknown(self): return self._status[4]
+    def clean(self): return self._status[5]
+    def branch(self): return self._extra['branch']
+    def extra(self): return self._extra
+    def flags(self, f): return self[f].flags()
+
+    def parents(self):
+        """return contexts for each parent changeset"""
+        return self._parents
+
+    def filectx(self, path, filelog=None):
+        """get a file context from the working directory"""
+        return self._filectxfn(self._repo, self, path)
+
+class memfilectx(object):
+    """A memfilectx is a subset of filectx supposed to be built by client
+    code and passed to commit functions.
+    """
+    def __init__(self, path, data, islink, isexec, copied):
+        """copied is the source file path, or None."""
+        self._path = path
+        self._data = data
+        self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
+        self._copied = None
+        if copied:
+            self._copied = (copied, nullid)
+
+    def __nonzero__(self): return True
+    def __str__(self): return "%s@%s" % (self.path(), self._changectx)
+    def path(self): return self._path
+    def data(self): return self._data
+    def flags(self): return self._flags
+    def isexec(self): return 'x' in self._flags
+    def islink(self): return 'l' in self._flags
+    def renamed(self): return self._copied
+