Mercurial > hg
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): |