hgext/hbisect.py
changeset 2348 1772852d7d14
parent 2305 e7de4dd43472
child 2734 07026da25ed8
equal deleted inserted replaced
2347:5b178298b7f4 2348:1772852d7d14
     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:
    62             f.write(hg.hex(self.badrev) + "\n")
    62             f.write(hg.hex(self.badrev) + "\n")
    63 
    63 
    64     def init(self):
    64     def init(self):
    65         """start a new bisection"""
    65         """start a new bisection"""
    66         if os.path.isdir(self.path):
    66         if os.path.isdir(self.path):
    67             self.ui.warn("bisect directory already exists\n")
    67             raise util.Abort(_("bisect directory already exists\n"))
    68             return 1
       
    69         os.mkdir(self.path)
    68         os.mkdir(self.path)
    70         check_clean(self.ui, self.repo)
    69         check_clean(self.ui, self.repo)
    71         return 0
    70         return 0
    72 
    71 
    73     def reset(self):
    72     def reset(self):
   135                 for p in parents:
   134                 for p in parents:
   136                     d[p][0] += 1
   135                     d[p][0] += 1
   137             return d
   136             return d
   138 
   137 
   139         if head in stop:
   138         if head in stop:
   140             self.ui.warn("Unconsistent state, %s is good and bad\n"
   139             raise util.Abort(_("Unconsistent state, %s:%s is good and bad")
   141                           % hg.hex(head))
   140                              % (cl.rev(head), hg.short(head)))
   142             sys.exit(1)
       
   143         n_child = num_children(head)
   141         n_child = num_children(head)
   144         for i in xrange(cl.rev(head)+1):
   142         for i in xrange(cl.rev(head)+1):
   145             n = cl.node(i)
   143             n = cl.node(i)
   146             parents = [p for p in cl.parents(n) if p != hg.nullid]
   144             parents = [p for p in cl.parents(n) if p != hg.nullid]
   147             for p in parents:
   145             for p in parents:
   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):
   234             ui.write("it is bad\n")
   240             ui.write("it is bad\n")
   235         else:
   241         else:
   236             b.good(new_rev)
   242             b.good(new_rev)
   237             ui.write("it is good\n")
   243             ui.write("it is good\n")
   238         anc = b.ancestors()
   244         anc = b.ancestors()
   239         repo.update(new_rev, force=True)
   245         #repo.update(new_rev, force=True)
   240     for v in anc:
   246     for v in anc:
   241         if v != rev:
   247         if v != rev:
   242             ui.warn("fail to found cset! :(\n")
   248             ui.warn("fail to found cset! :(\n")
   243             return 1
   249             return 1
   244     ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
   250     ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
   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 }