comparison hgext/hbisect.py @ 5736:6e79d5e0e541

bisect: keep history of all bad revisions - use a single state dict - find the minimum bad revision
author Matt Mackall <mpm@selenic.com>
date Thu, 27 Dec 2007 23:55:40 -0600
parents 9079081b8982
children 6c8df073c3ee
comparison
equal deleted inserted replaced
5735:9079081b8982 5736:6e79d5e0e541
13 class bisect(object): 13 class bisect(object):
14 """dichotomic search in the DAG of changesets""" 14 """dichotomic search in the DAG of changesets"""
15 def __init__(self, ui, repo): 15 def __init__(self, ui, repo):
16 self.repo = repo 16 self.repo = repo
17 self.ui = ui 17 self.ui = ui
18 self.goodnodes = [] 18 self.state = {'good': [], 'bad': [], 'skip': []}
19 self.skipnodes = []
20 self.badnode = None
21 19
22 p = self.repo.join("bisect.state") 20 p = self.repo.join("bisect.state")
23 if os.path.exists(p): 21 if os.path.exists(p):
24 for l in self.repo.opener("bisect.state"): 22 for l in self.repo.opener("bisect.state"):
25 type, node = l[:-1].split() 23 kind, node = l[:-1].split()
26 node = self.repo.lookup(node) 24 node = self.repo.lookup(node)
27 if type == "good": 25 if kind not in self.state:
28 self.goodnodes.append(node) 26 raise util.Abort(_("unknown bisect kind %s") % kind)
29 elif type == "skip": 27 self.state[kind].append(node)
30 self.skipnodes.append(node)
31 elif type == "bad":
32 self.badnode = node
33 28
34 def write(self): 29 def write(self):
35 f = self.repo.opener("bisect.state", "w") 30 f = self.repo.opener("bisect.state", "w")
36 for n in self.goodnodes: 31 for kind in self.state:
37 f.write("good %s\n" % hg.hex(n)) 32 for node in self.state[kind]:
38 for n in self.skipnodes: 33 f.write("%s %s\n" % (kind, hg.hex(node)))
39 f.write("skip %s\n" % hg.hex(n))
40 if self.badnode:
41 f.write("bad %s\n" % hg.hex(self.badnode))
42 34
43 def init(self): 35 def init(self):
44 """start a new bisection""" 36 """start a new bisection"""
45 p = self.repo.join("bisect.state") 37 p = self.repo.join("bisect.state")
46 if os.path.exists(p): 38 if os.path.exists(p):
47 os.unlink(p) 39 os.unlink(p)
48 40
49 def bisect(self): 41 def bisect(self):
50 cl = self.repo.changelog 42 cl = self.repo.changelog
51 clparents = cl.parentrevs 43 clparents = cl.parentrevs
52 bad = self.badnode 44 # only the earliest bad revision matters
53 badrev = cl.rev(bad) 45 badrev = min([cl.rev(n) for n in self.state['bad']])
46 bad = cl.node(badrev)
54 47
55 # build ancestors array 48 # build ancestors array
56 ancestors = [[]] * (cl.count() + 1) # an extra for [-1] 49 ancestors = [[]] * (cl.count() + 1) # an extra for [-1]
57 50
58 # clear goodnodes from array 51 # clear good revs from array
59 for good in self.goodnodes: 52 for node in self.state['good']:
60 ancestors[cl.rev(good)] = None 53 ancestors[cl.rev(node)] = None
61 for rev in xrange(cl.count(), -1, -1): 54 for rev in xrange(cl.count(), -1, -1):
62 if ancestors[rev] is None: 55 if ancestors[rev] is None:
63 for prev in clparents(rev): 56 for prev in clparents(rev):
64 ancestors[prev] = None 57 ancestors[prev] = None
65 58
90 raise util.Abort(_("Could not find the first bad revision")) 83 raise util.Abort(_("Could not find the first bad revision"))
91 84
92 # have we narrowed it down to one entry? 85 # have we narrowed it down to one entry?
93 tot = len(ancestors[badrev]) 86 tot = len(ancestors[badrev])
94 if tot == 1: 87 if tot == 1:
95 return (self.badnode, 0) 88 return (bad, 0)
96 89
97 # find the best node to test 90 # find the best node to test
98 best_rev = None 91 best_rev = None
99 best_len = -1 92 best_len = -1
100 skip = dict.fromkeys([cl.rev(s) for s in self.skipnodes]) 93 skip = dict.fromkeys([cl.rev(n) for n in self.state['skip']])
101 for n in ancestors[badrev]: 94 for n in ancestors[badrev]:
102 if n in skip: 95 if n in skip:
103 continue 96 continue
104 a = len(ancestors[n]) # number of ancestors 97 a = len(ancestors[n]) # number of ancestors
105 b = tot - a # number of non-ancestors 98 b = tot - a # number of non-ancestors
112 105
113 return (best_node, tot) 106 return (best_node, tot)
114 107
115 def next(self): 108 def next(self):
116 """find and update to the next revision to test""" 109 """find and update to the next revision to test"""
117 if self.goodnodes and self.badnode: 110 if self.state['good'] and self.state['bad']:
118 node, changesets = self.bisect() 111 node, changesets = self.bisect()
119 112
120 if changesets == 0: 113 if changesets == 0:
121 self.ui.write(_("The first bad revision is:\n")) 114 self.ui.write(_("The first bad revision is:\n"))
122 displayer = cmdutil.show_changeset(self.ui, self.repo, {}) 115 displayer = cmdutil.show_changeset(self.ui, self.repo, {})
133 cmdutil.bail_if_changed(self.repo) 126 cmdutil.bail_if_changed(self.repo)
134 return hg.clean(self.repo, node) 127 return hg.clean(self.repo, node)
135 128
136 def good(self, rev=None): 129 def good(self, rev=None):
137 """mark revision as good and update to the next revision to test""" 130 """mark revision as good and update to the next revision to test"""
138 self.goodnodes.append(self.repo.lookup(rev or '.')) 131 self.state['good'].append(self.repo.lookup(rev or '.'))
139 self.write() 132 self.write()
140 return self.next() 133 return self.next()
141 134
142 def skip(self, rev=None): 135 def skip(self, rev=None):
143 """mark revision as skipped and update to the next revision to test""" 136 """mark revision as skipped and update to the next revision to test"""
144 self.skipnodes.append(self.repo.lookup(rev or '.')) 137 self.state['skip'].append(self.repo.lookup(rev or '.'))
145 self.write() 138 self.write()
146 return self.next() 139 return self.next()
147 140
148 def bad(self, rev=None): 141 def bad(self, rev=None):
149 """mark revision as bad and update to the next revision to test""" 142 """mark revision as bad and update to the next revision to test"""
150 self.badnode = self.repo.lookup(rev or '.') 143 self.state['bad'].append(self.repo.lookup(rev or '.'))
151 self.write() 144 self.write()
152 return self.next() 145 return self.next()
153 146
154 def bisect_run(ui, repo, node=None, extra=None, 147 def bisect_run(ui, repo, node=None, extra=None,
155 reset=None, good=None, bad=None, skip=None): 148 reset=None, good=None, bad=None, skip=None):