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 gettext as _ |
9 from mercurial.demandload import demandload |
10 from mercurial.demandload import demandload |
10 demandload(globals(), "os sys sets mercurial:hg,util") |
11 demandload(globals(), "os sys sets mercurial:hg,util,commands") |
11 |
12 |
12 versionstr = "0.0.3" |
13 versionstr = "0.0.3" |
13 |
14 |
14 def lookup_rev(ui, repo, rev=None): |
15 def lookup_rev(ui, repo, rev=None): |
15 """returns rev or the checked-out revision if rev is None""" |
16 """returns rev or the checked-out revision if rev is None""" |
16 if not rev is None: |
17 if not rev is None: |
17 return repo.lookup(rev) |
18 return repo.lookup(rev) |
18 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] |
19 if len(parents) != 1: |
20 if len(parents) != 1: |
20 ui.warn("unexpected number of parents\n") |
21 raise util.Abort(_("unexpected number of parents, " |
21 ui.warn("please commit or revert\n") |
22 "please commit or revert")) |
22 sys.exit(1) |
|
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.changes() |
26 modified, added, removed, deleted, unknown = repo.changes() |
27 if modified or added or removed: |
27 if modified or added or removed: |
158 n_child[n] = len(n_child[n][1]) |
156 n_child[n] = len(n_child[n][1]) |
159 return anc, n_child |
157 return anc, n_child |
160 |
158 |
161 def next(self): |
159 def next(self): |
162 if not self.badrev: |
160 if not self.badrev: |
163 self.ui.warn("You should give at least one bad\n") |
161 raise util.Abort(_("You should give at least one bad revision")) |
164 sys.exit(1) |
|
165 if not self.goodrevs: |
162 if not self.goodrevs: |
166 self.ui.warn("No good revision given\n") |
163 self.ui.warn(_("No good revision given\n")) |
167 self.ui.warn("Assuming the first revision is good\n") |
164 self.ui.warn(_("Marking the first revision as good\n")) |
168 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors( |
165 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors( |
169 self.badrev) |
166 self.badrev) |
170 tot = len(ancestors) |
167 tot = len(ancestors) |
171 if tot == 1: |
168 if tot == 1: |
172 if ancestors.pop() != self.badrev: |
169 if ancestors.pop() != self.badrev: |
173 self.ui.warn("Could not find the first bad revision\n") |
170 raise util.Abort(_("Could not find the first bad revision")) |
174 sys.exit(1) |
171 self.ui.write(_("The first bad revision is:\n")) |
175 self.ui.write( |
172 displayer = commands.show_changeset(self.ui, self.repo, {}) |
176 "The first bad revision is: %s\n" % hg.hex(self.badrev)) |
173 displayer.show(changenode=self.badrev) |
177 sys.exit(0) |
174 return None |
178 self.ui.write("%d revisions left\n" % tot) |
|
179 best_rev = None |
175 best_rev = None |
180 best_len = -1 |
176 best_len = -1 |
181 for n in ancestors: |
177 for n in ancestors: |
182 l = num_ancestors[n] |
178 l = num_ancestors[n] |
183 l = min(l, tot - l) |
179 l = min(l, tot - l) |
184 if l > best_len: |
180 if l > best_len: |
185 best_len = l |
181 best_len = l |
186 best_rev = n |
182 best_rev = n |
|
183 assert best_rev is not None |
|
184 nb_tests = 0 |
|
185 q, r = divmod(tot, 2) |
|
186 while q: |
|
187 nb_tests += 1 |
|
188 q, r = divmod(q, 2) |
|
189 msg = _("Testing changeset %s:%s (%s changesets remaining, " |
|
190 "~%s tests)\n") % (self.repo.changelog.rev(best_rev), |
|
191 hg.short(best_rev), tot, nb_tests) |
|
192 self.ui.write(msg) |
187 return best_rev |
193 return best_rev |
188 |
194 |
189 def autonext(self): |
195 def autonext(self): |
190 """find and update to the next revision to test""" |
196 """find and update to the next revision to test""" |
191 check_clean(self.ui, self.repo) |
197 check_clean(self.ui, self.repo) |
192 rev = self.next() |
198 rev = self.next() |
193 self.ui.write("Now testing %s\n" % hg.hex(rev)) |
199 if rev is not None: |
194 return self.repo.update(rev, force=True) |
200 return self.repo.update(rev, force=True) |
195 |
201 |
196 def good(self, rev): |
202 def good(self, rev): |
197 self.goodrevs.append(rev) |
203 self.goodrevs.append(rev) |
198 |
204 |
199 def autogood(self, rev=None): |
205 def autogood(self, rev=None): |
200 """mark revision as good and update to the next revision to test""" |
206 """mark revision as good and update to the next revision to test""" |
201 check_clean(self.ui, self.repo) |
207 check_clean(self.ui, self.repo) |
202 rev = lookup_rev(self.ui, self.repo, rev) |
208 rev = lookup_rev(self.ui, self.repo, rev) |
203 self.good(rev) |
209 self.good(rev) |
204 if self.badrev: |
210 if self.badrev: |
205 self.autonext() |
211 return self.autonext() |
206 |
212 |
207 def bad(self, rev): |
213 def bad(self, rev): |
208 self.badrev = rev |
214 self.badrev = rev |
209 |
215 |
210 def autobad(self, rev=None): |
216 def autobad(self, rev=None): |
256 doc = cmdtable[cmd][0].__doc__ |
262 doc = cmdtable[cmd][0].__doc__ |
257 synopsis = cmdtable[cmd][2] |
263 synopsis = cmdtable[cmd][2] |
258 ui.write(synopsis + "\n") |
264 ui.write(synopsis + "\n") |
259 ui.write("\n" + doc + "\n") |
265 ui.write("\n" + doc + "\n") |
260 return |
266 return |
261 ui.write("list of subcommands for the bisect extension\n\n") |
267 ui.write(_("list of subcommands for the bisect extension\n\n")) |
262 cmds = cmdtable.keys() |
268 cmds = cmdtable.keys() |
263 cmds.sort() |
269 cmds.sort() |
264 m = max([len(c) for c in cmds]) |
270 m = max([len(c) for c in cmds]) |
265 for cmd in cmds: |
271 for cmd in cmds: |
266 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip() |
272 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip() |
267 ui.write(" %-*s %s\n" % (m, cmd, doc)) |
273 ui.write(" %-*s %s\n" % (m, cmd, doc)) |
268 |
274 |
269 b = bisect(ui, repo) |
275 b = bisect(ui, repo) |
270 bisectcmdtable = { |
276 bisectcmdtable = { |
271 "init": (b.init, 0, "hg bisect init"), |
277 "init": (b.init, 0, _("hg bisect init")), |
272 "bad": (b.autobad, 1, "hg bisect bad [<rev>]"), |
278 "bad": (b.autobad, 1, _("hg bisect bad [<rev>]")), |
273 "good": (b.autogood, 1, "hg bisect good [<rev>]"), |
279 "good": (b.autogood, 1, _("hg bisect good [<rev>]")), |
274 "next": (b.autonext, 0, "hg bisect next"), |
280 "next": (b.autonext, 0, _("hg bisect next")), |
275 "reset": (b.reset, 0, "hg bisect reset"), |
281 "reset": (b.reset, 0, _("hg bisect reset")), |
276 "help": (help_, 1, "hg bisect help [<subcommand>]"), |
282 "help": (help_, 1, _("hg bisect help [<subcommand>]")), |
277 } |
283 } |
278 |
284 |
279 if not bisectcmdtable.has_key(cmd): |
285 if not bisectcmdtable.has_key(cmd): |
280 ui.warn("bisect: Unknown sub-command\n") |
286 ui.warn(_("bisect: Unknown sub-command\n")) |
281 return help_() |
287 return help_() |
282 if len(args) > bisectcmdtable[cmd][1]: |
288 if len(args) > bisectcmdtable[cmd][1]: |
283 ui.warn("bisect: Too many arguments\n") |
289 ui.warn(_("bisect: Too many arguments\n")) |
284 return help_() |
290 return help_() |
285 return bisectcmdtable[cmd][0](*args) |
291 return bisectcmdtable[cmd][0](*args) |
286 |
292 |
287 cmdtable = { |
293 cmdtable = { |
288 "bisect": (bisect_run, [], "hg bisect [help|init|reset|next|good|bad]"), |
294 "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")), |
289 #"bisect-test": (test, [], "hg bisect-test rev"), |
295 #"bisect-test": (test, [], "hg bisect-test rev"), |
290 } |
296 } |