changeset 3135:abd9a05fca0b

Merge with crew
author Matt Mackall <mpm@selenic.com>
date Tue, 19 Sep 2006 15:28:13 -0500
parents 4bf2e895cf86 (diff) 15fde1970003 (current diff)
children b1db258e875c
files mercurial/context.py
diffstat 2 files changed, 140 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -5,6 +5,10 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
+from node import *
+from demandload import demandload
+demandload(globals(), "heapq")
+
 class changectx(object):
     """A changecontext object makes access to data related to a particular
     changeset convenient."""
@@ -42,7 +46,7 @@ class changectx(object):
     def node(self): return self._node
     def user(self): return self.changeset()[1]
     def date(self): return self.changeset()[2]
-    def changedfiles(self): return self.changeset()[3]
+    def files(self): return self.changeset()[3]
     def description(self): return self.changeset()[4]
 
     def parents(self):
@@ -74,10 +78,17 @@ class changectx(object):
         for f in m:
             yield self.filectx(f, fileid=mf[f])
 
+    def ancestor(self, c2):
+        """
+        return the ancestor context of self and c2
+        """
+        n = self._repo.changelog.ancestor(self._node, c2._node)
+        return changectx(self._repo, n)
+
 class filectx(object):
     """A filecontext object makes access to data related to a particular
        filerevision convenient."""
-    def __init__(self, repo, path, changeid=None, fileid=None):
+    def __init__(self, repo, path, changeid=None, fileid=None, filelog=None):
         """changeid can be a changeset revision, node, or tag.
            fileid can be a file revision or node."""
         self._repo = repo
@@ -85,15 +96,18 @@ class filectx(object):
 
         assert changeid or fileid
 
+        if filelog:
+            self._filelog = filelog
+        else:
+            self._filelog = self._repo.file(self._path)
+
         if not fileid:
             # if given a changeset id, go ahead and look up the file
             self._changeid = changeid
             self._changectx = self.changectx()
-            self._filelog = self._repo.file(self._path)
             self._filenode = self._changectx.filenode(self._path)
         else:
-            # else be lazy
-            self._filelog = self._repo.file(self._path)
+            # else delay changectx creation
             self._filenode = self._filelog.lookup(fileid)
             self._changeid = self._filelog.linkrev(self._filenode)
         self._filerev = self._filelog.rev(self._filenode)
@@ -118,18 +132,132 @@ class filectx(object):
     def manifest(self): return self.changectx().manifest()
 
     def data(self): return self._filelog.read(self._filenode)
-    def metadata(self): return self._filelog.readmeta(self._filenode)
     def renamed(self): return self._filelog.renamed(self._filenode)
+    def path(self): return self._path
 
     def parents(self):
-        # need to fix for renames
-        p = self._filelog.parents(self._filenode)
-        return [ filectx(self._repo, self._path, fileid=x) for x in p ]
+        p = self._path
+        fl = self._filelog
+        pl = [ (p, n, fl) for n in self._filelog.parents(self._filenode) ]
+
+        r = self.renamed()
+        if r:
+            pl[0] = (r[0], r[1], None)
+
+        return [ filectx(self._repo, p, fileid=n, filelog=l)
+                 for p,n,l in pl if n != nullid ]
 
     def children(self):
         # hard for renames
         c = self._filelog.children(self._filenode)
-        return [ filectx(self._repo, self._path, fileid=x) for x in c ]
+        return [ filectx(self._repo, self._path, fileid=x,
+                         filelog=self._filelog) for x in c ]
 
     def annotate(self):
         return self._filelog.annotate(self._filenode)
+
+    def ancestor(self, fc2):
+        """
+        find the common ancestor file context, if any, of self, and fc2
+        """
+
+        a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
+
+        if a == b:
+            return self
+
+        if a[0] == b[0]:
+            n = self._filelog.ancestor(a[1], b[1])
+            if n != nullid:
+                return filectx(self._repo, self._path,
+                               fileid=n, filelog=self._filelog)
+
+        # build a graph of all ancestors, crossing renames
+        ag = {}
+        fv = [a, b]
+        flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
+
+        while fv:
+            f,n = fv.pop()
+            try:
+                fl = flcache[f]
+            except KeyError:
+                flcache[f] = self._repo.file(f)
+                fl = flcache[f]
+            v = [n]
+            while v:
+                n = v.pop()
+                c = (f, n)
+                if c in ag:
+                    continue
+
+                pl = [ n for n in fl.parents(n) if n != nullid ]
+                v += pl
+                pl = [ (f, n) for n in pl ]
+                re = fl.renamed(n)
+                if re:
+                    pl.append(re)
+                    if re not in ag:
+                        fv.append(re)
+                ag[c] = pl
+
+        dist = {}
+        def depth(node):
+            try:
+                return dist[node]
+            except KeyError:
+                pl = ag[node]
+                if not pl:
+                    dist[node] = 0
+                else:
+                    dist[node] = max([depth(p) for p in pl]) + 1
+                return dist[node]
+
+        # traverse ancestors in order of decreasing distance from root
+        def ancestors(vertex):
+            h = [(-depth(vertex), vertex)]
+            seen = {}
+            while h:
+                d, v = heapq.heappop(h)
+                if v not in seen:
+                    seen[v] = 1
+                    yield (-d, v)
+                    for p in ag[v]:
+                        heapq.heappush(h, (-depth(p), p))
+
+        def generations(vertex):
+            sg, s = None, {}
+            for g,v in ancestors(vertex):
+                if g != sg:
+                    if sg:
+                        yield sg, s
+                    sg, s = g, {v:1}
+                else:
+                    s[v] = 1
+            yield sg, s
+
+        x = generations(a)
+        y = generations(b)
+        gx = x.next()
+        gy = y.next()
+
+        # increment each ancestor list until it is closer to root than
+        # the other, or they match
+        try:
+            while 1:
+                if gx[0] == gy[0]:
+                    # find the intersection
+                    i = [ n for n in gx[1] if n in gy[1] ]
+                    if i:
+                        fp,fn = i[0]
+                        fl = flcache[fp]
+                        return filectx(self._repo, fp, fileid=fn, filelog=fl)
+                    else:
+                        gy = y.next()
+                        gx = x.next()
+                elif gx[0] < gy[0]:
+                    gy = y.next()
+                else:
+                    gx = x.next()
+        except StopIteration:
+            return None
--- a/mercurial/filelog.py
+++ b/mercurial/filelog.py
@@ -37,7 +37,7 @@ class filelog(revlog):
         s = t.index('\1\n', 2)
         return t[s+2:]
 
-    def readmeta(self, node):
+    def _readmeta(self, node):
         t = self.revision(node)
         if not t.startswith('\1\n'):
             return {}
@@ -60,7 +60,7 @@ class filelog(revlog):
     def renamed(self, node):
         if self.parents(node)[0] != nullid:
             return False
-        m = self.readmeta(node)
+        m = self._readmeta(node)
         if m and m.has_key("copy"):
             return (m["copy"], bin(m["copyrev"]))
         return False