bisect: propagate ancestor lists directly to children
authorMatt Mackall <mpm@selenic.com>
Mon, 31 Dec 2007 18:20:33 -0600
changeset 5767 dd5f8ed31057
parent 5766 23caedc5a28f
child 5768 78d14403bdc7
bisect: propagate ancestor lists directly to children - calculate the children of all candidates - for each candidate, combine ancestor lists - pass ancestor lists to children - store ancestor count This eliminates the O(n**2) memory usage, while maintaining about the same performance.
hgext/hbisect.py
--- a/hgext/hbisect.py	Mon Dec 31 18:20:33 2007 -0600
+++ b/hgext/hbisect.py	Mon Dec 31 18:20:33 2007 -0600
@@ -8,7 +8,7 @@
 
 from mercurial.i18n import _
 from mercurial import hg, util, cmdutil
-import os, array
+import os
 
 def _bisect(changelog, state):
     clparents = changelog.parentrevs
@@ -31,30 +31,48 @@
         raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
                          % (badrev, hg.short(bad)))
 
+    # build children array
+    children = [[]] * (badrev + 1) # an extra for [-1]
+    for rev in xrange(badrev, -1, -1):
+        if ancestors[rev] == []:
+            for prev in clparents(rev):
+                if prev == -1:
+                    continue
+                l = children[prev]
+                if l:
+                    l.append(rev)
+                else:
+                    children[prev] = [rev]
+
     # accumulate ancestor lists
     for rev in xrange(badrev + 1):
-        if ancestors[rev] == []:
-            p1, p2 = clparents(rev)
-            a1, a2 = ancestors[p1], ancestors[p2]
-            if a1:
-                if a2:
-                    # merge ancestor lists
-                    a = dict.fromkeys(a2)
-                    a.update(dict.fromkeys(a1))
-                    a[rev] = None
-                    ancestors[rev] = array.array("l", a.keys())
+        l = ancestors[rev]
+        if l != None:
+            if not l:
+                a = [rev]
+            elif len(l) == 1:
+                a = l[0] + [rev]
+            else:
+                a = {}
+                for s in l:
+                    a.update(dict.fromkeys(s))
+                a[rev] = None
+                a = a.keys()
+            for c in children[rev]:
+                if ancestors[c]:
+                    ancestors[c].append(a)
                 else:
-                    ancestors[rev] = a1 + array.array("l", [rev])
-            elif a2:
-                ancestors[rev] = a2 + array.array("l", [rev])
-            else:
-                ancestors[rev] = array.array("l", [rev])
+                    ancestors[c] = [a]
+            ancestors[rev] = len(a)
 
-    if badrev not in ancestors[badrev]:
+    candidates = a # ancestors of badrev, last rev visited
+    del children
+
+    if badrev not in candidates:
         raise util.Abort(_("Could not find the first bad revision"))
 
     # have we narrowed it down to one entry?
-    tot = len(ancestors[badrev])
+    tot = len(candidates)
     if tot == 1:
         return (bad, 0)
 
@@ -62,10 +80,10 @@
     best_rev = None
     best_len = -1
     skip = dict.fromkeys([changelog.rev(n) for n in state['skip']])
-    for n in ancestors[badrev]:
+    for n in candidates:
         if n in skip:
             continue
-        a = len(ancestors[n]) # number of ancestors
+        a = ancestors[n] # number of ancestors
         b = tot - a # number of non-ancestors
         value = min(a, b) # how good is this test?
         if value > best_len: