filectx: add rename-aware ancestor algorithm
authorMatt Mackall <mpm@selenic.com>
Tue, 19 Sep 2006 14:58:54 -0500
changeset 3126 4bf2e895cf86
parent 3125 02b22fefc01f
child 3134 abd9a05fca0b
filectx: add rename-aware ancestor algorithm This code works but may trigger recursion depth issues
mercurial/context.py
--- a/mercurial/context.py	Sun Sep 17 22:59:33 2006 -0500
+++ b/mercurial/context.py	Tue Sep 19 14:58:54 2006 -0500
@@ -6,6 +6,8 @@
 # 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
@@ -145,3 +147,108 @@
     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