hgext/drophack.py
changeset 790 5af309865040
child 812 60dd0c401034
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/drophack.py	Wed Feb 12 17:18:50 2014 -0800
@@ -0,0 +1,163 @@
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+'''This extension add a hacky command to drop changeset during review
+
+This extension is intended as a temporary hack to allow Matt Mackall to use
+evolve in the Mercurial review it self. You should probably not use it if your
+name is not Matt Mackall.
+'''
+
+import os
+import time
+import contextlib
+
+from mercurial.i18n import _
+from mercurial import cmdutil
+from mercurial import repair
+from mercurial import scmutil
+from mercurial import lock as lockmod
+from mercurial import util
+from mercurial import commands
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+
+@contextlib.contextmanager
+def timed(ui, caption):
+    ostart = os.times()
+    cstart = time.time()
+    yield
+    cstop = time.time()
+    ostop = os.times()
+    wall = cstop - cstart
+    user = ostop[0] - ostart[0]
+    sys  = ostop[1] - ostart[1]
+    comb = user + sys
+    ui.write("%s: wall %f comb %f user %f sys %f\n"
+             % (caption, wall, comb, user, sys))
+
+def obsmarkerchainfrom(obsstore, nodes):
+    """return all marker chain starting from node
+
+    Starting from mean "use as successors"."""
+    # XXX need something smarter for descendant of bumped changeset
+    seennodes = set(nodes)
+    seenmarkers = set()
+    pendingnodes = set(nodes)
+    precursorsmarkers = obsstore.precursors
+    while pendingnodes:
+        current = pendingnodes.pop()
+        new = set()
+        for precmark in precursorsmarkers.get(current, ()):
+            if precmark in seenmarkers:
+                continue
+            seenmarkers.add(precmark)
+            new.add(precmark[0])
+            yield precmark
+        new -= seennodes
+        pendingnodes |= new
+
+def stripmarker(ui, repo, markers):
+    """remove <markers> from the repo obsstore
+
+    The old obsstore content is saved in a `obsstore.prestrip` file
+    """
+    repo = repo.unfiltered()
+    repo.destroying()
+    oldmarkers = list(repo.obsstore._all)
+    util.rename(repo.sjoin('obsstore'),
+                repo.join('obsstore.prestrip'))
+    del repo.obsstore # drop the cache
+    newstore = repo.obsstore
+    assert not newstore # should be empty after rename
+    tr = repo.transaction('drophack')
+    try:
+        for m in oldmarkers:
+            if m not in markers:
+                newstore.add(tr, [m])
+        tr.close()
+    finally:
+        tr.release()
+    repo.destroyed()
+
+
+@command('drop', [('r', 'rev', [], 'revision to update')], _('[-r] revs'))
+def cmddrop(ui, repo, *revs, **opts):
+    """I'm hacky do not use me!
+
+    This command strip a changeset, its precursors and all obsolescence marker
+    associated to its chain.
+
+    There is no way to limit the extend of the purge yet. You may have to
+    repull from other source to get some changeset and obsolescence marker
+    back.
+
+    This intended for Matt Mackall usage only. do not use me.
+    """
+    revs = list(revs)
+    revs.extend(opts['rev'])
+    if not revs:
+        revs = ['.']
+    # get the changeset
+    revs = scmutil.revrange(repo, revs)
+    if not revs:
+        ui.write_err('no revision to drop\n')
+        return 1
+    # lock from the beginning to prevent race
+    wlock = lock = None
+    try:
+        lock = repo.wlock()
+        lock = repo.lock()
+        # check they have no children
+        if repo.revs('%ld and public()', revs):
+            ui.write_err('cannot drop public revision')
+            return 1
+        if repo.revs('children(%ld) - %ld', revs, revs):
+            ui.write_err('cannot drop revision with children')
+            return 1
+        if repo.revs('. and %ld', revs):
+            newrevs = repo.revs('max(::. - %ld)', revs)
+            if newrevs:
+                assert len(newrevs) == 1
+                newrev = newrevs[0]
+            else:
+                newrev = -1
+            commands.update(ui, repo, newrev)
+            ui.status(_('working directory now at %s\n') % repo[newrev])
+        # get all markers and successors up to root
+        nodes = [repo[r].node() for r in revs]
+        with timed(ui, 'search obsmarker'):
+            markers = set(obsmarkerchainfrom(repo.obsstore, nodes))
+        ui.write('%i obsmarkers found\n' % len(markers))
+        cl = repo.unfiltered().changelog
+        with timed(ui, 'search nodes'):
+            allnodes = set(nodes)
+            allnodes.update(m[0] for m in markers if cl.hasnode(m[0]))
+        ui.write('%i nodes found\n' % len(allnodes))
+        cl = repo.changelog
+        visiblenodes = set(n for n in allnodes if cl.hasnode(n))
+        # check constraint again
+        if repo.revs('%ln and public()', visiblenodes):
+            ui.write_err('cannot drop public revision')
+            return 1
+        if repo.revs('children(%ln) - %ln', visiblenodes, visiblenodes):
+            ui.write_err('cannot drop revision with children')
+            return 1
+
+        if markers:
+            # strip them
+            with timed(ui, 'strip obsmarker'):
+                stripmarker(ui, repo, markers)
+        # strip the changeset
+        with timed(ui, 'strip nodes'):
+            repair.strip(ui, repo, allnodes, backup="all", topic='drophack')
+
+    finally:
+        lockmod.release(lock, wlock)
+
+    # rewrite the whole file.
+    # print data.
+    # - time to compute the chain
+    # - time to strip the changeset
+    # - time to strip the obs marker.