##// END OF EJS Templates
bisect: use more standard command syntax and help
Matt Mackall -
r5735:9079081b default
parent child Browse files
Show More
@@ -1,213 +1,203 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, cmdutil
10 from mercurial import hg, util, cmdutil
11 import os, array
11 import os, array
12
12
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.goodnodes = []
19 self.skipnodes = []
19 self.skipnodes = []
20 self.badnode = None
20 self.badnode = None
21
21
22 p = self.repo.join("bisect.state")
22 p = self.repo.join("bisect.state")
23 if os.path.exists(p):
23 if os.path.exists(p):
24 for l in self.repo.opener("bisect.state"):
24 for l in self.repo.opener("bisect.state"):
25 type, node = l[:-1].split()
25 type, node = l[:-1].split()
26 node = self.repo.lookup(node)
26 node = self.repo.lookup(node)
27 if type == "good":
27 if type == "good":
28 self.goodnodes.append(node)
28 self.goodnodes.append(node)
29 elif type == "skip":
29 elif type == "skip":
30 self.skipnodes.append(node)
30 self.skipnodes.append(node)
31 elif type == "bad":
31 elif type == "bad":
32 self.badnode = node
32 self.badnode = node
33
33
34 def write(self):
34 def write(self):
35 f = self.repo.opener("bisect.state", "w")
35 f = self.repo.opener("bisect.state", "w")
36 for n in self.goodnodes:
36 for n in self.goodnodes:
37 f.write("good %s\n" % hg.hex(n))
37 f.write("good %s\n" % hg.hex(n))
38 for n in self.skipnodes:
38 for n in self.skipnodes:
39 f.write("skip %s\n" % hg.hex(n))
39 f.write("skip %s\n" % hg.hex(n))
40 if self.badnode:
40 if self.badnode:
41 f.write("bad %s\n" % hg.hex(self.badnode))
41 f.write("bad %s\n" % hg.hex(self.badnode))
42
42
43 def init(self):
43 def init(self):
44 """start a new bisection"""
44 """start a new bisection"""
45 p = self.repo.join("bisect.state")
45 p = self.repo.join("bisect.state")
46 if os.path.exists(p):
46 if os.path.exists(p):
47 os.unlink(p)
47 os.unlink(p)
48
48
49 def bisect(self):
49 def bisect(self):
50 cl = self.repo.changelog
50 cl = self.repo.changelog
51 clparents = cl.parentrevs
51 clparents = cl.parentrevs
52 bad = self.badnode
52 bad = self.badnode
53 badrev = cl.rev(bad)
53 badrev = cl.rev(bad)
54
54
55 # build ancestors array
55 # build ancestors array
56 ancestors = [[]] * (cl.count() + 1) # an extra for [-1]
56 ancestors = [[]] * (cl.count() + 1) # an extra for [-1]
57
57
58 # clear goodnodes from array
58 # clear goodnodes from array
59 for good in self.goodnodes:
59 for good in self.goodnodes:
60 ancestors[cl.rev(good)] = None
60 ancestors[cl.rev(good)] = None
61 for rev in xrange(cl.count(), -1, -1):
61 for rev in xrange(cl.count(), -1, -1):
62 if ancestors[rev] is None:
62 if ancestors[rev] is None:
63 for prev in clparents(rev):
63 for prev in clparents(rev):
64 ancestors[prev] = None
64 ancestors[prev] = None
65
65
66 if ancestors[badrev] is None:
66 if ancestors[badrev] is None:
67 raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
67 raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
68 % (badrev, hg.short(bad)))
68 % (badrev, hg.short(bad)))
69
69
70 # accumulate ancestor lists
70 # accumulate ancestor lists
71 for rev in xrange(badrev + 1):
71 for rev in xrange(badrev + 1):
72 if ancestors[rev] == []:
72 if ancestors[rev] == []:
73 p1, p2 = clparents(rev)
73 p1, p2 = clparents(rev)
74 a1, a2 = ancestors[p1], ancestors[p2]
74 a1, a2 = ancestors[p1], ancestors[p2]
75 if a1:
75 if a1:
76 if a2:
76 if a2:
77 # merge ancestor lists
77 # merge ancestor lists
78 a = dict.fromkeys(a2)
78 a = dict.fromkeys(a2)
79 a.update(dict.fromkeys(a1))
79 a.update(dict.fromkeys(a1))
80 a[rev] = None
80 a[rev] = None
81 ancestors[rev] = array.array("l", a.keys())
81 ancestors[rev] = array.array("l", a.keys())
82 else:
82 else:
83 ancestors[rev] = a1 + array.array("l", [rev])
83 ancestors[rev] = a1 + array.array("l", [rev])
84 elif a2:
84 elif a2:
85 ancestors[rev] = a2 + array.array("l", [rev])
85 ancestors[rev] = a2 + array.array("l", [rev])
86 else:
86 else:
87 ancestors[rev] = array.array("l", [rev])
87 ancestors[rev] = array.array("l", [rev])
88
88
89 if badrev not in ancestors[badrev]:
89 if badrev not in ancestors[badrev]:
90 raise util.Abort(_("Could not find the first bad revision"))
90 raise util.Abort(_("Could not find the first bad revision"))
91
91
92 # have we narrowed it down to one entry?
92 # have we narrowed it down to one entry?
93 tot = len(ancestors[badrev])
93 tot = len(ancestors[badrev])
94 if tot == 1:
94 if tot == 1:
95 return (self.badnode, 0)
95 return (self.badnode, 0)
96
96
97 # find the best node to test
97 # find the best node to test
98 best_rev = None
98 best_rev = None
99 best_len = -1
99 best_len = -1
100 skip = dict.fromkeys([cl.rev(s) for s in self.skipnodes])
100 skip = dict.fromkeys([cl.rev(s) for s in self.skipnodes])
101 for n in ancestors[badrev]:
101 for n in ancestors[badrev]:
102 if n in skip:
102 if n in skip:
103 continue
103 continue
104 a = len(ancestors[n]) # number of ancestors
104 a = len(ancestors[n]) # number of ancestors
105 b = tot - a # number of non-ancestors
105 b = tot - a # number of non-ancestors
106 value = min(a, b) # how good is this test?
106 value = min(a, b) # how good is this test?
107 if value > best_len:
107 if value > best_len:
108 best_len = value
108 best_len = value
109 best_rev = n
109 best_rev = n
110 assert best_rev is not None
110 assert best_rev is not None
111 best_node = cl.node(best_rev)
111 best_node = cl.node(best_rev)
112
112
113 return (best_node, tot)
113 return (best_node, tot)
114
114
115 def next(self):
115 def next(self):
116 """find and update to the next revision to test"""
116 """find and update to the next revision to test"""
117 if self.goodnodes and self.badnode:
117 if self.goodnodes and self.badnode:
118 node, changesets = self.bisect()
118 node, changesets = self.bisect()
119
119
120 if changesets == 0:
120 if changesets == 0:
121 self.ui.write(_("The first bad revision is:\n"))
121 self.ui.write(_("The first bad revision is:\n"))
122 displayer = cmdutil.show_changeset(self.ui, self.repo, {})
122 displayer = cmdutil.show_changeset(self.ui, self.repo, {})
123 displayer.show(changenode=node)
123 displayer.show(changenode=node)
124 elif node is not None:
124 elif node is not None:
125 # compute the approximate number of remaining tests
125 # compute the approximate number of remaining tests
126 tests, size = 0, 2
126 tests, size = 0, 2
127 while size <= changesets:
127 while size <= changesets:
128 tests, size = tests + 1, size * 2
128 tests, size = tests + 1, size * 2
129 rev = self.repo.changelog.rev(node)
129 rev = self.repo.changelog.rev(node)
130 self.ui.write(_("Testing changeset %s:%s "
130 self.ui.write(_("Testing changeset %s:%s "
131 "(%s changesets remaining, ~%s tests)\n")
131 "(%s changesets remaining, ~%s tests)\n")
132 % (rev, hg.short(node), changesets, tests))
132 % (rev, hg.short(node), changesets, tests))
133 cmdutil.bail_if_changed(self.repo)
133 cmdutil.bail_if_changed(self.repo)
134 return hg.clean(self.repo, node)
134 return hg.clean(self.repo, node)
135
135
136 def good(self, rev=None):
136 def good(self, rev=None):
137 """mark revision as good and update to the next revision to test"""
137 """mark revision as good and update to the next revision to test"""
138 self.goodnodes.append(self.repo.lookup(rev or '.'))
138 self.goodnodes.append(self.repo.lookup(rev or '.'))
139 self.write()
139 self.write()
140 return self.next()
140 return self.next()
141
141
142 def skip(self, rev=None):
142 def skip(self, rev=None):
143 """mark revision as skipped and update to the next revision to test"""
143 """mark revision as skipped and update to the next revision to test"""
144 self.skipnodes.append(self.repo.lookup(rev or '.'))
144 self.skipnodes.append(self.repo.lookup(rev or '.'))
145 self.write()
145 self.write()
146 return self.next()
146 return self.next()
147
147
148 def bad(self, rev=None):
148 def bad(self, rev=None):
149 """mark revision as bad and update to the next revision to test"""
149 """mark revision as bad and update to the next revision to test"""
150 self.badnode = self.repo.lookup(rev or '.')
150 self.badnode = self.repo.lookup(rev or '.')
151 self.write()
151 self.write()
152 return self.next()
152 return self.next()
153
153
154 def bisect_run(ui, repo, cmd=None, *args):
154 def bisect_run(ui, repo, node=None, extra=None,
155 reset=None, good=None, bad=None, skip=None):
155 """Subdivision search of changesets
156 """Subdivision search of changesets
156
157
157 This extension helps to find changesets which introduce problems.
158 This extension helps to find changesets which introduce problems.
158 To use, mark the earliest changeset you know exhibits the problem
159 To use, mark the earliest changeset you know exhibits the problem
159 as bad, then mark the latest changeset which is free from the problem
160 as bad, then mark the latest changeset which is free from the problem
160 as good. Bisect will update your working directory to a revision for
161 as good. Bisect will update your working directory to a revision for
161 testing. Once you have performed tests, mark the working directory
162 testing. Once you have performed tests, mark the working directory
162 as bad or good and bisect will either update to another candidate
163 as bad or good and bisect will either update to another candidate
163 changeset or announce that it has found the bad revision.
164 changeset or announce that it has found the bad revision.
164
165
165 Note: bisect expects bad revisions to be descendants of good revisions.
166 Note: bisect expects bad revisions to be descendants of good revisions.
166 If you are looking for the point at which a problem was fixed, then make
167 If you are looking for the point at which a problem was fixed, then make
167 the problem-free state "bad" and the problematic state "good."
168 the problem-free state "bad" and the problematic state "good."
168
169
169 For subcommands see "hg bisect help\"
170 """
170 """
171 def help_(cmd=None, *args):
171 # backward compatibility
172 """show help for a given bisect subcommand or all subcommands"""
172 if node in "good bad reset init".split():
173 cmdtable = bisectcmdtable
173 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
174 if cmd:
174 cmd, node, extra = node, extra, None
175 doc = cmdtable[cmd][0].__doc__
175 if cmd == "good":
176 synopsis = cmdtable[cmd][2]
176 good = True
177 ui.write(synopsis + "\n")
177 elif cmd == "bad":
178 ui.write("\n" + doc + "\n")
178 bad = True
179 return
179 else:
180 ui.write(_("list of subcommands for the bisect extension\n\n"))
180 reset = True
181 cmds = cmdtable.keys()
181 elif extra or good + bad + skip + reset > 1:
182 cmds.sort()
182 raise util.Abort("Incompatible arguments")
183 m = max([len(c) for c in cmds])
184 for cmd in cmds:
185 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
186 ui.write(" %-*s %s\n" % (m, cmd, doc))
187
183
188 b = bisect(ui, repo)
184 b = bisect(ui, repo)
189 bisectcmdtable = {
185 if good:
190 "init": (b.init, 0, _("hg bisect init")),
186 return b.good(node)
191 "bad": (b.bad, 1, _("hg bisect bad [<rev>]")),
187 elif bad:
192 "good": (b.good, 1, _("hg bisect good [<rev>]")),
188 return b.bad(node)
193 "skip": (b.skip, 1, _("hg bisect skip [<rev>]")),
189 elif skip:
194 "next": (b.next, 0, _("hg bisect next")),
190 return b.skip(node)
195 "help": (help_, 1, _("hg bisect help [<subcommand>]")),
191 elif reset:
196 }
192 return b.init()
197
193 else:
198 if cmd == "reset":
194 return b.next()
199 cmd = "init"
200
201 if not bisectcmdtable.has_key(cmd):
202 ui.warn(_("bisect: Unknown sub-command\n"))
203 return help_()
204 if len(args) > bisectcmdtable[cmd][1]:
205 ui.warn(_("bisect: Too many arguments\n"))
206 return help_()
207 ret = bisectcmdtable[cmd][0](*args)
208 return ret
209
195
210 cmdtable = {
196 cmdtable = {
211 "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")),
197 "bisect": (bisect_run,
212 #"bisect-test": (test, [], "hg bisect-test rev"),
198 [('r', 'reset', False, _('reset bisect state')),
199 ('g', 'good', False, _('mark changeset good')),
200 ('b', 'bad', False, _('mark changeset bad')),
201 ('s', 'skip', False, _('skip testing changeset'))],
202 _("hg bisect [-gbsr] [REV]"))
213 }
203 }
@@ -1,36 +1,36 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 set -e
3 set -e
4
4
5 echo "[extensions]" >> $HGRCPATH
5 echo "[extensions]" >> $HGRCPATH
6 echo "hbisect=" >> $HGRCPATH
6 echo "hbisect=" >> $HGRCPATH
7
7
8 echo % init
8 echo % init
9 hg init
9 hg init
10
10
11 echo % committing changes
11 echo % committing changes
12 count=0
12 count=0
13 echo > a
13 echo > a
14 while test $count -lt 32 ; do
14 while test $count -lt 32 ; do
15 echo 'a' >> a
15 echo 'a' >> a
16 test $count -eq 0 && hg add
16 test $count -eq 0 && hg add
17 hg ci -m "msg $count" -d "$count 0"
17 hg ci -m "msg $count" -d "$count 0"
18 echo % committed changeset $count
18 echo % committed changeset $count
19 count=`expr $count + 1`
19 count=`expr $count + 1`
20 done
20 done
21
21
22 echo % log
22 echo % log
23 hg log
23 hg log
24
24
25 echo % hg up -C
25 echo % hg up -C
26 hg up -C
26 hg up -C
27
27
28 echo % bisect test
28 echo % bisect test
29 hg bisect init
29 hg bisect -r
30 hg bisect bad
30 hg bisect -b
31 hg bisect good 1
31 hg bisect -g 1
32 hg bisect good
32 hg bisect -g
33 hg bisect good
33 hg bisect -g
34 hg bisect good
34 hg bisect -g
35 hg bisect bad
35 hg bisect -b
36 hg bisect good
36 hg bisect -g
General Comments 0
You need to be logged in to leave comments. Login now