##// END OF EJS Templates
bisect: remove useless try/except
Patrick Mezard -
r5322:12181606 default
parent child Browse files
Show More
@@ -1,316 +1,313 b''
1 1 # bisect extension for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 4 # Inspired by git bisect, extension skeleton taken from mq.py.
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from mercurial.i18n import _
10 10 from mercurial import hg, util, commands, cmdutil
11 11 import os, sys, sets
12 12
13 13 versionstr = "0.0.3"
14 14
15 15 def lookup_rev(ui, repo, rev=None):
16 16 """returns rev or the checked-out revision if rev is None"""
17 17 if not rev is None:
18 18 return repo.lookup(rev)
19 19 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
20 20 if len(parents) != 1:
21 21 raise util.Abort(_("unexpected number of parents, "
22 22 "please commit or revert"))
23 23 return parents.pop()
24 24
25 25 def check_clean(ui, repo):
26 26 modified, added, removed, deleted, unknown = repo.status()[:5]
27 27 if modified or added or removed:
28 28 ui.warn("Repository is not clean, please commit or revert\n")
29 29 sys.exit(1)
30 30
31 31 class bisect(object):
32 32 """dichotomic search in the DAG of changesets"""
33 33 def __init__(self, ui, repo):
34 34 self.repo = repo
35 35 self.path = repo.join("bisect")
36 36 self.opener = util.opener(self.path)
37 37 self.ui = ui
38 38 self.goodrevs = []
39 39 self.badrev = None
40 40 self.good_path = "good"
41 41 self.bad_path = "bad"
42 42 self.is_reset = False
43 43
44 44 if os.path.exists(os.path.join(self.path, self.good_path)):
45 45 self.goodrevs = self.opener(self.good_path).read().splitlines()
46 46 self.goodrevs = [hg.bin(x) for x in self.goodrevs]
47 47 if os.path.exists(os.path.join(self.path, self.bad_path)):
48 48 r = self.opener(self.bad_path).read().splitlines()
49 49 if r:
50 50 self.badrev = hg.bin(r.pop(0))
51 51
52 52 def write(self):
53 53 if self.is_reset:
54 54 return
55 55 if not os.path.isdir(self.path):
56 56 os.mkdir(self.path)
57 57 f = self.opener(self.good_path, "w")
58 58 f.write("\n".join([hg.hex(r) for r in self.goodrevs]))
59 59 if len(self.goodrevs) > 0:
60 60 f.write("\n")
61 61 f = self.opener(self.bad_path, "w")
62 62 if self.badrev:
63 63 f.write(hg.hex(self.badrev) + "\n")
64 64
65 65 def init(self):
66 66 """start a new bisection"""
67 67 if os.path.isdir(self.path):
68 68 raise util.Abort(_("bisect directory already exists\n"))
69 69 os.mkdir(self.path)
70 70 check_clean(self.ui, self.repo)
71 71 return 0
72 72
73 73 def reset(self):
74 74 """finish a bisection"""
75 75 if os.path.isdir(self.path):
76 76 sl = [os.path.join(self.path, p)
77 77 for p in [self.bad_path, self.good_path]]
78 78 for s in sl:
79 79 if os.path.exists(s):
80 80 os.unlink(s)
81 81 os.rmdir(self.path)
82 82 # Not sure about this
83 83 #self.ui.write("Going back to tip\n")
84 84 #self.repo.update(self.repo.changelog.tip())
85 85 self.is_reset = True
86 86 return 0
87 87
88 88 def num_ancestors(self, head=None, stop=None):
89 89 """
90 90 returns a dict with the mapping:
91 91 node -> number of ancestors (self included)
92 92 for all nodes who are ancestor of head and
93 93 not in stop.
94 94 """
95 95 if head is None:
96 96 head = self.badrev
97 97 return self.__ancestors_and_nb_ancestors(head, stop)[1]
98 98
99 99 def ancestors(self, head=None, stop=None):
100 100 """
101 101 returns the set of the ancestors of head (self included)
102 102 who are not in stop.
103 103 """
104 104 if head is None:
105 105 head = self.badrev
106 106 return self.__ancestors_and_nb_ancestors(head, stop)[0]
107 107
108 108 def __ancestors_and_nb_ancestors(self, head, stop=None):
109 109 """
110 110 if stop is None then ancestors of goodrevs are used as
111 111 lower limit.
112 112
113 113 returns (anc, n_child) where anc is the set of the ancestors of head
114 114 and n_child is a dictionary with the following mapping:
115 115 node -> number of ancestors (self included)
116 116 """
117 117 cl = self.repo.changelog
118 118 if not stop:
119 119 stop = sets.Set([])
120 120 for i in xrange(len(self.goodrevs)-1, -1, -1):
121 121 g = self.goodrevs[i]
122 122 if g in stop:
123 123 continue
124 124 stop.update(cl.reachable(g))
125 125 def num_children(a):
126 126 """
127 127 returns a dictionnary with the following mapping
128 128 node -> [number of children, empty set]
129 129 """
130 130 d = {a: [0, sets.Set([])]}
131 131 for i in xrange(cl.rev(a)+1):
132 132 n = cl.node(i)
133 133 if not d.has_key(n):
134 134 d[n] = [0, sets.Set([])]
135 135 parents = [p for p in cl.parents(n) if p != hg.nullid]
136 136 for p in parents:
137 137 d[p][0] += 1
138 138 return d
139 139
140 140 if head in stop:
141 141 raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
142 142 % (cl.rev(head), hg.short(head)))
143 143 n_child = num_children(head)
144 144 for i in xrange(cl.rev(head)+1):
145 145 n = cl.node(i)
146 146 parents = [p for p in cl.parents(n) if p != hg.nullid]
147 147 for p in parents:
148 148 n_child[p][0] -= 1
149 149 if not n in stop:
150 150 n_child[n][1].union_update(n_child[p][1])
151 151 if n_child[p][0] == 0:
152 152 n_child[p] = len(n_child[p][1])
153 153 if not n in stop:
154 154 n_child[n][1].add(n)
155 155 if n_child[n][0] == 0:
156 156 if n == head:
157 157 anc = n_child[n][1]
158 158 n_child[n] = len(n_child[n][1])
159 159 return anc, n_child
160 160
161 161 def next(self):
162 162 if not self.badrev:
163 163 raise util.Abort(_("You should give at least one bad revision"))
164 164 if not self.goodrevs:
165 165 self.ui.warn(_("No good revision given\n"))
166 166 self.ui.warn(_("Marking the first revision as good\n"))
167 167 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(
168 168 self.badrev)
169 169 tot = len(ancestors)
170 170 if tot == 1:
171 171 if ancestors.pop() != self.badrev:
172 172 raise util.Abort(_("Could not find the first bad revision"))
173 173 self.ui.write(_("The first bad revision is:\n"))
174 174 displayer = cmdutil.show_changeset(self.ui, self.repo, {})
175 175 displayer.show(changenode=self.badrev)
176 176 return None
177 177 best_rev = None
178 178 best_len = -1
179 179 for n in ancestors:
180 180 l = num_ancestors[n]
181 181 l = min(l, tot - l)
182 182 if l > best_len:
183 183 best_len = l
184 184 best_rev = n
185 185 assert best_rev is not None
186 186 nb_tests = 0
187 187 q, r = divmod(tot, 2)
188 188 while q:
189 189 nb_tests += 1
190 190 q, r = divmod(q, 2)
191 191 msg = _("Testing changeset %s:%s (%s changesets remaining, "
192 192 "~%s tests)\n") % (self.repo.changelog.rev(best_rev),
193 193 hg.short(best_rev), tot, nb_tests)
194 194 self.ui.write(msg)
195 195 return best_rev
196 196
197 197 def autonext(self):
198 198 """find and update to the next revision to test"""
199 199 check_clean(self.ui, self.repo)
200 200 rev = self.next()
201 201 if rev is not None:
202 202 return hg.clean(self.repo, rev)
203 203
204 204 def good(self, rev):
205 205 self.goodrevs.append(rev)
206 206
207 207 def autogood(self, rev=None):
208 208 """mark revision as good and update to the next revision to test"""
209 209 check_clean(self.ui, self.repo)
210 210 rev = lookup_rev(self.ui, self.repo, rev)
211 211 self.good(rev)
212 212 if self.badrev:
213 213 return self.autonext()
214 214
215 215 def bad(self, rev):
216 216 self.badrev = rev
217 217
218 218 def autobad(self, rev=None):
219 219 """mark revision as bad and update to the next revision to test"""
220 220 check_clean(self.ui, self.repo)
221 221 rev = lookup_rev(self.ui, self.repo, rev)
222 222 self.bad(rev)
223 223 if self.goodrevs:
224 224 self.autonext()
225 225
226 226 # should we put it in the class ?
227 227 def test(ui, repo, rev):
228 228 """test the bisection code"""
229 229 b = bisect(ui, repo)
230 230 rev = repo.lookup(rev)
231 231 ui.write("testing with rev %s\n" % hg.hex(rev))
232 232 anc = b.ancestors()
233 233 while len(anc) > 1:
234 234 if not rev in anc:
235 235 ui.warn("failure while bisecting\n")
236 236 sys.exit(1)
237 237 ui.write("it worked :)\n")
238 238 new_rev = b.next()
239 239 ui.write("choosing if good or bad\n")
240 240 if rev in b.ancestors(head=new_rev):
241 241 b.bad(new_rev)
242 242 ui.write("it is bad\n")
243 243 else:
244 244 b.good(new_rev)
245 245 ui.write("it is good\n")
246 246 anc = b.ancestors()
247 247 #repo.update(new_rev, force=True)
248 248 for v in anc:
249 249 if v != rev:
250 250 ui.warn("fail to found cset! :(\n")
251 251 return 1
252 252 ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
253 253 ui.write("Everything is ok :)\n")
254 254 return 0
255 255
256 256 def bisect_run(ui, repo, cmd=None, *args):
257 257 """Dichotomic search in the DAG of changesets
258 258
259 259 This extension helps to find changesets which cause problems.
260 260 To use, mark the earliest changeset you know introduces the problem
261 261 as bad, then mark the latest changeset which is free from the problem
262 262 as good. Bisect will update your working directory to a revision for
263 263 testing. Once you have performed tests, mark the working directory
264 264 as bad or good and bisect will either update to another candidate
265 265 changeset or announce that it has found the bad revision.
266 266
267 267 Note: bisect expects bad revisions to be descendants of good revisions.
268 268 If you are looking for the point at which a problem was fixed, then make
269 269 the problem-free state "bad" and the problematic state "good."
270 270
271 271 For subcommands see "hg bisect help\"
272 272 """
273 273 def help_(cmd=None, *args):
274 274 """show help for a given bisect subcommand or all subcommands"""
275 275 cmdtable = bisectcmdtable
276 276 if cmd:
277 277 doc = cmdtable[cmd][0].__doc__
278 278 synopsis = cmdtable[cmd][2]
279 279 ui.write(synopsis + "\n")
280 280 ui.write("\n" + doc + "\n")
281 281 return
282 282 ui.write(_("list of subcommands for the bisect extension\n\n"))
283 283 cmds = cmdtable.keys()
284 284 cmds.sort()
285 285 m = max([len(c) for c in cmds])
286 286 for cmd in cmds:
287 287 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
288 288 ui.write(" %-*s %s\n" % (m, cmd, doc))
289 289
290 290 b = bisect(ui, repo)
291 291 bisectcmdtable = {
292 292 "init": (b.init, 0, _("hg bisect init")),
293 293 "bad": (b.autobad, 1, _("hg bisect bad [<rev>]")),
294 294 "good": (b.autogood, 1, _("hg bisect good [<rev>]")),
295 295 "next": (b.autonext, 0, _("hg bisect next")),
296 296 "reset": (b.reset, 0, _("hg bisect reset")),
297 297 "help": (help_, 1, _("hg bisect help [<subcommand>]")),
298 298 }
299 299
300 300 if not bisectcmdtable.has_key(cmd):
301 301 ui.warn(_("bisect: Unknown sub-command\n"))
302 302 return help_()
303 303 if len(args) > bisectcmdtable[cmd][1]:
304 304 ui.warn(_("bisect: Too many arguments\n"))
305 305 return help_()
306 try:
307 ret = bisectcmdtable[cmd][0](*args)
308 b.write()
309 return ret
310 except:
311 raise
306 ret = bisectcmdtable[cmd][0](*args)
307 b.write()
308 return ret
312 309
313 310 cmdtable = {
314 311 "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")),
315 312 #"bisect-test": (test, [], "hg bisect-test rev"),
316 313 }
General Comments 0
You need to be logged in to leave comments. Login now