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