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