WIP on handling stacks draft
authorBoris Feld <boris.feld@octobus.net>
Thu, 01 Feb 2018 18:26:56 +0100
changeset 5218 39c8585a7126
parent 3509 174d966e1eaa
WIP on handling stacks
hgext3rd/evolve/rewind.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/rewind.py	Thu Feb 01 18:26:56 2018 +0100
@@ -0,0 +1,150 @@
+from __future__ import absolute_import
+
+import random
+
+from mercurial import (
+    error,
+    hg,
+    lock as lockmod,
+    obsolete,
+    rewriteutil,
+    scmutil,
+)
+
+from mercurial.i18n import _
+
+from . import (
+    exthelper,
+    rewriteutil,
+)
+
+eh = exthelper.exthelper()
+
+@eh.command(
+    '^rewind',
+    [('r', 'rev', [], _("revision to rewind")),
+    ],
+    _('[-r] [REV]'))
+def rewind(ui, repo, *rev, **kwargs):
+    """rewind let you restore a topological head closest predecessor. It main
+    use-case is cancel the latest history rewriting operation, for example
+    amend. It cannot handle rebased stacks yet.
+    """
+    unfilteredrepo = repo.unfiltered()
+    currentwdirparent = repo['.']
+    revs = scmutil.revrange(repo, kwargs.get('rev', []))
+
+    # Check that we can rewind these changesets
+    rewriteutil.precheck(repo, revs, action="rewind")
+
+    # Check that we are not rebasing non-heads for the moment
+    ndrevs = list(repo.revs('descendants(%ld)', revs))
+    if len(ndrevs) != len(revs):
+        raise error.Abort('rewind only support rewinding heads')
+
+    # Warning if we will leave bookmarks behind
+    notbookmarked = list(repo.revs('%ld - bookmark()', revs))
+    if len(notbookmarked) != len(revs):
+        for rev in revs:
+            ctx = repo[rev]
+            if ctx.bookmarks():
+                for bookmark in ctx.bookmarks():
+                    ui.warn("Bookmark %r will be left behind\n" % bookmark)
+
+    revisionstorewind = _getrewindrevisions(revs, unfilteredrepo)
+
+    newrevs = {}
+
+    with repo.wlock(), repo.lock(), repo.transaction('rewind'):
+        for base, revision in revisionstorewind:
+            newrev = _rewindrevision(base, revision, unfilteredrepo)
+            newrevs[base] = newrev
+
+    # Finally update to the new changeset if we rewinded the current wdir
+    # parent
+    if currentwdirparent in newrevs:
+        hg.updaterepo(repo, newrevs[currentwdirparent], False)
+
+def _getrewindrevisions(revs, repo):
+    """returns the list of revisions ordered in a way that each revision
+    parents is either stable or is present before in the returned list
+    """
+
+    # We only support a single base revision for the moment
+    if len(revs) != 1:
+        raise error.Abort('rewind only support rewinding a single head')
+
+    base = list(revs)[0]
+    base = repo[base]
+
+    # Get the predecessor
+    predecessors = repo.obsstore.predecessors
+
+    preds = predecessors.get(base.node(), ())
+
+    if len(preds) == 0:
+        # Changeset has no predecessor, abort
+        raise error.Abort('revision %s has no predecessor' % base)
+    elif len(preds) > 1:
+        raise error.Abort('revision %s has too many predecessors' % base)
+
+    # Only support simple evolution flow for the moment
+    assert len(preds) == 1
+
+    # Take the predecessor as the start
+    nodes = [list(preds)[0][0]]
+    seen = set()
+    rewindrevisions = []
+
+    while nodes:
+        node = nodes[-1]
+        ctx = repo[node]
+
+        stable = True
+
+        # Check the parents
+        p1 = ctx.p1()
+        if p1 and p1.obsolete():
+            stable = False
+            nodes.append(p1)
+
+        p2 = ctx.p2()
+        if p2 and p2.obsolete():
+            stable = False
+            nodes.append(p2)
+
+        if stable is False:
+            continue
+
+        # If both parents are stable or in the list, add it to the plan
+        rewindrevisions.append(ctx)
+        nodes.pop()
+
+    return [(base, single_predecessor)]
+
+def _rewindrevision(base, ctx, repo):
+    """rewind a single revision named ctx. Base is the revision to mark as
+    obsolete
+    """
+    extra = ctx.extra().copy()
+    extra['__rewind-base__'] = base.node()
+
+    p1 = ctx.p1().node()
+    p1ctx = repo[p1]
+    p2 = ctx.p2().node()
+    p2ctx = repo[p2]
+
+    # Check that we are not creating orphans
+    if p1ctx.obsolete() or p1ctx.isunstable() or p2ctx.obsolete() or p2ctx.isunstable():
+        raise error.Abort("rewind don't works for rebased changesets yet")
+
+    extradict = {'extra': extra}
+
+    new, unusedvariable = rewriteutil.rewrite(repo, ctx, [], ctx,
+                                              [p1, p2],
+                                              commitopts=extradict)
+
+    # And create a obsmarker between the new node and the base
+    obsolete.createmarkers(repo, [(base, (repo[new],))])
+
+    return new