changeset 7227:e1afb50ec2aa

bisect: ability to check revision with command
author Alexander Solovyov <piranha@piranha.org.ua>
date Fri, 10 Oct 2008 16:58:14 +0300
parents b71a52f101dc
children 9b72c732ed2f
files mercurial/commands.py mercurial/hbisect.py
diffstat 2 files changed, 103 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/commands.py	Tue Oct 14 21:28:49 2008 +0200
+++ b/mercurial/commands.py	Fri Oct 10 16:58:14 2008 +0300
@@ -260,7 +260,7 @@
             ui.status(_('(use "backout --merge" '
                         'if you want to auto-merge)\n'))
 
-def bisect(ui, repo, rev=None, extra=None,
+def bisect(ui, repo, rev=None, extra=None, command=None,
                reset=None, good=None, bad=None, skip=None, noupdate=None):
     """subdivision search of changesets
 
@@ -275,67 +275,12 @@
 
     As a shortcut, you can also use the revision argument to mark a
     revision as good or bad without checking it out first.
+
+    If you supply a command it will be used for automatic bisection. Its
+    exit status will be used as flag to mark revision as bad or good (good
+    in case of 0 and bad in any other case).
     """
-    # backward compatibility
-    if rev in "good bad reset init".split():
-        ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
-        cmd, rev, extra = rev, extra, None
-        if cmd == "good":
-            good = True
-        elif cmd == "bad":
-            bad = True
-        else:
-            reset = True
-    elif extra or good + bad + skip + reset > 1:
-        raise util.Abort(_('incompatible arguments'))
-
-    if reset:
-        p = repo.join("bisect.state")
-        if os.path.exists(p):
-            os.unlink(p)
-        return
-
-    # load state
-    state = {'good': [], 'bad': [], 'skip': []}
-    if os.path.exists(repo.join("bisect.state")):
-        for l in repo.opener("bisect.state"):
-            kind, node = l[:-1].split()
-            node = repo.lookup(node)
-            if kind not in state:
-                raise util.Abort(_("unknown bisect kind %s") % kind)
-            state[kind].append(node)
-
-    # update state
-    node = repo.lookup(rev or '.')
-    if good:
-        state['good'].append(node)
-    elif bad:
-        state['bad'].append(node)
-    elif skip:
-        state['skip'].append(node)
-
-    # save state
-    f = repo.opener("bisect.state", "w", atomictemp=True)
-    wlock = repo.wlock()
-    try:
-        for kind in state:
-            for node in state[kind]:
-                f.write("%s %s\n" % (kind, hex(node)))
-        f.rename()
-    finally:
-        del wlock
-
-    if not state['good'] or not state['bad']:
-        if (good or bad or skip or reset):
-            return
-        if not state['good']:
-            raise util.Abort(_('cannot bisect (no known good revisions)'))
-        else:
-            raise util.Abort(_('cannot bisect (no known bad revisions)'))
-
-    # actually bisect
-    nodes, changesets, good = hbisect.bisect(repo.changelog, state)
-    if changesets == 0:
+    def print_result(nodes, good):
         displayer = cmdutil.show_changeset(ui, repo, {})
         transition = (good and "good" or "bad")
         if len(nodes) == 1:
@@ -348,6 +293,74 @@
                        "%s revision could be any of:\n") % transition)
             for n in nodes:
                 displayer.show(changenode=n)
+
+    def check_state(state, interactive=True):
+        if not state['good'] or not state['bad']:
+            if (good or bad or skip or reset) and interactive:
+                return
+            if not state['good']:
+                raise util.Abort(_('cannot bisect (no known good revisions)'))
+            else:
+                raise util.Abort(_('cannot bisect (no known bad revisions)'))
+        return True
+
+    # backward compatibility
+    if rev in "good bad reset init".split():
+        ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
+        cmd, rev, extra = rev, extra, None
+        if cmd == "good":
+            good = True
+        elif cmd == "bad":
+            bad = True
+        else:
+            reset = True
+    elif extra or good + bad + skip + reset + bool(command) > 1:
+        raise util.Abort(_('incompatible arguments'))
+
+    if reset:
+        p = repo.join("bisect.state")
+        if os.path.exists(p):
+            os.unlink(p)
+        return
+
+    state = hbisect.load_state(repo)
+
+    if command:
+        changesets = 1
+        while changesets:
+            # check state
+            status = bool(list(os.popen3(command)[2]))
+            node = repo.lookup(rev or '.')
+            transition = (status and 'bad' or 'good')
+            state[transition].append(node)
+            ui.note(_('Changeset %s: %s\n') % (short(node), transition))
+            check_state(state, interactive=False)
+            # bisect
+            nodes, changesets, good = hbisect.bisect(repo.changelog, state)
+            # update to next check
+            cmdutil.bail_if_changed(repo)
+            hg.clean(repo, nodes[0], show_stats=False)
+        hbisect.save_state(repo, state)
+        return print_result(nodes, not status)
+
+    # update state
+    node = repo.lookup(rev or '.')
+    if good:
+        state['good'].append(node)
+    elif bad:
+        state['bad'].append(node)
+    elif skip:
+        state['skip'].append(node)
+
+    hbisect.save_state(repo, state)
+
+    if not check_state(state):
+        return
+
+    # actually bisect
+    nodes, changesets, good = hbisect.bisect(repo.changelog, state)
+    if changesets == 0:
+        print_result(nodes, good)
     else:
         assert len(nodes) == 1 # only a single node can be tested next
         node = nodes[0]
@@ -3008,8 +3021,9 @@
           ('g', 'good', False, _('mark changeset good')),
           ('b', 'bad', False, _('mark changeset bad')),
           ('s', 'skip', False, _('skip testing changeset')),
+          ('c', 'command', '', _('Use command to check changeset state')),
           ('U', 'noupdate', False, _('do not update to target'))],
-         _("hg bisect [-gbsr] [REV]")),
+         _("hg bisect [-gbsr] [REV] [-c COMMAND]")),
     "branch":
         (branch,
          [('f', 'force', None,
--- a/mercurial/hbisect.py	Tue Oct 14 21:28:49 2008 +0200
+++ b/mercurial/hbisect.py	Fri Oct 10 16:58:14 2008 +0300
@@ -7,8 +7,9 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
+import os
 from i18n import _
-from node import short
+from node import short, hex
 import util
 
 def bisect(changelog, state):
@@ -116,3 +117,28 @@
     best_node = changelog.node(best_rev)
 
     return ([best_node], tot, good)
+
+
+def load_state(repo):
+    state = {'good': [], 'bad': [], 'skip': []}
+    if os.path.exists(repo.join("bisect.state")):
+        for l in repo.opener("bisect.state"):
+            kind, node = l[:-1].split()
+            node = repo.lookup(node)
+            if kind not in state:
+                raise util.Abort(_("unknown bisect kind %s") % kind)
+            state[kind].append(node)
+    return state
+
+
+def save_state(repo, state):
+    f = repo.opener("bisect.state", "w", atomictemp=True)
+    wlock = repo.wlock()
+    try:
+        for kind in state:
+            for node in state[kind]:
+                f.write("%s %s\n" % (kind, hex(node)))
+        f.rename()
+    finally:
+        del wlock
+