comparison mercurial/hbisect.py @ 5775:2dd202a6e15b

bisect: make bisect a built-in command
author Matt Mackall <mpm@selenic.com>
date Mon, 31 Dec 2007 18:20:34 -0600
parents hgext/hbisect.py@c850a8640981
children 35ec669cdd43
comparison
equal deleted inserted replaced
5774:c850a8640981 5775:2dd202a6e15b
1 # changelog bisection for mercurial
2 #
3 # Copyright 2007 Matt Mackall
4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
5 # Inspired by git bisect, extension skeleton taken from mq.py.
6 #
7 # This software may be used and distributed according to the terms
8 # of the GNU General Public License, incorporated herein by reference.
9
10 from i18n import _
11 import hg, util
12
13 def bisect(changelog, state):
14 clparents = changelog.parentrevs
15 # only the earliest bad revision matters
16 badrev = min([changelog.rev(n) for n in state['bad']])
17 bad = changelog.node(badrev)
18 skip = dict.fromkeys([changelog.rev(n) for n in state['skip']])
19
20 # build ancestors array
21 ancestors = [[]] * (changelog.count() + 1) # an extra for [-1]
22
23 # clear good revs from array
24 for node in state['good']:
25 ancestors[changelog.rev(node)] = None
26 for rev in xrange(changelog.count(), -1, -1):
27 if ancestors[rev] is None:
28 for prev in clparents(rev):
29 ancestors[prev] = None
30
31 if ancestors[badrev] is None:
32 raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
33 % (badrev, hg.short(bad)))
34
35 # build children dict
36 children = {}
37 visit = [badrev]
38 candidates = []
39 while visit:
40 rev = visit.pop(0)
41 if ancestors[rev] == []:
42 candidates.append(rev)
43 for prev in clparents(rev):
44 if prev != -1:
45 if prev in children:
46 children[prev].append(rev)
47 else:
48 children[prev] = [rev]
49 visit.append(prev)
50
51 candidates.sort()
52 # have we narrowed it down to one entry?
53 tot = len(candidates)
54 if tot == 1:
55 return (bad, 0)
56 perfect = tot / 2
57
58 # find the best node to test
59 best_rev = None
60 best_len = -1
61 poison = {}
62 for rev in candidates:
63 if rev in poison:
64 for c in children.get(rev, []):
65 poison[c] = True # poison children
66 continue
67
68 a = ancestors[rev] or [rev]
69 ancestors[rev] = None
70
71 x = len(a) # number of ancestors
72 y = tot - x # number of non-ancestors
73 value = min(x, y) # how good is this test?
74 if value > best_len and rev not in skip:
75 best_len = value
76 best_rev = rev
77 if value == perfect: # found a perfect candidate? quit early
78 break
79
80 if y < perfect: # all downhill from here?
81 for c in children.get(rev, []):
82 poison[c] = True # poison children
83 continue
84
85 for c in children.get(rev, []):
86 if ancestors[c]:
87 ancestors[c] = dict.fromkeys(ancestors[c] + a).keys()
88 else:
89 ancestors[c] = a + [c]
90
91 assert best_rev is not None
92 best_node = changelog.node(best_rev)
93
94 return (best_node, tot)