changeset 3593:b92114f201c9 mercurial-4.1

test-compat: merge mercurial-4.2 into mercurial-4.1
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Wed, 21 Mar 2018 16:35:18 +0100
parents 7a5c3175015e (diff) 7ac98f83ae6d (current diff)
children 6fd84046d4a4
files hgext3rd/topic/__init__.py tests/test-discovery-obshashrange.t tests/test-evolve-continue.t tests/test-evolve-cycles.t tests/test-evolve-obshistory.t tests/test-evolve-orphan-merge.t tests/test-evolve-orphan-split.t tests/test-evolve-stop.t tests/test-evolve-templates.t tests/test-evolve-topic.t tests/test-evolve.t tests/test-exchange-obsmarkers-case-B5.t tests/test-fold.t tests/test-grab.t tests/test-obsolete.t tests/test-prev-next.t tests/test-prune.t tests/test-topic-change.t tests/test-topic-stack-complex.t tests/test-topic-stack.t tests/test-topic-tutorial.t tests/test-tutorial.t tests/test-unstable.t tests/test-userguide.t tests/test-wireproto.t
diffstat 40 files changed, 2289 insertions(+), 1508 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Sat Jan 20 12:38:11 2018 +0100
+++ b/.hgtags	Wed Mar 21 16:35:18 2018 +0100
@@ -63,3 +63,4 @@
 c4940c22d76b9c6b3c2117a3b490f3c4fd796972 7.0.1
 06a3cb59495636df8b567e49a0fd7fd8fd823074 7.1.0
 bf6b859807bac23752a26e58876fe3a4a9a2fef8 7.2.0
+6c772398eb4e209914e1074cdac4f3ebf714e437 7.2.1
--- a/CHANGELOG	Sat Jan 20 12:38:11 2018 +0100
+++ b/CHANGELOG	Wed Mar 21 16:35:18 2018 +0100
@@ -1,6 +1,21 @@
 Changelog
 =========
 
+7.3.0 -- 2018-03-21
+---------------------
+
+  * grab: new command to grab a changeset, put in on wdir parent
+          and update to it
+  * resolve: shows how to continue evolve after resolving all conflicts
+  * evolve: `--continue` flag now continue evolving all the remaining revisions
+  * prev and next now prompts user to choose a changeset in case of ambiguity
+  * evolve: a new `--stop` flag which can be used to stop interrupted evolution
+
+  * fold: fix issue related to bookmarks movement (issue5772)
+  * amend: take lock before parsing the commit description (issue5266)
+  * legacy: respect 'server.bundle1' config if any is set
+  * previous: fix behavior on obsolete rev when topic is involved (issue5708)
+
 7.2.1 --2018-01-20
 -------------------
 
--- a/debian/changelog	Sat Jan 20 12:38:11 2018 +0100
+++ b/debian/changelog	Wed Mar 21 16:35:18 2018 +0100
@@ -1,3 +1,9 @@
+mercurial-evolve (7.3.0-1) unstable; urgency=medium
+
+  * New upstream release
+
+ -- Pierre-Yves David <pierre-yves.david@ens-lyon.org>  Wed, 21 Mar 2018 15:34:15 +0100
+
 mercurial-evolve (7.2.1-1) unstable; urgency=medium
 
   * new upstream release
--- a/docs/index.rst	Sat Jan 20 12:38:11 2018 +0100
+++ b/docs/index.rst	Wed Mar 21 16:35:18 2018 +0100
@@ -157,6 +157,8 @@
 or care about the new features added by evolution won't be negatively impacted
 by the new default.
 
+You can find the `evolution roadmap in the wiki`_.
+
 .. # .. _`this query`: https://bz.mercurial-scm.org/buglist.cgi?component=evolution&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=NEED_EXAMPLE
 
 Resources
@@ -173,3 +175,4 @@
 .. _`sharing mutable history`: sharing.html
 .. _`concepts`: concepts.html
 .. _`MQ migration guide`: from-mq.html
+.. _`evolution roadmap in the wiki`: https://www.mercurial-scm.org/wiki/CEDRoadMap
--- a/hgext3rd/evolve/__init__.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/__init__.py	Wed Mar 21 16:35:18 2018 +0100
@@ -252,11 +252,7 @@
     evolution=all
 """.strip()
 
-
-import os
 import sys
-import re
-import collections
 import struct
 
 try:
@@ -287,23 +283,18 @@
     cmdutil,
     commands,
     context,
-    copies,
     dirstate,
     error,
     extensions,
     help,
     hg,
     lock as lockmod,
-    merge,
     node,
-    obsolete,
     patch,
-    phases,
     revset,
     scmutil,
 )
 
-from mercurial.commands import mergetoolopts
 from mercurial.i18n import _
 from mercurial.node import nullid
 
@@ -312,7 +303,8 @@
     compat,
     debugcmd,
     cmdrewrite,
-    evolvestate,
+    state,
+    evolvecmd,
     exthelper,
     metadata,
     obscache,
@@ -330,8 +322,6 @@
 minimumhgversion = metadata.minimumhgversion
 buglink = metadata.buglink
 
-sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
-
 # Flags for enabling optional parts of evolve
 commandopt = 'allnewcommands'
 
@@ -345,6 +335,7 @@
               'evolve.date': 'cyan',
               'evolve.current_rev': 'bold',
               'evolve.verb': '',
+              'evolve.operation': 'bold'
               }
 
 _pack = struct.pack
@@ -353,7 +344,6 @@
 aliases, entry = cmdutil.findcmd('commit', commands.table)
 commitopts3 = cmdrewrite.commitopts3
 interactiveopt = cmdrewrite.interactiveopt
-_bookmarksupdater = rewriteutil.bookmarksupdater
 rewrite = rewriteutil.rewrite
 
 # This extension contains the following code
@@ -365,6 +355,7 @@
 
 eh = exthelper.exthelper()
 eh.merge(debugcmd.eh)
+eh.merge(evolvecmd.eh)
 eh.merge(obsexchange.eh)
 eh.merge(checkheads.eh)
 eh.merge(safeguard.eh)
@@ -424,7 +415,9 @@
         evolveopts = ['all']
         repo.ui.setconfig('experimental', 'evolution', evolveopts, 'evolve')
     if obsolete.isenabled(repo, 'exchange'):
-        repo.ui.setconfig('server', 'bundle1', False)
+        # if no config explicitly set, disable bundle1
+        if not isinstance(repo.ui.config('server', 'bundle1'), str):
+            repo.ui.setconfig('server', 'bundle1', False)
 
     class trdescrepo(repo.__class__):
 
@@ -520,18 +513,6 @@
         ui.setconfig('alias', 'odiff',
                      "diff --hidden --rev 'limit(precursors(.),1)' --rev .",
                      'evolve')
-    if ui.config('alias', 'grab') is None:
-        if os.name == 'nt':
-            hgexe = ('"%s"' % util.hgexecutable())
-            ui.setconfig('alias', 'grab', "! " + hgexe
-                         + " rebase --dest . --rev $@ && "
-                         + hgexe + " up tip",
-                         'evolve')
-        else:
-            ui.setconfig('alias', 'grab',
-                         "! $HG rebase --dest . --rev $@ && $HG up tip",
-                         'evolve')
-
 
 ### Troubled revset symbol
 
@@ -710,7 +691,8 @@
     ui.warn(msg % shortnode)
 
     # Check that evolve is activated for performance reasons
-    if ui.quiet or not obsolete.isenabled(repo, commandopt):
+    evolvecommandenabled = any('evolve' in e for e in cmdtable)
+    if ui.quiet or not evolvecommandenabled:
         return
 
     # Show a warning for helping the user to solve the issue
@@ -853,8 +835,8 @@
         raise
 
 def summaryhook(ui, repo):
-    state = evolvestate.evolvestate(repo)
-    if state:
+    evolvestate = state.cmdstate(repo)
+    if evolvestate:
         # i18n: column positioning for "hg summary"
         ui.status(_('evolve: (evolve --continue)\n'))
 
@@ -888,79 +870,6 @@
 ### Old Evolve extension content                                  ###
 #####################################################################
 
-# XXX need clean up and proper sorting in other section
-
-### changeset rewriting logic
-#############################
-
-class MergeFailure(error.Abort):
-    pass
-
-def relocate(repo, orig, dest, pctx=None, keepbranch=False):
-    """rewrite <rev> on dest"""
-    if orig.rev() == dest.rev():
-        raise error.Abort(_('tried to relocate a node on top of itself'),
-                          hint=_("This shouldn't happen. If you still "
-                                 "need to move changesets, please do so "
-                                 "manually with nothing to rebase - working "
-                                 "directory parent is also destination"))
-
-    if pctx is None:
-        if len(orig.parents()) == 2:
-            raise error.Abort(_("tried to relocate a merge commit without "
-                                "specifying which parent should be moved"),
-                              hint=_("Specify the parent by passing in pctx"))
-        pctx = orig.p1()
-
-    commitmsg = orig.description()
-
-    cache = {}
-    sha1s = re.findall(sha1re, commitmsg)
-    unfi = repo.unfiltered()
-    for sha1 in sha1s:
-        ctx = None
-        try:
-            ctx = unfi[sha1]
-        except error.RepoLookupError:
-            continue
-
-        if not ctx.obsolete():
-            continue
-
-        successors = compat.successorssets(repo, ctx.node(), cache)
-
-        # We can't make any assumptions about how to update the hash if the
-        # cset in question was split or diverged.
-        if len(successors) == 1 and len(successors[0]) == 1:
-            newsha1 = node.hex(successors[0][0])
-            commitmsg = commitmsg.replace(sha1, newsha1[:len(sha1)])
-        else:
-            repo.ui.note(_('The stale commit message reference to %s could '
-                           'not be updated\n') % sha1)
-
-    tr = repo.currenttransaction()
-    assert tr is not None
-    try:
-        r = _evolvemerge(repo, orig, dest, pctx, keepbranch)
-        if r[-1]: # some conflict
-            raise error.Abort(_('unresolved merge conflicts '
-                                '(see hg help resolve)'))
-        nodenew = _relocatecommit(repo, orig, commitmsg)
-    except error.Abort as exc:
-        with repo.dirstate.parentchange():
-            repo.setparents(repo['.'].node(), nullid)
-            repo.dirstate.write(tr)
-            # fix up dirstate for copies and renames
-            compat.duplicatecopies(repo, repo[None], dest.rev(), orig.p1().rev())
-
-        class LocalMergeFailure(MergeFailure, exc.__class__):
-            pass
-        exc.__class__ = LocalMergeFailure
-        tr.close() # to keep changes in this transaction (e.g. dirstate)
-        raise
-    _finalizerelocate(repo, orig, dest, nodenew, tr)
-    return nodenew
-
 ### new command
 #############################
 
@@ -1038,1009 +947,6 @@
     _deprecatealias('gup', 'next')
     _deprecatealias('gdown', 'previous')
 
-def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category):
-    """Resolve the troubles affecting one revision"""
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction("evolve")
-        if 'orphan' == category:
-            result = _solveunstable(ui, repo, ctx, dryrun, confirm, progresscb)
-        elif 'phasedivergent' == category:
-            result = _solvebumped(ui, repo, ctx, dryrun, confirm, progresscb)
-        elif 'contentdivergent' == category:
-            result = _solvedivergent(ui, repo, ctx, dryrun, confirm,
-                                     progresscb)
-        else:
-            assert False, "unknown trouble category: %s" % (category)
-        tr.close()
-        return result
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-def _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat):
-    """Used by the evolve function to display an error message when
-    no troubles can be resolved"""
-    troublecategories = ['phasedivergent', 'contentdivergent', 'orphan']
-    unselectedcategories = [c for c in troublecategories if c != targetcat]
-    msg = None
-    hint = None
-
-    troubled = {
-        "orphan": repo.revs("orphan()"),
-        "contentdivergent": repo.revs("contentdivergent()"),
-        "phasedivergent": repo.revs("phasedivergent()"),
-        "all": repo.revs("troubled()"),
-    }
-
-    hintmap = {
-        'phasedivergent': _("do you want to use --phase-divergent"),
-        'phasedivergent+contentdivergent': _("do you want to use "
-                                             "--phase-divergent or"
-                                             " --content-divergent"),
-        'phasedivergent+orphan': _("do you want to use --phase-divergent"
-                                   " or --orphan"),
-        'contentdivergent': _("do you want to use --content-divergent"),
-        'contentdivergent+orphan': _("do you want to use --content-divergent"
-                                     " or --orphan"),
-        'orphan': _("do you want to use --orphan"),
-        'any+phasedivergent': _("do you want to use --any (or --rev) and"
-                                " --phase-divergent"),
-        'any+phasedivergent+contentdivergent': _("do you want to use --any"
-                                                 " (or --rev) and"
-                                                 " --phase-divergent or"
-                                                 " --content-divergent"),
-        'any+phasedivergent+orphan': _("do you want to use --any (or --rev)"
-                                       " and --phase-divergent or --orphan"),
-        'any+contentdivergent': _("do you want to use --any (or --rev) and"
-                                  " --content-divergent"),
-        'any+contentdivergent+orphan': _("do you want to use --any (or --rev)"
-                                         " and --content-divergent or "
-                                         "--orphan"),
-        'any+orphan': _("do you want to use --any (or --rev)"
-                        "and --orphan"),
-    }
-
-    if revopt:
-        revs = scmutil.revrange(repo, revopt)
-        if not revs:
-            msg = _("set of specified revisions is empty")
-        else:
-            msg = _("no %s changesets in specified revisions") % targetcat
-            othertroubles = []
-            for cat in unselectedcategories:
-                if revs & troubled[cat]:
-                    othertroubles.append(cat)
-            if othertroubles:
-                hint = hintmap['+'.join(othertroubles)]
-
-    elif anyopt:
-        msg = _("no %s changesets to evolve") % targetcat
-        othertroubles = []
-        for cat in unselectedcategories:
-            if troubled[cat]:
-                othertroubles.append(cat)
-        if othertroubles:
-            hint = hintmap['+'.join(othertroubles)]
-
-    else:
-        # evolve without any option = relative to the current wdir
-        if targetcat == 'orphan':
-            msg = _("nothing to evolve on current working copy parent")
-        else:
-            msg = _("current working copy parent is not %s") % targetcat
-
-        p1 = repo['.'].rev()
-        othertroubles = []
-        for cat in unselectedcategories:
-            if p1 in troubled[cat]:
-                othertroubles.append(cat)
-        if othertroubles:
-            hint = hintmap['+'.join(othertroubles)]
-        else:
-            length = len(troubled[targetcat])
-            if length:
-                hint = _("%d other %s in the repository, do you want --any "
-                         "or --rev") % (length, targetcat)
-            else:
-                othertroubles = []
-                for cat in unselectedcategories:
-                    if troubled[cat]:
-                        othertroubles.append(cat)
-                if othertroubles:
-                    hint = hintmap['any+' + ('+'.join(othertroubles))]
-                else:
-                    msg = _("no troubled changesets")
-
-    assert msg is not None
-    ui.write_err("%s\n" % msg)
-    if hint:
-        ui.write_err("(%s)\n" % hint)
-        return 2
-    else:
-        return 1
-
-def _cleanup(ui, repo, startnode, showprogress):
-    if showprogress:
-        ui.progress(_('evolve'), None)
-    if repo['.'] != startnode:
-        ui.status(_('working directory is now at %s\n') % repo['.'])
-
-class MultipleSuccessorsError(RuntimeError):
-    """Exception raised by _singlesuccessor when multiple successor sets exists
-
-    The object contains the list of successorssets in its 'successorssets'
-    attribute to call to easily recover.
-    """
-
-    def __init__(self, successorssets):
-        self.successorssets = successorssets
-
-def _singlesuccessor(repo, p):
-    """returns p (as rev) if not obsolete or its unique latest successors
-
-    fail if there are no such successor"""
-
-    if not p.obsolete():
-        return p.rev()
-    obs = repo[p]
-    ui = repo.ui
-    newer = compat.successorssets(repo, obs.node())
-    # search of a parent which is not killed
-    while not newer:
-        ui.debug("stabilize target %s is plain dead,"
-                 " trying to stabilize on its parent\n" %
-                 obs)
-        obs = obs.parents()[0]
-        newer = compat.successorssets(repo, obs.node())
-    if len(newer) > 1 or len(newer[0]) > 1:
-        raise MultipleSuccessorsError(newer)
-
-    return repo[newer[0][0]].rev()
-
-def builddependencies(repo, revs):
-    """returns dependency graphs giving an order to solve instability of revs
-    (see _orderrevs for more information on usage)"""
-
-    # For each troubled revision we keep track of what instability if any should
-    # be resolved in order to resolve it. Example:
-    # dependencies = {3: [6], 6:[]}
-    # Means that: 6 has no dependency, 3 depends on 6 to be solved
-    dependencies = {}
-    # rdependencies is the inverted dict of dependencies
-    rdependencies = collections.defaultdict(set)
-
-    for r in revs:
-        dependencies[r] = set()
-        for p in repo[r].parents():
-            try:
-                succ = _singlesuccessor(repo, p)
-            except MultipleSuccessorsError as exc:
-                dependencies[r] = exc.successorssets
-                continue
-            if succ in revs:
-                dependencies[r].add(succ)
-                rdependencies[succ].add(r)
-    return dependencies, rdependencies
-
-def _dedupedivergents(repo, revs):
-    """Dedupe the divergents revs in revs to get one from each group with the
-    lowest revision numbers
-    """
-    repo = repo.unfiltered()
-    res = set()
-    # To not reevaluate divergents of the same group once one is encountered
-    discarded = set()
-    for rev in revs:
-        if rev in discarded:
-            continue
-        divergent = repo[rev]
-        base, others = divergentdata(divergent)
-        othersrevs = [o.rev() for o in others]
-        res.add(min([divergent.rev()] + othersrevs))
-        discarded.update(othersrevs)
-    return res
-
-instabilities_map = {
-    'contentdivergent': "content-divergent",
-    'phasedivergent': "phase-divergent"
-}
-
-def _selectrevs(repo, allopt, revopt, anyopt, targetcat):
-    """select troubles in repo matching according to given options"""
-    revs = set()
-    if allopt or revopt:
-        revs = repo.revs("%s()" % targetcat)
-        if revopt:
-            revs = scmutil.revrange(repo, revopt) & revs
-        elif not anyopt:
-            topic = getattr(repo, 'currenttopic', '')
-            if topic:
-                revs = repo.revs('topic(%s)', topic) & revs
-            elif targetcat == 'orphan':
-                revs = _aspiringdescendant(repo,
-                                           repo.revs('(.::) - obsolete()::'))
-                revs = set(revs)
-        if targetcat == 'contentdivergent':
-            # Pick one divergent per group of divergents
-            revs = _dedupedivergents(repo, revs)
-    elif anyopt:
-        revs = repo.revs('first(%s())' % (targetcat))
-    elif targetcat == 'orphan':
-        revs = set(_aspiringchildren(repo, repo.revs('(.::) - obsolete()::')))
-        if 1 < len(revs):
-            msg = "multiple evolve candidates"
-            hint = (_("select one of %s with --rev")
-                    % ', '.join([str(repo[r]) for r in sorted(revs)]))
-            raise error.Abort(msg, hint=hint)
-    elif instabilities_map.get(targetcat, targetcat) in repo['.'].instabilities():
-        revs = set([repo['.'].rev()])
-    return revs
-
-
-def _orderrevs(repo, revs):
-    """Compute an ordering to solve instability for the given revs
-
-    revs is a list of unstable revisions.
-
-    Returns the same revisions ordered to solve their instability from the
-    bottom to the top of the stack that the stabilization process will produce
-    eventually.
-
-    This ensures the minimal number of stabilizations, as we can stabilize each
-    revision on its final stabilized destination.
-    """
-    # Step 1: Build the dependency graph
-    dependencies, rdependencies = builddependencies(repo, revs)
-    # Step 2: Build the ordering
-    # Remove the revisions with no dependency(A) and add them to the ordering.
-    # Removing these revisions leads to new revisions with no dependency (the
-    # one depending on A) that we can remove from the dependency graph and add
-    # to the ordering. We progress in a similar fashion until the ordering is
-    # built
-    solvablerevs = collections.deque([r for r in sorted(dependencies.keys())
-                                      if not dependencies[r]])
-    ordering = []
-    while solvablerevs:
-        rev = solvablerevs.popleft()
-        for dependent in rdependencies[rev]:
-            dependencies[dependent].remove(rev)
-            if not dependencies[dependent]:
-                solvablerevs.append(dependent)
-        del dependencies[rev]
-        ordering.append(rev)
-
-    ordering.extend(sorted(dependencies))
-    return ordering
-
-def divergentsets(repo, ctx):
-    """Compute sets of commits divergent with a given one"""
-    cache = {}
-    base = {}
-    for n in compat.allprecursors(repo.obsstore, [ctx.node()]):
-        if n == ctx.node():
-            # a node can't be a base for divergence with itself
-            continue
-        nsuccsets = compat.successorssets(repo, n, cache)
-        for nsuccset in nsuccsets:
-            if ctx.node() in nsuccset:
-                # we are only interested in *other* successor sets
-                continue
-            if tuple(nsuccset) in base:
-                # we already know the latest base for this divergency
-                continue
-            base[tuple(nsuccset)] = n
-    divergence = []
-    for divset, b in base.iteritems():
-        divergence.append({
-            'divergentnodes': divset,
-            'commonprecursor': b
-        })
-
-    return divergence
-
-def _preparelistctxs(items, condition):
-    return [item.hex() for item in items if condition(item)]
-
-def _formatctx(fm, ctx):
-    fm.data(node=ctx.hex())
-    fm.data(desc=ctx.description())
-    fm.data(date=ctx.date())
-    fm.data(user=ctx.user())
-
-def listtroubles(ui, repo, troublecategories, **opts):
-    """Print all the troubles for the repo (or given revset)"""
-    troublecategories = troublecategories or ['contentdivergent', 'orphan', 'phasedivergent']
-    showunstable = 'orphan' in troublecategories
-    showbumped = 'phasedivergent' in troublecategories
-    showdivergent = 'contentdivergent' in troublecategories
-
-    revs = repo.revs('+'.join("%s()" % t for t in troublecategories))
-    if opts.get('rev'):
-        revs = scmutil.revrange(repo, opts.get('rev'))
-
-    fm = ui.formatter('evolvelist', opts)
-    for rev in revs:
-        ctx = repo[rev]
-        unpars = _preparelistctxs(ctx.parents(), lambda p: p.orphan())
-        obspars = _preparelistctxs(ctx.parents(), lambda p: p.obsolete())
-        imprecs = _preparelistctxs(repo.set("allprecursors(%n)", ctx.node()),
-                                   lambda p: not p.mutable())
-        dsets = divergentsets(repo, ctx)
-
-        fm.startitem()
-        # plain formatter section
-        hashlen, desclen = 12, 60
-        desc = ctx.description()
-        if desc:
-            desc = desc.splitlines()[0]
-        desc = (desc[:desclen] + '...') if len(desc) > desclen else desc
-        fm.plain('%s: ' % ctx.hex()[:hashlen])
-        fm.plain('%s\n' % desc)
-        fm.data(node=ctx.hex(), rev=ctx.rev(), desc=desc, phase=ctx.phasestr())
-
-        for unpar in unpars if showunstable else []:
-            fm.plain('  %s: %s (%s parent)\n' % (TROUBLES['ORPHAN'],
-                                                 unpar[:hashlen],
-                                                 TROUBLES['ORPHAN']))
-        for obspar in obspars if showunstable else []:
-            fm.plain('  %s: %s (obsolete parent)\n' % (TROUBLES['ORPHAN'],
-                                                       obspar[:hashlen]))
-        for imprec in imprecs if showbumped else []:
-            fm.plain('  %s: %s (immutable precursor)\n' %
-                     (TROUBLES['PHASEDIVERGENT'], imprec[:hashlen]))
-
-        if dsets and showdivergent:
-            for dset in dsets:
-                fm.plain('  %s: ' % TROUBLES['CONTENTDIVERGENT'])
-                first = True
-                for n in dset['divergentnodes']:
-                    t = "%s (%s)" if first else " %s (%s)"
-                    first = False
-                    fm.plain(t % (node.hex(n)[:hashlen], repo[n].phasestr()))
-                comprec = node.hex(dset['commonprecursor'])[:hashlen]
-                fm.plain(" (precursor %s)\n" % comprec)
-        fm.plain("\n")
-
-        # templater-friendly section
-        _formatctx(fm, ctx)
-        troubles = []
-        for unpar in unpars:
-            troubles.append({'troubletype': TROUBLES['ORPHAN'],
-                             'sourcenode': unpar, 'sourcetype': 'orphanparent'})
-        for obspar in obspars:
-            troubles.append({'troubletype': TROUBLES['ORPHAN'],
-                             'sourcenode': obspar,
-                             'sourcetype': 'obsoleteparent'})
-        for imprec in imprecs:
-            troubles.append({'troubletype': TROUBLES['PHASEDIVERGENT'],
-                             'sourcenode': imprec,
-                             'sourcetype': 'immutableprecursor'})
-        for dset in dsets:
-            divnodes = [{'node': node.hex(n),
-                         'phase': repo[n].phasestr(),
-                        } for n in dset['divergentnodes']]
-            troubles.append({'troubletype': TROUBLES['CONTENTDIVERGENT'],
-                             'commonprecursor': node.hex(dset['commonprecursor']),
-                             'divergentnodes': divnodes})
-        fm.data(troubles=troubles)
-
-    fm.end()
-
-@eh.command(
-    '^evolve|stabilize|solve',
-    [('n', 'dry-run', False,
-      _('do not perform actions, just print what would be done')),
-     ('', 'confirm', False,
-      _('ask for confirmation before performing the action')),
-     ('A', 'any', False,
-      _('also consider troubled changesets unrelated to current working '
-        'directory')),
-     ('r', 'rev', [], _('solves troubles of these revisions')),
-     ('', 'bumped', False, _('solves only bumped changesets')),
-     ('', 'phase-divergent', False, _('solves only phase-divergent changesets')),
-     ('', 'divergent', False, _('solves only divergent changesets')),
-     ('', 'content-divergent', False, _('solves only content-divergent changesets')),
-     ('', 'unstable', False, _('solves only unstable changesets')),
-     ('', 'orphan', False, _('solves only orphan changesets (default)')),
-     ('a', 'all', False, _('evolve all troubled changesets related to the '
-                           'current  working directory and its descendants')),
-     ('c', 'continue', False, _('continue an interrupted evolution')),
-     ('l', 'list', False, 'provide details on troubled changesets in the repo'),
-    ] + mergetoolopts,
-    _('[OPTIONS]...')
-)
-def evolve(ui, repo, **opts):
-    """solve troubled changesets in your repository
-
-    Modifying history can lead to various types of troubled changesets:
-    unstable, bumped, or divergent. The evolve command resolves your troubles
-    by executing one of the following actions:
-
-    - update working copy to a successor
-    - rebase an unstable changeset
-    - extract the desired changes from a bumped changeset
-    - fuse divergent changesets back together
-
-    If you pass no arguments, evolve works in automatic mode: it will execute a
-    single action to reduce instability related to your working copy. There are
-    two cases for this action. First, if the parent of your working copy is
-    obsolete, evolve updates to the parent's successor. Second, if the working
-    copy parent is not obsolete but has obsolete predecessors, then evolve
-    determines if there is an unstable changeset that can be rebased onto the
-    working copy parent in order to reduce instability.
-    If so, evolve rebases that changeset. If not, evolve refuses to guess your
-    intention, and gives a hint about what you might want to do next.
-
-    Any time evolve creates a changeset, it updates the working copy to the new
-    changeset. (Currently, every successful evolve operation involves an update
-    as well; this may change in future.)
-
-    Automatic mode only handles common use cases. For example, it avoids taking
-    action in the case of ambiguity, and it ignores unstable changesets that
-    are not related to your working copy.
-    It also refuses to solve bumped or divergent changesets unless you
-    explicitly request such behavior (see below).
-
-    Eliminating all instability around your working copy may require multiple
-    invocations of :hg:`evolve`. Alternately, use ``--all`` to recursively
-    select and evolve all unstable changesets that can be rebased onto the
-    working copy parent.
-    This is more powerful than successive invocations, since ``--all`` handles
-    ambiguous cases (e.g. unstable changesets with multiple children) by
-    evolving all branches.
-
-    When your repository cannot be handled by automatic mode, you might need to
-    use ``--rev`` to specify a changeset to evolve. For example, if you have
-    an unstable changeset that is not related to the working copy parent,
-    you could use ``--rev`` to evolve it. Or, if some changeset has multiple
-    unstable children, evolve in automatic mode refuses to guess which one to
-    evolve; you have to use ``--rev`` in that case.
-
-    Alternately, ``--any`` makes evolve search for the next evolvable changeset
-    regardless of whether it is related to the working copy parent.
-
-    You can supply multiple revisions to evolve multiple troubled changesets
-    in a single invocation. In revset terms, ``--any`` is equivalent to ``--rev
-    first(unstable())``. ``--rev`` and ``--all`` are mutually exclusive, as are
-    ``--rev`` and ``--any``.
-
-    ``hg evolve --any --all`` is useful for cleaning up instability across all
-    branches, letting evolve figure out the appropriate order and destination.
-
-    When you have troubled changesets that are not unstable, :hg:`evolve`
-    refuses to consider them unless you specify the category of trouble you
-    wish to resolve, with ``--bumped`` or ``--divergent``. These options are
-    currently mutually exclusive with each other and with ``--unstable``
-    (the default). You can combine ``--bumped`` or ``--divergent`` with
-    ``--rev``, ``--all``, or ``--any``.
-
-    You can also use the evolve command to list the troubles affecting your
-    repository by using the --list flag. You can choose to display only some
-    categories of troubles with the --unstable, --divergent or --bumped flags.
-    """
-
-    opts = _checkevolveopts(repo, opts)
-    # Options
-    contopt = opts['continue']
-    anyopt = opts['any']
-    allopt = opts['all']
-    startnode = repo['.']
-    dryrunopt = opts['dry_run']
-    confirmopt = opts['confirm']
-    revopt = opts['rev']
-
-    troublecategories = ['phase_divergent', 'content_divergent', 'orphan']
-    specifiedcategories = [t.replace('_', '')
-                           for t in troublecategories
-                           if opts[t]]
-    if opts['list']:
-        compat.startpager(ui, 'evolve')
-        listtroubles(ui, repo, specifiedcategories, **opts)
-        return
-
-    targetcat = 'orphan'
-    if 1 < len(specifiedcategories):
-        msg = _('cannot specify more than one trouble category to solve (yet)')
-        raise error.Abort(msg)
-    elif len(specifiedcategories) == 1:
-        targetcat = specifiedcategories[0]
-    elif repo['.'].obsolete():
-        displayer = cmdutil.show_changeset(ui, repo,
-                                           {'template': shorttemplate})
-        # no args and parent is obsolete, update to successors
-        try:
-            ctx = repo[_singlesuccessor(repo, repo['.'])]
-        except MultipleSuccessorsError as exc:
-            repo.ui.write_err('parent is obsolete with multiple successors:\n')
-            for ln in exc.successorssets:
-                for n in ln:
-                    displayer.show(repo[n])
-            return 2
-
-        ui.status(_('update:'))
-        if not ui.quiet:
-            displayer.show(ctx)
-
-        if dryrunopt:
-            return 0
-        res = hg.update(repo, ctx.rev())
-        if ctx != startnode:
-            ui.status(_('working directory is now at %s\n') % ctx)
-        return res
-
-    ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'evolve')
-    troubled = set(repo.revs('troubled()'))
-
-    # Progress handling
-    seen = 1
-    count = allopt and len(troubled) or 1
-    showprogress = allopt
-
-    def progresscb():
-        if revopt or allopt:
-            ui.progress(_('evolve'), seen, unit=_('changesets'), total=count)
-
-    # Continuation handling
-    if contopt:
-        state = evolvestate.evolvestate(repo)
-        if not state:
-            raise error.Abort('no evolve to continue')
-        state.load()
-        orig = repo[state['current']]
-        with repo.wlock(), repo.lock():
-            ctx = orig
-            source = ctx.extra().get('source')
-            extra = {}
-            if source:
-                extra['source'] = source
-                extra['intermediate-source'] = ctx.hex()
-            else:
-                extra['source'] = ctx.hex()
-            user = ctx.user()
-            date = ctx.date()
-            message = ctx.description()
-            ui.status(_('evolving %d:%s "%s"\n') % (ctx.rev(), ctx,
-                                                    message.split('\n', 1)[0]))
-            targetphase = max(ctx.phase(), phases.draft)
-            overrides = {('phases', 'new-commit'): targetphase}
-
-            with repo.ui.configoverride(overrides, 'evolve-continue'):
-                node = repo.commit(text=message, user=user,
-                                   date=date, extra=extra)
-
-            obsolete.createmarkers(repo, [(ctx, (repo[node],))])
-            state.delete()
-            return
-
-    cmdutil.bailifchanged(repo)
-
-    revs = _selectrevs(repo, allopt, revopt, anyopt, targetcat)
-
-    if not revs:
-        return _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat)
-
-    # For the progress bar to show
-    count = len(revs)
-    # Order the revisions
-    if targetcat == 'orphan':
-        revs = _orderrevs(repo, revs)
-    for rev in revs:
-        progresscb()
-        _solveone(ui, repo, repo[rev], dryrunopt, confirmopt,
-                  progresscb, targetcat)
-        seen += 1
-    progresscb()
-    _cleanup(ui, repo, startnode, showprogress)
-
-def _checkevolveopts(repo, opts):
-    """ check the options passed to `hg evolve` and warn for deprecation warning
-    if any """
-
-    if opts['continue']:
-        if opts['any']:
-            raise error.Abort('cannot specify both "--any" and "--continue"')
-        if opts['all']:
-            raise error.Abort('cannot specify both "--all" and "--continue"')
-
-    if opts['rev']:
-        if opts['any']:
-            raise error.Abort('cannot specify both "--rev" and "--any"')
-        if opts['all']:
-            raise error.Abort('cannot specify both "--rev" and "--all"')
-
-    # Backward compatibility
-    if opts['unstable']:
-        msg = ("'evolve --unstable' is deprecated, "
-               "use 'evolve --orphan'")
-        repo.ui.deprecwarn(msg, '4.4')
-
-        opts['orphan'] = opts['divergent']
-
-    if opts['divergent']:
-        msg = ("'evolve --divergent' is deprecated, "
-               "use 'evolve --content-divergent'")
-        repo.ui.deprecwarn(msg, '4.4')
-
-        opts['content_divergent'] = opts['divergent']
-
-    if opts['bumped']:
-        msg = ("'evolve --bumped' is deprecated, "
-               "use 'evolve --phase-divergent'")
-        repo.ui.deprecwarn(msg, '4.4')
-
-        opts['phase_divergent'] = opts['bumped']
-
-    return opts
-
-def _possibledestination(repo, rev):
-    """return all changesets that may be a new parent for REV"""
-    tonode = repo.changelog.node
-    parents = repo.changelog.parentrevs
-    torev = repo.changelog.rev
-    dest = set()
-    tovisit = list(parents(rev))
-    while tovisit:
-        r = tovisit.pop()
-        succsets = compat.successorssets(repo, tonode(r))
-        if not succsets:
-            tovisit.extend(parents(r))
-        else:
-            # We should probably pick only one destination from split
-            # (case where '1 < len(ss)'), This could be the currently tipmost
-            # but logic is less clear when result of the split are now on
-            # multiple branches.
-            for ss in succsets:
-                for n in ss:
-                    dest.add(torev(n))
-    return dest
-
-def _aspiringchildren(repo, revs):
-    """Return a list of changectx which can be stabilized on top of pctx or
-    one of its descendants. Empty list if none can be found."""
-    target = set(revs)
-    result = []
-    for r in repo.revs('orphan() - %ld', revs):
-        dest = _possibledestination(repo, r)
-        if target & dest:
-            result.append(r)
-    return result
-
-def _aspiringdescendant(repo, revs):
-    """Return a list of changectx which can be stabilized on top of pctx or
-    one of its descendants recursively. Empty list if none can be found."""
-    target = set(revs)
-    result = set(target)
-    paths = collections.defaultdict(set)
-    for r in repo.revs('orphan() - %ld', revs):
-        for d in _possibledestination(repo, r):
-            paths[d].add(r)
-
-    result = set(target)
-    tovisit = list(revs)
-    while tovisit:
-        base = tovisit.pop()
-        for unstable in paths[base]:
-            if unstable not in result:
-                tovisit.append(unstable)
-                result.add(unstable)
-    return sorted(result - target)
-
-def _solveunstable(ui, repo, orig, dryrun=False, confirm=False,
-                   progresscb=None):
-    """Stabilize an unstable changeset"""
-    pctx = orig.p1()
-    keepbranch = orig.p1().branch() != orig.branch()
-    if len(orig.parents()) == 2:
-        if not pctx.obsolete():
-            pctx = orig.p2()  # second parent is obsolete ?
-            keepbranch = orig.p2().branch() != orig.branch()
-        elif orig.p2().obsolete():
-            hint = _("Redo the merge (%s) and use `hg prune <old> "
-                     "--succ <new>` to obsolete the old one") % orig.hex()[:12]
-            ui.warn(_("warning: no support for evolving merge changesets "
-                      "with two obsolete parents yet\n") +
-                    _("(%s)\n") % hint)
-            return False
-
-    if not pctx.obsolete():
-        ui.warn(_("cannot solve instability of %s, skipping\n") % orig)
-        return False
-    obs = pctx
-    newer = compat.successorssets(repo, obs.node())
-    # search of a parent which is not killed
-    while not newer or newer == [()]:
-        ui.debug("stabilize target %s is plain dead,"
-                 " trying to stabilize on its parent\n" %
-                 obs)
-        obs = obs.parents()[0]
-        newer = compat.successorssets(repo, obs.node())
-    if len(newer) > 1:
-        msg = _("skipping %s: divergent rewriting. can't choose "
-                "destination\n") % obs
-        ui.write_err(msg)
-        return 2
-    targets = newer[0]
-    assert targets
-    if len(targets) > 1:
-        # split target, figure out which one to pick, are they all in line?
-        targetrevs = [repo[r].rev() for r in targets]
-        roots = repo.revs('roots(%ld)', targetrevs)
-        heads = repo.revs('heads(%ld)', targetrevs)
-        if len(roots) > 1 or len(heads) > 1:
-            msg = "cannot solve split across two branches\n"
-            ui.write_err(msg)
-            return 2
-        target = repo[heads.first()]
-    else:
-        target = targets[0]
-    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    target = repo[target]
-    if not ui.quiet or confirm:
-        repo.ui.write(_('move:'))
-        displayer.show(orig)
-        repo.ui.write(_('atop:'))
-        displayer.show(target)
-    if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
-            raise error.Abort(_('evolve aborted by user'))
-    if progresscb:
-        progresscb()
-    todo = 'hg rebase -r %s -d %s\n' % (orig, target)
-    if dryrun:
-        repo.ui.write(todo)
-    else:
-        repo.ui.note(todo)
-        if progresscb:
-            progresscb()
-        try:
-            relocate(repo, orig, target, pctx, keepbranch)
-        except MergeFailure:
-            ops = {'current': orig.node()}
-            state = evolvestate.evolvestate(repo, opts=ops)
-            state.save()
-            repo.ui.write_err(_('evolve failed!\n'))
-            repo.ui.write_err(
-                _("fix conflict and run 'hg evolve --continue'"
-                  " or use 'hg update -C .' to abort\n"))
-            raise
-
-def _solvebumped(ui, repo, bumped, dryrun=False, confirm=False,
-                 progresscb=None):
-    """Stabilize a bumped changeset"""
-    repo = repo.unfiltered()
-    bumped = repo[bumped.rev()]
-    # For now we deny bumped merge
-    if len(bumped.parents()) > 1:
-        msg = _('skipping %s : we do not handle merge yet\n') % bumped
-        ui.write_err(msg)
-        return 2
-    prec = repo.set('last(allprecursors(%d) and public())', bumped).next()
-    # For now we deny target merge
-    if len(prec.parents()) > 1:
-        msg = _('skipping: %s: public version is a merge, '
-                'this is not handled yet\n') % prec
-        ui.write_err(msg)
-        return 2
-
-    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    if not ui.quiet or confirm:
-        repo.ui.write(_('recreate:'))
-        displayer.show(bumped)
-        repo.ui.write(_('atop:'))
-        displayer.show(prec)
-    if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
-        raise error.Abort(_('evolve aborted by user'))
-    if dryrun:
-        todo = 'hg rebase --rev %s --dest %s;\n' % (bumped, prec.p1())
-        repo.ui.write(todo)
-        repo.ui.write(('hg update %s;\n' % prec))
-        repo.ui.write(('hg revert --all --rev %s;\n' % bumped))
-        repo.ui.write(('hg commit --msg "%s update to %s"\n' %
-                       (TROUBLES['PHASEDIVERGENT'], bumped)))
-        return 0
-    if progresscb:
-        progresscb()
-    newid = tmpctx = None
-    tmpctx = bumped
-    # Basic check for common parent. Far too complicated and fragile
-    tr = repo.currenttransaction()
-    assert tr is not None
-    bmupdate = _bookmarksupdater(repo, bumped.node(), tr)
-    if not list(repo.set('parents(%d) and parents(%d)', bumped, prec)):
-        # Need to rebase the changeset at the right place
-        repo.ui.status(
-            _('rebasing to destination parent: %s\n') % prec.p1())
-        try:
-            tmpid = relocate(repo, bumped, prec.p1())
-            if tmpid is not None:
-                tmpctx = repo[tmpid]
-                obsolete.createmarkers(repo, [(bumped, (tmpctx,))])
-        except MergeFailure:
-            repo.vfs.write('graftstate', bumped.hex() + '\n')
-            repo.ui.write_err(_('evolution failed!\n'))
-            msg = _("fix conflict and run 'hg evolve --continue'\n")
-            repo.ui.write_err(msg)
-            raise
-    # Create the new commit context
-    repo.ui.status(_('computing new diff\n'))
-    files = set()
-    copied = copies.pathcopies(prec, bumped)
-    precmanifest = prec.manifest().copy()
-    # 3.3.2 needs a list.
-    # future 3.4 don't detect the size change during iteration
-    # this is fishy
-    for key, val in list(bumped.manifest().iteritems()):
-        precvalue = precmanifest.get(key, None)
-        if precvalue is not None:
-            del precmanifest[key]
-        if precvalue != val:
-            files.add(key)
-    files.update(precmanifest)  # add missing files
-    # commit it
-    if files: # something to commit!
-        def filectxfn(repo, ctx, path):
-            if path in bumped:
-                fctx = bumped[path]
-                flags = fctx.flags()
-                mctx = compat.memfilectx(repo, ctx, fctx, flags, copied, path)
-                return mctx
-            return None
-        text = '%s update to %s:\n\n' % (TROUBLES['PHASEDIVERGENT'], prec)
-        text += bumped.description()
-
-        new = context.memctx(repo,
-                             parents=[prec.node(), node.nullid],
-                             text=text,
-                             files=files,
-                             filectxfn=filectxfn,
-                             user=bumped.user(),
-                             date=bumped.date(),
-                             extra=bumped.extra())
-
-        newid = repo.commitctx(new)
-    if newid is None:
-        obsolete.createmarkers(repo, [(tmpctx, ())])
-        newid = prec.node()
-    else:
-        phases.retractboundary(repo, tr, bumped.phase(), [newid])
-        obsolete.createmarkers(repo, [(tmpctx, (repo[newid],))],
-                               flag=obsolete.bumpedfix)
-    bmupdate(newid)
-    repo.ui.status(_('committed as %s\n') % node.short(newid))
-    # reroute the working copy parent to the new changeset
-    with repo.dirstate.parentchange():
-        repo.dirstate.setparents(newid, node.nullid)
-
-def _solvedivergent(ui, repo, divergent, dryrun=False, confirm=False,
-                    progresscb=None):
-    repo = repo.unfiltered()
-    divergent = repo[divergent.rev()]
-    base, others = divergentdata(divergent)
-    if len(others) > 1:
-        othersstr = "[%s]" % (','.join([str(i) for i in others]))
-        msg = _("skipping %d:%s with a changeset that got split"
-                " into multiple ones:\n"
-                "|[%s]\n"
-                "| This is not handled by automatic evolution yet\n"
-                "| You have to fallback to manual handling with commands "
-                "such as:\n"
-                "| - hg touch -D\n"
-                "| - hg prune\n"
-                "| \n"
-                "| You should contact your local evolution Guru for help.\n"
-                ) % (divergent, TROUBLES['CONTENTDIVERGENT'], othersstr)
-        ui.write_err(msg)
-        return 2
-    other = others[0]
-    if len(other.parents()) > 1:
-        msg = _("skipping %s: %s changeset can't be "
-                "a merge (yet)\n") % (divergent, TROUBLES['CONTENTDIVERGENT'])
-        ui.write_err(msg)
-        hint = _("You have to fallback to solving this by hand...\n"
-                 "| This probably means redoing the merge and using \n"
-                 "| `hg prune` to kill older version.\n")
-        ui.write_err(hint)
-        return 2
-    if other.p1() not in divergent.parents():
-        msg = _("skipping %s: have a different parent than %s "
-                "(not handled yet)\n") % (divergent, other)
-        hint = _("| %(d)s, %(o)s are not based on the same changeset.\n"
-                 "| With the current state of its implementation, \n"
-                 "| evolve does not work in that case.\n"
-                 "| rebase one of them next to the other and run \n"
-                 "| this command again.\n"
-                 "| - either: hg rebase --dest 'p1(%(d)s)' -r %(o)s\n"
-                 "| - or:     hg rebase --dest 'p1(%(o)s)' -r %(d)s\n"
-                 ) % {'d': divergent, 'o': other}
-        ui.write_err(msg)
-        ui.write_err(hint)
-        return 2
-
-    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    if not ui.quiet or confirm:
-        ui.write(_('merge:'))
-        displayer.show(divergent)
-        ui.write(_('with: '))
-        displayer.show(other)
-        ui.write(_('base: '))
-        displayer.show(base)
-    if confirm and ui.prompt(_('perform evolve? [Ny]'), 'n') != 'y':
-        raise error.Abort(_('evolve aborted by user'))
-    if dryrun:
-        ui.write(('hg update -c %s &&\n' % divergent))
-        ui.write(('hg merge %s &&\n' % other))
-        ui.write(('hg commit -m "auto merge resolving conflict between '
-                 '%s and %s"&&\n' % (divergent, other)))
-        ui.write(('hg up -C %s &&\n' % base))
-        ui.write(('hg revert --all --rev tip &&\n'))
-        ui.write(('hg commit -m "`hg log -r %s --template={desc}`";\n'
-                 % divergent))
-        return
-    if divergent not in repo[None].parents():
-        repo.ui.status(_('updating to "local" conflict\n'))
-        hg.update(repo, divergent.rev())
-    repo.ui.note(_('merging %s changeset\n') % TROUBLES['CONTENTDIVERGENT'])
-    if progresscb:
-        progresscb()
-    stats = merge.update(repo,
-                         other.node(),
-                         branchmerge=True,
-                         force=False,
-                         ancestor=base.node(),
-                         mergeancestor=True)
-    hg._showstats(repo, stats)
-    if stats[3]:
-        repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
-                         "or 'hg update -C .' to abort\n"))
-    if stats[3] > 0:
-        raise error.Abort('merge conflict between several amendments '
-                          '(this is not automated yet)',
-                          hint="""/!\ You can try:
-/!\ * manual merge + resolve => new cset X
-/!\ * hg up to the parent of the amended changeset (which are named W and Z)
-/!\ * hg revert --all -r X
-/!\ * hg ci -m "same message as the amended changeset" => new cset Y
-/!\ * hg prune -n Y W Z
-""")
-    if progresscb:
-        progresscb()
-    emtpycommitallowed = repo.ui.backupconfig('ui', 'allowemptycommit')
-    tr = repo.currenttransaction()
-    assert tr is not None
-    try:
-        repo.ui.setconfig('ui', 'allowemptycommit', True, 'evolve')
-        with repo.dirstate.parentchange():
-            repo.dirstate.setparents(divergent.node(), node.nullid)
-        oldlen = len(repo)
-        cmdrewrite.amend(ui, repo, message='', logfile='')
-        if oldlen == len(repo):
-            new = divergent
-            # no changes
-        else:
-            new = repo['.']
-        obsolete.createmarkers(repo, [(other, (new,))])
-        phases.retractboundary(repo, tr, other.phase(), [new.node()])
-    finally:
-        repo.ui.restoreconfig(emtpycommitallowed)
-
-def divergentdata(ctx):
-    """return base, other part of a conflict
-
-    This only return the first one.
-
-    XXX this woobly function won't survive XXX
-    """
-    repo = ctx._repo.unfiltered()
-    for base in repo.set('reverse(allprecursors(%d))', ctx):
-        newer = compat.successorssets(ctx._repo, base.node())
-        # drop filter and solution including the original ctx
-        newer = [n for n in newer if n and ctx.node() not in n]
-        if newer:
-            return base, tuple(ctx._repo[o] for o in newer[0])
-    raise error.Abort("base of divergent changeset %s not found" % ctx,
-                      hint='this case is not yet handled')
-
 def _gettopic(ctx):
     """handle topic fetching with or without the extension"""
     return getattr(ctx, 'topic', lambda: '')()
@@ -2054,10 +960,10 @@
 
 def _prevupdate(repo, displayer, target, bookmark, dryrun):
     if dryrun:
-        repo.ui.write(('hg update %s;\n' % target.rev()))
+        repo.ui.write(_('hg update %s;\n') % target)
         if bookmark is not None:
-            repo.ui.write(('hg bookmark %s -r %s;\n'
-                           % (bookmark, target.rev())))
+            repo.ui.write(_('hg bookmark %s -r %s;\n')
+                          % (bookmark, target))
     else:
         ret = hg.update(repo, target.rev())
         if not ret:
@@ -2086,7 +992,7 @@
 
     # we do not filter in the 1 case to allow prev to t0
     if currenttopic and topic and _gettopicidx(p1) != 1:
-        parents = [repo[_singlesuccessor(repo, ctx)] if ctx.mutable() else ctx
+        parents = [repo[utility._singlesuccessor(repo, ctx)] if ctx.mutable() else ctx
                    for ctx in parents]
         parents = [ctx for ctx in parents if ctx.topic() == currenttopic]
 
@@ -2102,16 +1008,22 @@
         if movebookmark:
             bookmark = repo._activebookmark
     else:
-        for p in parents:
-            displayer.show(p)
-        repo.ui.warn(_('multiple parents, explicitly update to one\n'))
+        header = _("multiple parents, choose one to update:")
+        prevs = [p.rev() for p in parents]
+        choosedrev = utility.revselectionprompt(repo.ui, repo, prevs, header)
+        if choosedrev is None:
+            for p in parents:
+                displayer.show(p)
+            repo.ui.warn(_('multiple parents, explicitly update to one\n'))
+        else:
+            target = repo[choosedrev]
     return target, bookmark
 
 @eh.command(
     '^previous',
     [('B', 'move-bookmark', False,
         _('move active bookmark after update')),
-     ('', 'merge', False, _('bring uncommitted change along')),
+     ('m', 'merge', False, _('bring uncommitted change along')),
      ('', 'no-topic', False, _('ignore topic and move topologically')),
      ('n', 'dry-run', False,
         _('do not perform actions, just print what would be done'))],
@@ -2128,7 +1040,7 @@
         wkctx = repo[None]
         wparents = wkctx.parents()
         if len(wparents) != 1:
-            raise error.Abort('merge in progress')
+            raise error.Abort(_('merge in progress'))
         if not opts['merge']:
             try:
                 cmdutil.bailifchanged(repo)
@@ -2136,7 +1048,8 @@
                 exc.hint = _('do you want --merge?')
                 raise
 
-        displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+        displayer = compat.changesetdisplayer(ui, repo,
+                                              {'template': shorttemplate})
         topic = not opts.get("no_topic", False)
 
         target, bookmark = _findprevtarget(repo, displayer,
@@ -2160,7 +1073,7 @@
     '^next',
     [('B', 'move-bookmark', False,
         _('move active bookmark after update')),
-     ('', 'merge', False, _('bring uncommitted change along')),
+     ('m', 'merge', False, _('bring uncommitted change along')),
      ('', 'evolve', False, _('evolve the next changeset if necessary')),
      ('', 'no-topic', False, _('ignore topic and move topologically')),
      ('n', 'dry-run', False,
@@ -2181,7 +1094,7 @@
         wkctx = repo[None]
         wparents = wkctx.parents()
         if len(wparents) != 1:
-            raise error.Abort('merge in progress')
+            raise error.Abort(_('merge in progress'))
         if not opts['merge']:
             try:
                 cmdutil.bailifchanged(repo)
@@ -2196,41 +1109,25 @@
             filtered = [ctx for ctx in children if ctx.topic() != topic]
             # XXX N-square membership on children
             children = [ctx for ctx in children if ctx not in filtered]
-        displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+        displayer = compat.changesetdisplayer(ui, repo,
+                                              {'template': shorttemplate})
         if len(children) == 1:
             c = children[0]
-            bm = repo._activebookmark
-            shouldmove = opts.get('move_bookmark') and bm is not None
-            if dryrunopt:
-                ui.write(('hg update %s;\n' % c.rev()))
-                if shouldmove:
-                    ui.write(('hg bookmark %s -r %s;\n' % (bm, c.rev())))
+            return _updatetonext(ui, repo, c, displayer, opts)
+        elif children:
+            cheader = _("ambiguous next changeset, choose one to update:")
+            crevs = [c.rev() for c in children]
+            choosedrev = utility.revselectionprompt(ui, repo, crevs, cheader)
+            if choosedrev is None:
+                ui.warn(_("ambiguous next changeset:\n"))
+                for c in children:
+                    displayer.show(c)
+                ui.warn(_("explicitly update to one of them\n"))
+                return 1
             else:
-                ret = hg.update(repo, c.rev())
-                if not ret:
-                    lock = tr = None
-                    try:
-                        lock = repo.lock()
-                        tr = repo.transaction('next')
-                        if shouldmove:
-                            bmchanges = [(bm, c.node())]
-                            compat.bookmarkapplychanges(repo, tr, bmchanges)
-                        else:
-                            bookmarksmod.deactivate(repo)
-                        tr.close()
-                    finally:
-                        lockmod.release(tr, lock)
-            if not ui.quiet:
-                displayer.show(c)
-            result = 0
-        elif children:
-            ui.warn(_("ambiguous next changeset:\n"))
-            for c in children:
-                displayer.show(c)
-            ui.warn(_('explicitly update to one of them\n'))
-            result = 1
+                return _updatetonext(ui, repo, repo[choosedrev], displayer, opts)
         else:
-            aspchildren = _aspiringchildren(repo, [repo['.'].rev()])
+            aspchildren = evolvecmd._aspiringchildren(repo, [repo['.'].rev()])
             if topic:
                 filtered.extend(repo[c] for c in children
                                 if repo[c].topic() != topic)
@@ -2246,26 +1143,67 @@
                     msg = _('(%i unstable changesets to be evolved here, '
                             'do you want --evolve?)\n')
                     ui.warn(msg % len(aspchildren))
-                result = 1
+                return 1
             elif 1 < len(aspchildren):
-                ui.warn(_("ambiguous next (unstable) changeset:\n"))
-                for c in aspchildren:
-                    displayer.show(repo[c])
-                ui.warn(_("(run 'hg evolve --rev REV' on one of them)\n"))
-                return 1
+                cheader = _("ambiguous next (unstable) changeset, choose one to"
+                            " evolve and update:")
+                choosedrev = utility.revselectionprompt(ui, repo,
+                                                        aspchildren, cheader)
+                if choosedrev is None:
+                    ui.warn(_("ambiguous next (unstable) changeset:\n"))
+                    for c in aspchildren:
+                        displayer.show(repo[c])
+                    ui.warn(_("(run 'hg evolve --rev REV' on one of them)\n"))
+                    return 1
+                else:
+                    return _nextevolve(ui, repo, repo[choosedrev], opts)
             else:
-                cmdutil.bailifchanged(repo)
-                result = _solveone(ui, repo, repo[aspchildren[0]], dryrunopt,
-                                   False, lambda: None, category='orphan')
-                if not result:
-                    ui.status(_('working directory now at %s\n')
-                              % ui.label(str(repo['.']), 'evolve.node'))
-                return result
-            return 1
-        return result
+                return _nextevolve(ui, repo, aspchildren[0], opts)
     finally:
         lockmod.release(wlock)
 
+def _nextevolve(ui, repo, aspchildren, opts):
+    """logic for hg next command to evolve and update to an aspiring children"""
+
+    cmdutil.bailifchanged(repo)
+    evolvestate = state.cmdstate(repo, opts={'command': 'next'})
+    result = evolvecmd._solveone(ui, repo, repo[aspchildren],
+                                 evolvestate, opts.get('dry_run'), False,
+                                 lambda: None, category='orphan')
+    # making sure a next commit is formed
+    if result[0] and result[1]:
+        ui.status(_('working directory now at %s\n')
+                  % ui.label(str(repo['.']), 'evolve.node'))
+    return 0
+
+def _updatetonext(ui, repo, children, displayer, opts):
+    """ logic for `hg next` command to update to children and move bookmarks if
+    required """
+    bm = repo._activebookmark
+    shouldmove = opts.get('move_bookmark') and bm is not None
+    if opts.get('dry_run'):
+        ui.write(_('hg update %s;\n') % children)
+        if shouldmove:
+            ui.write(_('hg bookmark %s -r %s;\n') % (bm, children))
+    else:
+        ret = hg.update(repo, children)
+        if not ret:
+            lock = tr = None
+            try:
+                lock = repo.lock()
+                tr = repo.transaction('next')
+                if shouldmove:
+                    bmchanges = [(bm, children.node())]
+                    compat.bookmarkapplychanges(repo, tr, bmchanges)
+                else:
+                    bookmarksmod.deactivate(repo)
+                tr.close()
+            finally:
+                lockmod.release(tr, lock)
+    if not ui.quiet:
+        displayer.show(children)
+    return 0
+
 @eh.wrapcommand('commit')
 def commitwrapper(orig, ui, repo, *arg, **kwargs):
     tr = None
@@ -2287,7 +1225,7 @@
                 oldbookmarks.extend(repo.nodebookmarks(old.node()))
                 markers.append((old, (new,)))
             if markers:
-                obsolete.createmarkers(repo, markers)
+                compat.createmarkers(repo, markers, operation="amend")
             bmchanges = []
             for book in oldbookmarks:
                 bmchanges.append((book, new.node()))
@@ -2432,47 +1370,6 @@
                               _helploader))
         help.helptable.sort()
 
-def _relocatecommit(repo, orig, commitmsg):
-    if commitmsg is None:
-        commitmsg = orig.description()
-    extra = dict(orig.extra())
-    if 'branch' in extra:
-        del extra['branch']
-    extra['rebase_source'] = orig.hex()
-
-    backup = repo.ui.backupconfig('phases', 'new-commit')
-    try:
-        targetphase = max(orig.phase(), phases.draft)
-        repo.ui.setconfig('phases', 'new-commit', targetphase, 'evolve')
-        # Commit might fail if unresolved files exist
-        nodenew = repo.commit(text=commitmsg, user=orig.user(),
-                              date=orig.date(), extra=extra)
-    finally:
-        repo.ui.restoreconfig(backup)
-    return nodenew
-
-def _finalizerelocate(repo, orig, dest, nodenew, tr):
-    destbookmarks = repo.nodebookmarks(dest.node())
-    nodesrc = orig.node()
-    destphase = repo[nodesrc].phase()
-    oldbookmarks = repo.nodebookmarks(nodesrc)
-    bmchanges = []
-
-    if nodenew is not None:
-        phases.retractboundary(repo, tr, destphase, [nodenew])
-        obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))])
-        for book in oldbookmarks:
-            bmchanges.append((book, nodenew))
-    else:
-        obsolete.createmarkers(repo, [(repo[nodesrc], ())])
-        # Behave like rebase, move bookmarks to dest
-        for book in oldbookmarks:
-            bmchanges.append((book, dest.node()))
-    for book in destbookmarks: # restore bookmark that rebase move
-        bmchanges.append((book, dest.node()))
-    if bmchanges:
-        compat.bookmarkapplychanges(repo, tr, bmchanges)
-
 evolvestateversion = 0
 
 @eh.uisetup
@@ -2481,32 +1378,13 @@
             _("use 'hg evolve --continue' or 'hg update -C .' to abort"))
     cmdutil.unfinishedstates.append(data)
 
+    afterresolved = ('evolvestate', _('hg evolve --continue'))
+    grabresolved = ('grabstate', _('hg grab --continue'))
+    cmdutil.afterresolvedstates.append(afterresolved)
+    cmdutil.afterresolvedstates.append(grabresolved)
+
 @eh.wrapfunction(hg, 'clean')
 def clean(orig, repo, *args, **kwargs):
     ret = orig(repo, *args, **kwargs)
     util.unlinkpath(repo.vfs.join('evolvestate'), ignoremissing=True)
     return ret
-
-def _evolvemerge(repo, orig, dest, pctx, keepbranch):
-    """Used by the evolve function to merge dest on top of pctx.
-    return the same tuple as merge.graft"""
-    if repo['.'].rev() != dest.rev():
-        merge.update(repo,
-                     dest,
-                     branchmerge=False,
-                     force=True)
-    if repo._activebookmark:
-        repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark)
-    bookmarksmod.deactivate(repo)
-    if keepbranch:
-        repo.dirstate.setbranch(orig.branch())
-    if util.safehasattr(repo, 'currenttopic'):
-        # uurrgs
-        # there no other topic setter yet
-        if not orig.topic() and repo.vfs.exists('topic'):
-                repo.vfs.unlink('topic')
-        else:
-            with repo.vfs.open('topic', 'w') as f:
-                f.write(orig.topic())
-
-    return merge.graft(repo, orig, pctx, ['destination', 'evolving'], True)
--- a/hgext3rd/evolve/cmdrewrite.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/cmdrewrite.py	Wed Mar 21 16:35:18 2018 +0100
@@ -22,6 +22,7 @@
     error,
     hg,
     lock as lockmod,
+    merge,
     node,
     obsolete,
     patch,
@@ -34,6 +35,7 @@
 
 from . import (
     compat,
+    state,
     exthelper,
     rewriteutil,
     utility,
@@ -72,7 +74,7 @@
     """
     # N.B. this is extremely similar to setupheaderopts() in mq.py
     if not opts.get('date') and opts.get('current_date'):
-        opts['date'] = '%d %d' % util.makedate()
+        opts['date'] = '%d %d' % compat.makedate()
     if not opts.get('user') and opts.get('current_user'):
         opts['user'] = ui.username()
 
@@ -126,13 +128,13 @@
         edit = opts.pop('edit', False)
         log = opts.get('logfile')
         opts['amend'] = True
-        if not (edit or opts['message'] or log):
-            opts['message'] = repo['.'].description()
         _resolveoptions(ui, opts)
         _alias, commitcmd = cmdutil.findcmd('commit', commands.table)
         try:
             wlock = repo.wlock()
             lock = repo.lock()
+            if not (edit or opts['message'] or log):
+                opts['message'] = repo['.'].description()
             rewriteutil.precheck(repo, [repo['.'].rev()], action='amend')
             return commitcmd[0](ui, repo, *pats, **opts)
         finally:
@@ -387,7 +389,8 @@
         if opts.get('note'):
             metadata['note'] = opts['note']
 
-        obsolete.createmarkers(repo, [(old, (repo[newid],))], metadata=metadata)
+        compat.createmarkers(repo, [(old, (repo[newid],))], metadata=metadata,
+                             operation="uncommit")
         phases.retractboundary(repo, tr, oldphase, [newid])
         if opts.get('revert'):
             hg.updaterepo(repo, newid, True)
@@ -425,7 +428,7 @@
     fp.seek(0)
     newnode = _patchtocommit(ui, repo, old, fp)
     # creating obs marker temp -> ()
-    obsolete.createmarkers(repo, [(repo[tempnode], ())])
+    compat.createmarkers(repo, [(repo[tempnode], ())], operation="uncommit")
     return newnode
 
 def _createtempcommit(ui, repo, old, match):
@@ -609,8 +612,14 @@
                                                          root.p2().node()],
                                                         commitopts=commitopts)
             phases.retractboundary(repo, tr, targetphase, [newid])
-            obsolete.createmarkers(repo, [(ctx, (repo[newid],))
-                                   for ctx in allctx], metadata=metadata)
+            compat.createmarkers(repo, [(ctx, (repo[newid],))
+                                 for ctx in allctx], metadata=metadata,
+                                 operation="fold")
+            # move bookmarks from old nodes to the new one
+            # XXX: we should make rewriteutil.rewrite() handle such cases
+            for ctx in allctx:
+                bmupdater = rewriteutil.bookmarksupdater(repo, ctx.node(), tr)
+                bmupdater(newid)
             tr.close()
         finally:
             tr.release()
@@ -736,9 +745,9 @@
                     metadata['note'] = opts['note']
 
                 phases.retractboundary(repo, tr, targetphase, [newid])
-                obsolete.createmarkers(repo, [(ctx, (repo[newid],))
-                                              for ctx in allctx],
-                                       metadata=metadata)
+                compat.createmarkers(repo, [(ctx, (repo[newid],))
+                                            for ctx in allctx],
+                                     metadata=metadata, operation="metaedit")
             else:
                 ui.status(_("nothing changed\n"))
             tr.close()
@@ -764,7 +773,7 @@
     date = opts.get('date')
     user = opts.get('user')
     if date:
-        metadata['date'] = '%i %i' % util.parsedate(date)
+        metadata['date'] = '%i %i' % compat.parsedate(date)
     if user:
         metadata['user'] = user
     return metadata
@@ -930,12 +939,14 @@
             metadata['note'] = opts['note']
 
         # create markers
-        obsolete.createmarkers(repo, relations, metadata=metadata)
+        compat.createmarkers(repo, relations, metadata=metadata,
+                             operation="prune")
 
         # informs that changeset have been pruned
         ui.status(_('%i changesets pruned\n') % len(precs))
 
-        for ctx in repo.unfiltered().set('bookmark() and %ld', precs):
+        precrevs = (precursor.rev() for precursor in precs)
+        for ctx in repo.unfiltered().set('bookmark() and %ld', precrevs):
             # used to be:
             #
             #   ldest = list(repo.set('max((::%d) - obsolete())', ctx))
@@ -986,11 +997,11 @@
     try:
         wlock = repo.wlock()
         lock = repo.lock()
-        rev = scmutil.revsingle(repo, revarg[0])
+        ctx = scmutil.revsingle(repo, revarg[0])
+        rev = ctx.rev()
         cmdutil.bailifchanged(repo)
         rewriteutil.precheck(repo, [rev], action='split')
         tr = repo.transaction('split')
-        ctx = repo[rev]
 
         if len(ctx.parents()) > 1:
             raise error.Abort(_("cannot split merge commits"))
@@ -1042,8 +1053,8 @@
             metadata = {}
             if opts.get('note'):
                 metadata['note'] = opts['note']
-            obsolete.createmarkers(repo, [(repo[rev], newcommits)],
-                                   metadata=metadata)
+            compat.createmarkers(repo, [(repo[rev], newcommits)],
+                                 metadata=metadata, operation="split")
         tr.close()
     finally:
         # Restore the old branch
@@ -1081,7 +1092,7 @@
     if not duplicate:
         rewriteutil.precheck(repo, revs, touch)
     tmpl = utility.shorttemplate
-    displayer = cmdutil.show_changeset(ui, repo, {'template': tmpl})
+    displayer = compat.changesetdisplayer(ui, repo, {'template': tmpl})
     wlock = lock = tr = None
     try:
         wlock = repo.wlock()
@@ -1134,8 +1145,8 @@
                 metadata = {}
                 if opts.get('note'):
                     metadata['note'] = opts['note']
-                obsolete.createmarkers(repo, [(ctx, (repo[new],))],
-                                       metadata=metadata)
+                compat.createmarkers(repo, [(ctx, (repo[new],))],
+                                     metadata=metadata, operation="touch")
             phases.retractboundary(repo, tr, ctx.phase(), [new])
             if ctx in repo[None].parents():
                 with repo.dirstate.parentchange():
@@ -1143,3 +1154,94 @@
         tr.close()
     finally:
         lockmod.release(tr, lock, wlock)
+
+@eh.command(
+    'grab',
+    [('r', 'rev', '', 'revision to grab'),
+     ('c', 'continue', False, 'continue interrupted grab'),
+     ('a', 'abort', False, 'abort interrupted grab'),
+    ],
+    _('[-r] rev'))
+def grab(ui, repo, *revs, **opts):
+    """grabs a commit, move it on the top of working directory parent and
+    updates to it."""
+
+    cont = opts.get('continue')
+    abort = opts.get('abort')
+
+    if cont and abort:
+        raise error.Abort(_("cannot specify both --continue and --abort"))
+
+    revs = list(revs)
+    if opts.get('rev'):
+        revs.append(opts['rev'])
+
+    with repo.wlock(), repo.lock(), repo.transaction('grab'):
+        grabstate = state.cmdstate(repo, path='grabstate')
+        pctx = repo['.']
+
+        if not cont and not abort:
+            cmdutil.bailifchanged(repo)
+            revs = scmutil.revrange(repo, revs)
+            if len(revs) > 1:
+                raise error.Abort(_("specify just one revision"))
+            elif not revs:
+                raise error.Abort(_("empty revision set"))
+
+            origctx = repo[revs.first()]
+
+            if origctx in pctx.ancestors() or origctx.node() == pctx.node():
+                raise error.Abort(_("cannot grab an ancestor revision"))
+
+            rewriteutil.precheck(repo, [origctx.rev()], 'grab')
+
+            ui.status(_('grabbing %d:%s "%s"\n') %
+                      (origctx.rev(), origctx,
+                       origctx.description().split("\n", 1)[0]))
+            stats = merge.graft(repo, origctx, origctx.p1(), ['local',
+                                                              'destination'])
+            if stats[3]:
+                grabstate.addopts({'orignode': origctx.node(),
+                                   'oldpctx': pctx.node()})
+                grabstate.save()
+                raise error.InterventionRequired(_("unresolved merge conflicts"
+                                                   " (see hg help resolve)"))
+
+        elif abort:
+            if not grabstate:
+                raise error.Abort(_("no interrupted grab state exists"))
+            grabstate.load()
+            pctxnode = grabstate['oldpctx']
+            ui.status(_("aborting grab, updating to %s\n") %
+                      node.hex(pctxnode)[:12])
+            hg.updaterepo(repo, pctxnode, True)
+            return 0
+
+        else:
+            if revs:
+                raise error.Abort(_("cannot specify both --continue and "
+                                    "revision"))
+            if not grabstate:
+                raise error.Abort(_("no interrupted grab state exists"))
+
+            grabstate.load()
+            orignode = grabstate['orignode']
+            origctx = repo[orignode]
+
+        overrides = {('phases', 'new-commit'): origctx.phase()}
+        with repo.ui.configoverride(overrides, 'grab'):
+            newnode = repo.commit(text=origctx.description(),
+                                  user=origctx.user(),
+                                  date=origctx.date(), extra=origctx.extra())
+
+        if grabstate:
+            grabstate.delete()
+        newctx = repo[newnode] if newnode else pctx
+        compat.createmarkers(repo, [(origctx, (newctx,))], operation="grab")
+
+        if newnode is None:
+            ui.warn(_("note: grab of %d:%s created no changes to commit\n") %
+                    (origctx.rev(), origctx))
+            return 0
+
+        return 0
--- a/hgext3rd/evolve/compat.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/compat.py	Wed Mar 21 16:35:18 2018 +0100
@@ -6,14 +6,21 @@
 Compatibility module
 """
 
+import inspect
+
+import functools
+
 from mercurial import (
     copies,
     context,
     hg,
+    mdiff,
     obsolete,
     revset,
     util,
+    wireproto,
 )
+from mercurial.hgweb import hgweb_mod
 
 # hg < 4.2 compat
 try:
@@ -28,12 +35,35 @@
 except ImportError:
     obsutil = None
 
+# hg < 4.6 compat (c8e2d6ed1f9e)
+try:
+    from mercurial import logcmdutil
+    changesetdisplayer = logcmdutil.changesetdisplayer
+    changesetprinter = logcmdutil.changesetprinter
+    displaygraph = logcmdutil.displaygraph
+except (AttributeError, ImportError):
+    from mercurial import cmdutil
+    changesetdisplayer = cmdutil.show_changeset
+    changesetprinter = cmdutil.changeset_printer
+    displaygraph = cmdutil.displaygraph
+
 from . import (
     exthelper,
 )
 
 eh = exthelper.exthelper()
 
+# Wrap obsolete.creatmarkers and make it accept but ignore "operation" argument
+# for hg < 4.3
+createmarkers = obsolete.createmarkers
+originalcreatemarkers = createmarkers
+while isinstance(originalcreatemarkers, functools.partial):
+    originalcreatemarkers = originalcreatemarkers.func
+if originalcreatemarkers.__code__.co_argcount < 6:
+    def createmarkers(repo, relations, flag=0, date=None, metadata=None,
+                      operation=None):
+        return obsolete.createmarkers(repo, relations, flag, date, metadata)
+
 if not util.safehasattr(hg, '_copycache'):
     # exact copy of relevantmarkers as in Mercurial-176d1a0ce385
     # this fixes relevant markers computation for version < hg-4.3
@@ -226,3 +256,40 @@
         cachevfs = vfsmod.vfs(repo.vfs.join('cache'))
         cachevfs.createmode = repo.store.createmode
     return cachevfs
+
+def strdiff(a, b, fn1, fn2):
+    """ A version of mdiff.unidiff for comparing two strings
+    """
+    args = [a, '', b, '', fn1, fn2]
+
+    # hg < 4.6 compat 8b6dd3922f70
+    argspec = inspect.getargspec(mdiff.unidiff)
+
+    if 'binary' in argspec.args:
+        args.append(False)
+
+    return mdiff.unidiff(*args)
+
+# date related
+
+try:
+    import mercurial.utils.dateutil
+    makedate = mercurial.utils.dateutil.makedate
+    parsedate = mercurial.utils.dateutil.parsedate
+except ImportError as e:
+    import mercurial.util
+    makedate = mercurial.util.makedate
+    parsedate = mercurial.util.parsedate
+
+def wireprotocommand(exthelper, name, args='', permission='pull'):
+    if 3 <= len(wireproto.wireprotocommand.func_defaults):
+        return wireproto.wireprotocommand(name, args, permission=permission)
+
+    else:
+        # <= hg-4.5 permission must be registered in dictionnary
+        def decorator(func):
+            @eh.extsetup
+            def install(ui):
+                hgweb_mod.perms[name] = permission
+                wireproto.commands[name] = (func, args)
+        return decorator
--- a/hgext3rd/evolve/depthcache.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/depthcache.py	Wed Mar 21 16:35:18 2018 +0100
@@ -87,11 +87,11 @@
 
         if util.safehasattr(repo, 'updatecaches'):
             @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None):
+            def updatecaches(self, tr=None, **kwargs):
                 if utility.shouldwarmcache(self, tr):
                     self.depthcache.update(self)
                     self.depthcache.save(self)
-                super(depthcacherepo, self).updatecaches(tr)
+                super(depthcacherepo, self).updatecaches(tr, **kwargs)
 
         else:
             def transaction(self, *args, **kwargs):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/evolvecmd.py	Wed Mar 21 16:35:18 2018 +0100
@@ -0,0 +1,1297 @@
+# Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
+#                Logilab SA        <contact@logilab.fr>
+#                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+#                Patrick Mezard <patrick@mezard.eu>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+"""logic related to hg evolve command"""
+
+import collections
+import re
+
+from mercurial import (
+    bookmarks as bookmarksmod,
+    cmdutil,
+    commands,
+    context,
+    copies,
+    error,
+    hg,
+    lock as lockmod,
+    merge,
+    node,
+    obsolete,
+    phases,
+    scmutil,
+    util,
+)
+
+from mercurial.i18n import _
+
+from . import (
+    cmdrewrite,
+    compat,
+    exthelper,
+    rewriteutil,
+    state,
+    utility,
+)
+
+TROUBLES = compat.TROUBLES
+shorttemplate = utility.shorttemplate
+_bookmarksupdater = rewriteutil.bookmarksupdater
+sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
+
+eh = exthelper.exthelper()
+_bookmarksupdater = rewriteutil.bookmarksupdater
+mergetoolopts = commands.mergetoolopts
+
+def _solveone(ui, repo, ctx, evolvestate, dryrun, confirm,
+              progresscb, category):
+    """Resolve the troubles affecting one revision
+
+    returns a tuple (bool, newnode) where,
+        bool: a boolean value indicating whether the instability was solved
+        newnode: if bool is True, then the newnode of the resultant commit
+                 formed. newnode can be node, when resolution led to no new
+                 commit. If bool is False, this is ''.
+    """
+    wlock = lock = tr = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        tr = repo.transaction("evolve")
+        if 'orphan' == category:
+            result = _solveunstable(ui, repo, ctx, evolvestate,
+                                    dryrun, confirm, progresscb)
+        elif 'phasedivergent' == category:
+            result = _solvebumped(ui, repo, ctx, evolvestate,
+                                  dryrun, confirm, progresscb)
+        elif 'contentdivergent' == category:
+            result = _solvedivergent(ui, repo, ctx, evolvestate,
+                                     dryrun, confirm, progresscb)
+        else:
+            assert False, "unknown trouble category: %s" % (category)
+        tr.close()
+        return result
+    finally:
+        lockmod.release(tr, lock, wlock)
+
+def _solveunstable(ui, repo, orig, evolvestate, dryrun=False, confirm=False,
+                   progresscb=None):
+    """ Tries to stabilize the changeset orig which is orphan.
+
+    returns a tuple (bool, newnode) where,
+        bool: a boolean value indicating whether the instability was solved
+        newnode: if bool is True, then the newnode of the resultant commit
+                 formed. newnode can be node, when resolution led to no new
+                 commit. If bool is False, this is ''.
+    """
+    pctx = orig.p1()
+    keepbranch = orig.p1().branch() != orig.branch()
+    if len(orig.parents()) == 2:
+        p1obs = orig.p1().obsolete()
+        p2obs = orig.p2().obsolete()
+        if not p1obs and p2obs:
+            pctx = orig.p2()  # second parent is obsolete ?
+            keepbranch = orig.p2().branch() != orig.branch()
+        elif not p2obs and p1obs:
+            pass
+        else:
+            # store that we are resolving an orphan merge with both parents
+            # obsolete and proceed with first parent
+            evolvestate['orphanmerge'] = True
+            # we should process the second parent first, so that in case of
+            # no-conflicts the first parent is processed later and preserved as
+            # first parent
+            pctx = orig.p2()
+            keepbranch = orig.p2().branch() != orig.branch()
+
+    if not pctx.obsolete():
+        ui.warn(_("cannot solve instability of %s, skipping\n") % orig)
+        return (False, '')
+    obs = pctx
+    newer = compat.successorssets(repo, obs.node())
+    # search of a parent which is not killed
+    while not newer or newer == [()]:
+        ui.debug("stabilize target %s is plain dead,"
+                 " trying to stabilize on its parent\n" %
+                 obs)
+        obs = obs.parents()[0]
+        newer = compat.successorssets(repo, obs.node())
+    if len(newer) > 1:
+        msg = _("skipping %s: divergent rewriting. can't choose "
+                "destination\n") % obs
+        ui.write_err(msg)
+        return (False, '')
+    targets = newer[0]
+    assert targets
+    if len(targets) > 1:
+        # split target, figure out which one to pick, are they all in line?
+        targetrevs = [repo[r].rev() for r in targets]
+        roots = repo.revs('roots(%ld)', targetrevs)
+        heads = repo.revs('heads(%ld)', targetrevs)
+        if len(roots) > 1 or len(heads) > 1:
+            cheader = _("ancestor '%s' split over multiple topological"
+                        " branches.\nchoose an evolve destination:") % orig
+            selectedrev = utility.revselectionprompt(ui, repo, list(heads),
+                                                     cheader)
+            if selectedrev is None:
+                msg = _("could not solve instability, "
+                        "ambiguous destination: "
+                        "parent split across two branches\n")
+                ui.write_err(msg)
+                return (False, '')
+            target = repo[selectedrev]
+        else:
+            target = repo[heads.first()]
+    else:
+        target = targets[0]
+    displayer = compat.changesetdisplayer(ui, repo, {'template': shorttemplate})
+    target = repo[target]
+    if not ui.quiet or confirm:
+        repo.ui.write(_('move:'), label='evolve.operation')
+        displayer.show(orig)
+        repo.ui.write(_('atop:'))
+        displayer.show(target)
+    if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
+            raise error.Abort(_('evolve aborted by user'))
+    if progresscb:
+        progresscb()
+    todo = 'hg rebase -r %s -d %s\n' % (orig, target)
+    if dryrun:
+        repo.ui.write(todo)
+        return (False, '')
+    else:
+        repo.ui.note(todo)
+        if progresscb:
+            progresscb()
+        try:
+            newid = relocate(repo, orig, target, pctx, keepbranch)
+            return (True, newid)
+        except MergeFailure:
+            ops = {'current': orig.node()}
+            evolvestate.addopts(ops)
+            evolvestate.save()
+            repo.ui.write_err(_('evolve failed!\n'))
+            repo.ui.write_err(
+                _("fix conflict and run 'hg evolve --continue'"
+                  " or use 'hg update -C .' to abort\n"))
+            raise
+
+def _solvebumped(ui, repo, bumped, evolvestate, dryrun=False, confirm=False,
+                 progresscb=None):
+    """Stabilize a bumped changeset
+
+    returns a tuple (bool, newnode) where,
+        bool: a boolean value indicating whether the instability was solved
+        newnode: if bool is True, then the newnode of the resultant commit
+                 formed. newnode can be node, when resolution led to no new
+                 commit. If bool is False, this is ''.
+    """
+    repo = repo.unfiltered()
+    bumped = repo[bumped.rev()]
+    # For now we deny bumped merge
+    if len(bumped.parents()) > 1:
+        msg = _('skipping %s : we do not handle merge yet\n') % bumped
+        ui.write_err(msg)
+        return (False, '')
+    prec = repo.set('last(allprecursors(%d) and public())', bumped.rev()).next()
+    # For now we deny target merge
+    if len(prec.parents()) > 1:
+        msg = _('skipping: %s: public version is a merge, '
+                'this is not handled yet\n') % prec
+        ui.write_err(msg)
+        return (False, '')
+
+    displayer = compat.changesetdisplayer(ui, repo, {'template': shorttemplate})
+    if not ui.quiet or confirm:
+        repo.ui.write(_('recreate:'), label='evolve.operation')
+        displayer.show(bumped)
+        repo.ui.write(_('atop:'))
+        displayer.show(prec)
+    if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
+        raise error.Abort(_('evolve aborted by user'))
+    if dryrun:
+        todo = 'hg rebase --rev %s --dest %s;\n' % (bumped, prec.p1())
+        repo.ui.write(todo)
+        repo.ui.write(('hg update %s;\n' % prec))
+        repo.ui.write(('hg revert --all --rev %s;\n' % bumped))
+        repo.ui.write(('hg commit --msg "%s update to %s"\n' %
+                       (TROUBLES['PHASEDIVERGENT'], bumped)))
+        return (False, '')
+    if progresscb:
+        progresscb()
+    newid = tmpctx = None
+    tmpctx = bumped
+    # Basic check for common parent. Far too complicated and fragile
+    tr = repo.currenttransaction()
+    assert tr is not None
+    bmupdate = _bookmarksupdater(repo, bumped.node(), tr)
+    if not list(repo.set('parents(%d) and parents(%d)', bumped.rev(), prec.rev())):
+        # Need to rebase the changeset at the right place
+        repo.ui.status(
+            _('rebasing to destination parent: %s\n') % prec.p1())
+        try:
+            tmpid = relocate(repo, bumped, prec.p1())
+            if tmpid is not None:
+                tmpctx = repo[tmpid]
+                compat.createmarkers(repo, [(bumped, (tmpctx,))],
+                                     operation='evolve')
+        except MergeFailure:
+            repo.vfs.write('graftstate', bumped.hex() + '\n')
+            repo.ui.write_err(_('evolution failed!\n'))
+            msg = _("fix conflict and run 'hg evolve --continue'\n")
+            repo.ui.write_err(msg)
+            raise
+    # Create the new commit context
+    repo.ui.status(_('computing new diff\n'))
+    files = set()
+    copied = copies.pathcopies(prec, bumped)
+    precmanifest = prec.manifest().copy()
+    # 3.3.2 needs a list.
+    # future 3.4 don't detect the size change during iteration
+    # this is fishy
+    for key, val in list(bumped.manifest().iteritems()):
+        precvalue = precmanifest.get(key, None)
+        if precvalue is not None:
+            del precmanifest[key]
+        if precvalue != val:
+            files.add(key)
+    files.update(precmanifest)  # add missing files
+    # commit it
+    if files: # something to commit!
+        def filectxfn(repo, ctx, path):
+            if path in bumped:
+                fctx = bumped[path]
+                flags = fctx.flags()
+                mctx = compat.memfilectx(repo, ctx, fctx, flags, copied, path)
+                return mctx
+            return None
+        text = '%s update to %s:\n\n' % (TROUBLES['PHASEDIVERGENT'], prec)
+        text += bumped.description()
+
+        new = context.memctx(repo,
+                             parents=[prec.node(), node.nullid],
+                             text=text,
+                             files=files,
+                             filectxfn=filectxfn,
+                             user=bumped.user(),
+                             date=bumped.date(),
+                             extra=bumped.extra())
+
+        newid = repo.commitctx(new)
+    if newid is None:
+        compat.createmarkers(repo, [(tmpctx, ())], operation='evolve')
+        newid = prec.node()
+    else:
+        phases.retractboundary(repo, tr, bumped.phase(), [newid])
+        compat.createmarkers(repo, [(tmpctx, (repo[newid],))],
+                             flag=obsolete.bumpedfix, operation='evolve')
+    bmupdate(newid)
+    repo.ui.status(_('committed as %s\n') % node.short(newid))
+    # reroute the working copy parent to the new changeset
+    with repo.dirstate.parentchange():
+        repo.dirstate.setparents(newid, node.nullid)
+    return (True, newid)
+
+def _solvedivergent(ui, repo, divergent, evolvestate, dryrun=False,
+                    confirm=False, progresscb=None):
+    """tries to solve content-divergence of a changeset
+
+    returns a tuple (bool, newnode) where,
+        bool: a boolean value indicating whether the instability was solved
+        newnode: if bool is True, then the newnode of the resultant commit
+                 formed. newnode can be node, when resolution led to no new
+                 commit. If bool is False, this is ''.
+    """
+    repo = repo.unfiltered()
+    divergent = repo[divergent.rev()]
+    base, others = divergentdata(divergent)
+    if len(others) > 1:
+        othersstr = "[%s]" % (','.join([str(i) for i in others]))
+        msg = _("skipping %d:%s with a changeset that got split"
+                " into multiple ones:\n"
+                "|[%s]\n"
+                "| This is not handled by automatic evolution yet\n"
+                "| You have to fallback to manual handling with commands "
+                "such as:\n"
+                "| - hg touch -D\n"
+                "| - hg prune\n"
+                "| \n"
+                "| You should contact your local evolution Guru for help.\n"
+                ) % (divergent, TROUBLES['CONTENTDIVERGENT'], othersstr)
+        ui.write_err(msg)
+        return (False, '')
+    other = others[0]
+    if len(other.parents()) > 1:
+        msg = _("skipping %s: %s changeset can't be "
+                "a merge (yet)\n") % (divergent, TROUBLES['CONTENTDIVERGENT'])
+        ui.write_err(msg)
+        hint = _("You have to fallback to solving this by hand...\n"
+                 "| This probably means redoing the merge and using \n"
+                 "| `hg prune` to kill older version.\n")
+        ui.write_err(hint)
+        return (False, '')
+    if other.p1() not in divergent.parents():
+        msg = _("skipping %s: have a different parent than %s "
+                "(not handled yet)\n") % (divergent, other)
+        hint = _("| %(d)s, %(o)s are not based on the same changeset.\n"
+                 "| With the current state of its implementation, \n"
+                 "| evolve does not work in that case.\n"
+                 "| rebase one of them next to the other and run \n"
+                 "| this command again.\n"
+                 "| - either: hg rebase --dest 'p1(%(d)s)' -r %(o)s\n"
+                 "| - or:     hg rebase --dest 'p1(%(o)s)' -r %(d)s\n"
+                 ) % {'d': divergent, 'o': other}
+        ui.write_err(msg)
+        ui.write_err(hint)
+        return (False, '')
+
+    displayer = compat.changesetdisplayer(ui, repo, {'template': shorttemplate})
+    if not ui.quiet or confirm:
+        ui.write(_('merge:'), label='evolve.operation')
+        displayer.show(divergent)
+        ui.write(_('with: '))
+        displayer.show(other)
+        ui.write(_('base: '))
+        displayer.show(base)
+    if confirm and ui.prompt(_('perform evolve? [Ny]'), 'n') != 'y':
+        raise error.Abort(_('evolve aborted by user'))
+    if dryrun:
+        ui.write(('hg update -c %s &&\n' % divergent))
+        ui.write(('hg merge %s &&\n' % other))
+        ui.write(('hg commit -m "auto merge resolving conflict between '
+                 '%s and %s"&&\n' % (divergent, other)))
+        ui.write(('hg up -C %s &&\n' % base))
+        ui.write(('hg revert --all --rev tip &&\n'))
+        ui.write(('hg commit -m "`hg log -r %s --template={desc}`";\n'
+                 % divergent))
+        return (False, '')
+    if divergent not in repo[None].parents():
+        repo.ui.status(_('updating to "local" conflict\n'))
+        hg.update(repo, divergent.rev())
+    repo.ui.note(_('merging %s changeset\n') % TROUBLES['CONTENTDIVERGENT'])
+    if progresscb:
+        progresscb()
+    stats = merge.update(repo,
+                         other.node(),
+                         branchmerge=True,
+                         force=False,
+                         ancestor=base.node(),
+                         mergeancestor=True)
+    hg._showstats(repo, stats)
+    if stats[3]:
+        repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
+                         "or 'hg update -C .' to abort\n"))
+    if stats[3] > 0:
+        raise error.Abort('merge conflict between several amendments '
+                          '(this is not automated yet)',
+                          hint="""/!\ You can try:
+/!\ * manual merge + resolve => new cset X
+/!\ * hg up to the parent of the amended changeset (which are named W and Z)
+/!\ * hg revert --all -r X
+/!\ * hg ci -m "same message as the amended changeset" => new cset Y
+/!\ * hg prune -n Y W Z
+""")
+    if progresscb:
+        progresscb()
+    emtpycommitallowed = repo.ui.backupconfig('ui', 'allowemptycommit')
+    tr = repo.currenttransaction()
+    assert tr is not None
+    try:
+        repo.ui.setconfig('ui', 'allowemptycommit', True, 'evolve')
+        with repo.dirstate.parentchange():
+            repo.dirstate.setparents(divergent.node(), node.nullid)
+        oldlen = len(repo)
+        cmdrewrite.amend(ui, repo, message='', logfile='')
+        if oldlen == len(repo):
+            new = divergent
+            # no changes
+        else:
+            new = repo['.']
+        compat.createmarkers(repo, [(other, (new,))], operation='evolve')
+        phases.retractboundary(repo, tr, other.phase(), [new.node()])
+        return (True, new.node())
+    finally:
+        repo.ui.restoreconfig(emtpycommitallowed)
+
+class MergeFailure(error.Abort):
+    pass
+
+def _orderrevs(repo, revs):
+    """Compute an ordering to solve instability for the given revs
+
+    revs is a list of unstable revisions.
+
+    Returns the same revisions ordered to solve their instability from the
+    bottom to the top of the stack that the stabilization process will produce
+    eventually.
+
+    This ensures the minimal number of stabilizations, as we can stabilize each
+    revision on its final stabilized destination.
+    """
+    # Step 1: Build the dependency graph
+    dependencies, rdependencies = utility.builddependencies(repo, revs)
+    # Step 2: Build the ordering
+    # Remove the revisions with no dependency(A) and add them to the ordering.
+    # Removing these revisions leads to new revisions with no dependency (the
+    # one depending on A) that we can remove from the dependency graph and add
+    # to the ordering. We progress in a similar fashion until the ordering is
+    # built
+    solvablerevs = collections.deque([r for r in sorted(dependencies.keys())
+                                      if not dependencies[r]])
+    ordering = []
+    while solvablerevs:
+        rev = solvablerevs.popleft()
+        for dependent in rdependencies[rev]:
+            dependencies[dependent].remove(rev)
+            if not dependencies[dependent]:
+                solvablerevs.append(dependent)
+        del dependencies[rev]
+        ordering.append(rev)
+
+    ordering.extend(sorted(dependencies))
+    return ordering
+
+def relocate(repo, orig, dest, pctx=None, keepbranch=False):
+    """rewrites the orig rev on dest rev
+
+    returns the node of new commit which is formed
+    """
+    if orig.rev() == dest.rev():
+        raise error.Abort(_('tried to relocate a node on top of itself'),
+                          hint=_("This shouldn't happen. If you still "
+                                 "need to move changesets, please do so "
+                                 "manually with nothing to rebase - working "
+                                 "directory parent is also destination"))
+
+    if pctx is None:
+        if len(orig.parents()) == 2:
+            raise error.Abort(_("tried to relocate a merge commit without "
+                                "specifying which parent should be moved"),
+                              hint=_("Specify the parent by passing in pctx"))
+        pctx = orig.p1()
+
+    commitmsg = orig.description()
+
+    cache = {}
+    sha1s = re.findall(sha1re, commitmsg)
+    unfi = repo.unfiltered()
+    for sha1 in sha1s:
+        ctx = None
+        try:
+            ctx = unfi[sha1]
+        except error.RepoLookupError:
+            continue
+
+        if not ctx.obsolete():
+            continue
+
+        successors = compat.successorssets(repo, ctx.node(), cache)
+
+        # We can't make any assumptions about how to update the hash if the
+        # cset in question was split or diverged.
+        if len(successors) == 1 and len(successors[0]) == 1:
+            newsha1 = node.hex(successors[0][0])
+            commitmsg = commitmsg.replace(sha1, newsha1[:len(sha1)])
+        else:
+            repo.ui.note(_('The stale commit message reference to %s could '
+                           'not be updated\n') % sha1)
+
+    tr = repo.currenttransaction()
+    assert tr is not None
+    try:
+        r = _evolvemerge(repo, orig, dest, pctx, keepbranch)
+        if r[-1]: # some conflict
+            raise error.Abort(_('unresolved merge conflicts '
+                                '(see hg help resolve)'))
+        nodenew = _relocatecommit(repo, orig, commitmsg)
+    except error.Abort as exc:
+        with repo.dirstate.parentchange():
+            repo.setparents(repo['.'].node(), node.nullid)
+            repo.dirstate.write(tr)
+            # fix up dirstate for copies and renames
+            compat.duplicatecopies(repo, repo[None], dest.rev(), orig.p1().rev())
+
+        class LocalMergeFailure(MergeFailure, exc.__class__):
+            pass
+        exc.__class__ = LocalMergeFailure
+        tr.close() # to keep changes in this transaction (e.g. dirstate)
+        raise
+    _finalizerelocate(repo, orig, dest, nodenew, tr)
+    return nodenew
+
+def _relocatecommit(repo, orig, commitmsg):
+    if commitmsg is None:
+        commitmsg = orig.description()
+    extra = dict(orig.extra())
+    if 'branch' in extra:
+        del extra['branch']
+    extra['rebase_source'] = orig.hex()
+
+    backup = repo.ui.backupconfig('phases', 'new-commit')
+    try:
+        targetphase = max(orig.phase(), phases.draft)
+        repo.ui.setconfig('phases', 'new-commit', targetphase, 'evolve')
+        # Commit might fail if unresolved files exist
+        nodenew = repo.commit(text=commitmsg, user=orig.user(),
+                              date=orig.date(), extra=extra)
+    finally:
+        repo.ui.restoreconfig(backup)
+    return nodenew
+
+def _finalizerelocate(repo, orig, dest, nodenew, tr):
+    destbookmarks = repo.nodebookmarks(dest.node())
+    nodesrc = orig.node()
+    oldbookmarks = repo.nodebookmarks(nodesrc)
+    bmchanges = []
+
+    if nodenew is not None:
+        compat.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))],
+                             operation='evolve')
+        for book in oldbookmarks:
+            bmchanges.append((book, nodenew))
+    else:
+        compat.createmarkers(repo, [(repo[nodesrc], ())], operation='evolve')
+        # Behave like rebase, move bookmarks to dest
+        for book in oldbookmarks:
+            bmchanges.append((book, dest.node()))
+    for book in destbookmarks: # restore bookmark that rebase move
+        bmchanges.append((book, dest.node()))
+    if bmchanges:
+        compat.bookmarkapplychanges(repo, tr, bmchanges)
+
+def _evolvemerge(repo, orig, dest, pctx, keepbranch):
+    """Used by the evolve function to merge dest on top of pctx.
+    return the same tuple as merge.graft"""
+    if repo['.'].rev() != dest.rev():
+        merge.update(repo,
+                     dest,
+                     branchmerge=False,
+                     force=True)
+    if repo._activebookmark:
+        repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark)
+    bookmarksmod.deactivate(repo)
+    if keepbranch:
+        repo.dirstate.setbranch(orig.branch())
+    if util.safehasattr(repo, 'currenttopic'):
+        # uurrgs
+        # there no other topic setter yet
+        if not orig.topic() and repo.vfs.exists('topic'):
+                repo.vfs.unlink('topic')
+        else:
+            with repo.vfs.open('topic', 'w') as f:
+                f.write(orig.topic())
+
+    return merge.graft(repo, orig, pctx, ['destination', 'evolving'], True)
+
+instabilities_map = {
+    'contentdivergent': "content-divergent",
+    'phasedivergent': "phase-divergent"
+}
+
+def _selectrevs(repo, allopt, revopt, anyopt, targetcat):
+    """select troubles in repo matching according to given options"""
+    revs = set()
+    if allopt or revopt:
+        revs = repo.revs("%s()" % targetcat)
+        if revopt:
+            revs = scmutil.revrange(repo, revopt) & revs
+        elif not anyopt:
+            topic = getattr(repo, 'currenttopic', '')
+            if topic:
+                revs = repo.revs('topic(%s)', topic) & revs
+            elif targetcat == 'orphan':
+                revs = _aspiringdescendant(repo,
+                                           repo.revs('(.::) - obsolete()::'))
+                revs = set(revs)
+        if targetcat == 'contentdivergent':
+            # Pick one divergent per group of divergents
+            revs = _dedupedivergents(repo, revs)
+    elif anyopt:
+        revs = repo.revs('first(%s())' % (targetcat))
+    elif targetcat == 'orphan':
+        revs = set(_aspiringchildren(repo, repo.revs('(.::) - obsolete()::')))
+        if 1 < len(revs):
+            msg = "multiple evolve candidates"
+            hint = (_("select one of %s with --rev")
+                    % ', '.join([str(repo[r]) for r in sorted(revs)]))
+            raise error.Abort(msg, hint=hint)
+    elif instabilities_map.get(targetcat, targetcat) in repo['.'].instabilities():
+        revs = set([repo['.'].rev()])
+    return revs
+
+def _dedupedivergents(repo, revs):
+    """Dedupe the divergents revs in revs to get one from each group with the
+    lowest revision numbers
+    """
+    repo = repo.unfiltered()
+    res = set()
+    # To not reevaluate divergents of the same group once one is encountered
+    discarded = set()
+    for rev in revs:
+        if rev in discarded:
+            continue
+        divergent = repo[rev]
+        base, others = divergentdata(divergent)
+        othersrevs = [o.rev() for o in others]
+        res.add(min([divergent.rev()] + othersrevs))
+        discarded.update(othersrevs)
+    return res
+
+def divergentdata(ctx):
+    """return base, other part of a conflict
+
+    This only return the first one.
+
+    XXX this woobly function won't survive XXX
+    """
+    repo = ctx._repo.unfiltered()
+    for base in repo.set('reverse(allprecursors(%d))', ctx.rev()):
+        newer = compat.successorssets(ctx._repo, base.node())
+        # drop filter and solution including the original ctx
+        newer = [n for n in newer if n and ctx.node() not in n]
+        if newer:
+            return base, tuple(ctx._repo[o] for o in newer[0])
+    raise error.Abort(_("base of divergent changeset %s not found") % ctx,
+                      hint=_('this case is not yet handled'))
+
+def _aspiringdescendant(repo, revs):
+    """Return a list of changectx which can be stabilized on top of pctx or
+    one of its descendants recursively. Empty list if none can be found."""
+    target = set(revs)
+    result = set(target)
+    paths = collections.defaultdict(set)
+    for r in repo.revs('orphan() - %ld', revs):
+        for d in _possibledestination(repo, r):
+            paths[d].add(r)
+
+    result = set(target)
+    tovisit = list(revs)
+    while tovisit:
+        base = tovisit.pop()
+        for unstable in paths[base]:
+            if unstable not in result:
+                tovisit.append(unstable)
+                result.add(unstable)
+    return sorted(result - target)
+
+def _aspiringchildren(repo, revs):
+    """Return a list of changectx which can be stabilized on top of pctx or
+    one of its descendants. Empty list if none can be found."""
+    target = set(revs)
+    result = []
+    for r in repo.revs('orphan() - %ld', revs):
+        dest = _possibledestination(repo, r)
+        if target & dest:
+            result.append(r)
+    return result
+
+def _possibledestination(repo, rev):
+    """return all changesets that may be a new parent for REV"""
+    tonode = repo.changelog.node
+    parents = repo.changelog.parentrevs
+    torev = repo.changelog.rev
+    dest = set()
+    tovisit = list(parents(rev))
+    while tovisit:
+        r = tovisit.pop()
+        succsets = compat.successorssets(repo, tonode(r))
+        if not succsets:
+            tovisit.extend(parents(r))
+        else:
+            # We should probably pick only one destination from split
+            # (case where '1 < len(ss)'), This could be the currently tipmost
+            # but logic is less clear when result of the split are now on
+            # multiple branches.
+            for ss in succsets:
+                for n in ss:
+                    dest.add(torev(n))
+    return dest
+
+def _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat):
+    """Used by the evolve function to display an error message when
+    no troubles can be resolved"""
+    troublecategories = ['phasedivergent', 'contentdivergent', 'orphan']
+    unselectedcategories = [c for c in troublecategories if c != targetcat]
+    msg = None
+    hint = None
+
+    troubled = {
+        "orphan": repo.revs("orphan()"),
+        "contentdivergent": repo.revs("contentdivergent()"),
+        "phasedivergent": repo.revs("phasedivergent()"),
+        "all": repo.revs("troubled()"),
+    }
+
+    hintmap = {
+        'phasedivergent': _("do you want to use --phase-divergent"),
+        'phasedivergent+contentdivergent': _("do you want to use "
+                                             "--phase-divergent or"
+                                             " --content-divergent"),
+        'phasedivergent+orphan': _("do you want to use --phase-divergent"
+                                   " or --orphan"),
+        'contentdivergent': _("do you want to use --content-divergent"),
+        'contentdivergent+orphan': _("do you want to use --content-divergent"
+                                     " or --orphan"),
+        'orphan': _("do you want to use --orphan"),
+        'any+phasedivergent': _("do you want to use --any (or --rev) and"
+                                " --phase-divergent"),
+        'any+phasedivergent+contentdivergent': _("do you want to use --any"
+                                                 " (or --rev) and"
+                                                 " --phase-divergent or"
+                                                 " --content-divergent"),
+        'any+phasedivergent+orphan': _("do you want to use --any (or --rev)"
+                                       " and --phase-divergent or --orphan"),
+        'any+contentdivergent': _("do you want to use --any (or --rev) and"
+                                  " --content-divergent"),
+        'any+contentdivergent+orphan': _("do you want to use --any (or --rev)"
+                                         " and --content-divergent or "
+                                         "--orphan"),
+        'any+orphan': _("do you want to use --any (or --rev)"
+                        "and --orphan"),
+    }
+
+    if revopt:
+        revs = scmutil.revrange(repo, revopt)
+        if not revs:
+            msg = _("set of specified revisions is empty")
+        else:
+            msg = _("no %s changesets in specified revisions") % targetcat
+            othertroubles = []
+            for cat in unselectedcategories:
+                if revs & troubled[cat]:
+                    othertroubles.append(cat)
+            if othertroubles:
+                hint = hintmap['+'.join(othertroubles)]
+
+    elif anyopt:
+        msg = _("no %s changesets to evolve") % targetcat
+        othertroubles = []
+        for cat in unselectedcategories:
+            if troubled[cat]:
+                othertroubles.append(cat)
+        if othertroubles:
+            hint = hintmap['+'.join(othertroubles)]
+
+    else:
+        # evolve without any option = relative to the current wdir
+        if targetcat == 'orphan':
+            msg = _("nothing to evolve on current working copy parent")
+        else:
+            msg = _("current working copy parent is not %s") % targetcat
+
+        p1 = repo['.'].rev()
+        othertroubles = []
+        for cat in unselectedcategories:
+            if p1 in troubled[cat]:
+                othertroubles.append(cat)
+        if othertroubles:
+            hint = hintmap['+'.join(othertroubles)]
+        else:
+            length = len(troubled[targetcat])
+            if length:
+                hint = _("%d other %s in the repository, do you want --any "
+                         "or --rev") % (length, targetcat)
+            else:
+                othertroubles = []
+                for cat in unselectedcategories:
+                    if troubled[cat]:
+                        othertroubles.append(cat)
+                if othertroubles:
+                    hint = hintmap['any+' + ('+'.join(othertroubles))]
+                else:
+                    msg = _("no troubled changesets")
+
+    assert msg is not None
+    ui.write_err("%s\n" % msg)
+    if hint:
+        ui.write_err("(%s)\n" % hint)
+        return 2
+    else:
+        return 1
+
+def _preparelistctxs(items, condition):
+    return [item.hex() for item in items if condition(item)]
+
+def _formatctx(fm, ctx):
+    fm.data(node=ctx.hex())
+    fm.data(desc=ctx.description())
+    fm.data(date=ctx.date())
+    fm.data(user=ctx.user())
+
+def listtroubles(ui, repo, troublecategories, **opts):
+    """Print all the troubles for the repo (or given revset)"""
+    troublecategories = troublecategories or ['contentdivergent', 'orphan', 'phasedivergent']
+    showunstable = 'orphan' in troublecategories
+    showbumped = 'phasedivergent' in troublecategories
+    showdivergent = 'contentdivergent' in troublecategories
+
+    revs = repo.revs('+'.join("%s()" % t for t in troublecategories))
+    if opts.get('rev'):
+        revs = scmutil.revrange(repo, opts.get('rev'))
+
+    fm = ui.formatter('evolvelist', opts)
+    for rev in revs:
+        ctx = repo[rev]
+        unpars = _preparelistctxs(ctx.parents(), lambda p: p.orphan())
+        obspars = _preparelistctxs(ctx.parents(), lambda p: p.obsolete())
+        imprecs = _preparelistctxs(repo.set("allprecursors(%n)", ctx.node()),
+                                   lambda p: not p.mutable())
+        dsets = divergentsets(repo, ctx)
+
+        fm.startitem()
+        # plain formatter section
+        hashlen, desclen = 12, 60
+        desc = ctx.description()
+        if desc:
+            desc = desc.splitlines()[0]
+        desc = (desc[:desclen] + '...') if len(desc) > desclen else desc
+        fm.plain('%s: ' % ctx.hex()[:hashlen])
+        fm.plain('%s\n' % desc)
+        fm.data(node=ctx.hex(), rev=ctx.rev(), desc=desc, phase=ctx.phasestr())
+
+        for unpar in unpars if showunstable else []:
+            fm.plain('  %s: %s (%s parent)\n' % (TROUBLES['ORPHAN'],
+                                                 unpar[:hashlen],
+                                                 TROUBLES['ORPHAN']))
+        for obspar in obspars if showunstable else []:
+            fm.plain('  %s: %s (obsolete parent)\n' % (TROUBLES['ORPHAN'],
+                                                       obspar[:hashlen]))
+        for imprec in imprecs if showbumped else []:
+            fm.plain('  %s: %s (immutable precursor)\n' %
+                     (TROUBLES['PHASEDIVERGENT'], imprec[:hashlen]))
+
+        if dsets and showdivergent:
+            for dset in dsets:
+                fm.plain('  %s: ' % TROUBLES['CONTENTDIVERGENT'])
+                first = True
+                for n in dset['divergentnodes']:
+                    t = "%s (%s)" if first else " %s (%s)"
+                    first = False
+                    fm.plain(t % (node.hex(n)[:hashlen], repo[n].phasestr()))
+                comprec = node.hex(dset['commonprecursor'])[:hashlen]
+                fm.plain(" (precursor %s)\n" % comprec)
+        fm.plain("\n")
+
+        # templater-friendly section
+        _formatctx(fm, ctx)
+        troubles = []
+        for unpar in unpars:
+            troubles.append({'troubletype': TROUBLES['ORPHAN'],
+                             'sourcenode': unpar, 'sourcetype': 'orphanparent'})
+        for obspar in obspars:
+            troubles.append({'troubletype': TROUBLES['ORPHAN'],
+                             'sourcenode': obspar,
+                             'sourcetype': 'obsoleteparent'})
+        for imprec in imprecs:
+            troubles.append({'troubletype': TROUBLES['PHASEDIVERGENT'],
+                             'sourcenode': imprec,
+                             'sourcetype': 'immutableprecursor'})
+        for dset in dsets:
+            divnodes = [{'node': node.hex(n),
+                         'phase': repo[n].phasestr(),
+                        } for n in dset['divergentnodes']]
+            troubles.append({'troubletype': TROUBLES['CONTENTDIVERGENT'],
+                             'commonprecursor': node.hex(dset['commonprecursor']),
+                             'divergentnodes': divnodes})
+        fm.data(troubles=troubles)
+
+    fm.end()
+
+def _checkevolveopts(repo, opts):
+    """ check the options passed to `hg evolve` and warn for deprecation warning
+    if any """
+
+    if opts['continue']:
+        if opts['any']:
+            raise error.Abort(_('cannot specify both "--any" and "--continue"'))
+        if opts['all']:
+            raise error.Abort(_('cannot specify both "--all" and "--continue"'))
+        if opts['rev']:
+            raise error.Abort(_('cannot specify both "--rev" and "--continue"'))
+        if opts['stop']:
+            raise error.Abort(_('cannot specify both "--stop" and'
+                                ' "--continue"'))
+
+    if opts['stop']:
+        if opts['any']:
+            raise error.Abort(_('cannot specify both "--any" and "--stop"'))
+        if opts['all']:
+            raise error.Abort(_('cannot specify both "--all" and "--stop"'))
+        if opts['rev']:
+            raise error.Abort(_('cannot specify both "--rev" and "--stop"'))
+
+    if opts['rev']:
+        if opts['any']:
+            raise error.Abort(_('cannot specify both "--rev" and "--any"'))
+        if opts['all']:
+            raise error.Abort(_('cannot specify both "--rev" and "--all"'))
+
+    # Backward compatibility
+    if opts['unstable']:
+        msg = ("'evolve --unstable' is deprecated, "
+               "use 'evolve --orphan'")
+        repo.ui.deprecwarn(msg, '4.4')
+
+        opts['orphan'] = opts['divergent']
+
+    if opts['divergent']:
+        msg = ("'evolve --divergent' is deprecated, "
+               "use 'evolve --content-divergent'")
+        repo.ui.deprecwarn(msg, '4.4')
+
+        opts['content_divergent'] = opts['divergent']
+
+    if opts['bumped']:
+        msg = ("'evolve --bumped' is deprecated, "
+               "use 'evolve --phase-divergent'")
+        repo.ui.deprecwarn(msg, '4.4')
+
+        opts['phase_divergent'] = opts['bumped']
+
+    return opts
+
+def _cleanup(ui, repo, startnode, showprogress):
+    if showprogress:
+        ui.progress(_('evolve'), None)
+    if repo['.'] != startnode:
+        ui.status(_('working directory is now at %s\n') % repo['.'])
+
+def divergentsets(repo, ctx):
+    """Compute sets of commits divergent with a given one"""
+    cache = {}
+    base = {}
+    for n in compat.allprecursors(repo.obsstore, [ctx.node()]):
+        if n == ctx.node():
+            # a node can't be a base for divergence with itself
+            continue
+        nsuccsets = compat.successorssets(repo, n, cache)
+        for nsuccset in nsuccsets:
+            if ctx.node() in nsuccset:
+                # we are only interested in *other* successor sets
+                continue
+            if tuple(nsuccset) in base:
+                # we already know the latest base for this divergency
+                continue
+            base[tuple(nsuccset)] = n
+    divergence = []
+    for divset, b in base.iteritems():
+        divergence.append({
+            'divergentnodes': divset,
+            'commonprecursor': b
+        })
+
+    return divergence
+
+@eh.command(
+    '^evolve|stabilize|solve',
+    [('n', 'dry-run', False,
+      _('do not perform actions, just print what would be done')),
+     ('', 'confirm', False,
+      _('ask for confirmation before performing the action')),
+     ('A', 'any', False,
+      _('also consider troubled changesets unrelated to current working '
+        'directory')),
+     ('r', 'rev', [], _('solves troubles of these revisions')),
+     ('', 'bumped', False, _('solves only bumped changesets')),
+     ('', 'phase-divergent', False, _('solves only phase-divergent changesets')),
+     ('', 'divergent', False, _('solves only divergent changesets')),
+     ('', 'content-divergent', False, _('solves only content-divergent changesets')),
+     ('', 'unstable', False, _('solves only unstable changesets')),
+     ('', 'orphan', False, _('solves only orphan changesets (default)')),
+     ('a', 'all', False, _('evolve all troubled changesets related to the '
+                           'current  working directory and its descendants')),
+     ('c', 'continue', False, _('continue an interrupted evolution')),
+     ('', 'stop', False, _('stop the interrupted evolution')),
+     ('l', 'list', False, 'provide details on troubled changesets in the repo'),
+    ] + mergetoolopts,
+    _('[OPTIONS]...')
+)
+def evolve(ui, repo, **opts):
+    """solve troubled changesets in your repository
+
+    Modifying history can lead to various types of troubled changesets:
+    unstable, bumped, or divergent. The evolve command resolves your troubles
+    by executing one of the following actions:
+
+    - update working copy to a successor
+    - rebase an unstable changeset
+    - extract the desired changes from a bumped changeset
+    - fuse divergent changesets back together
+
+    If you pass no arguments, evolve works in automatic mode: it will execute a
+    single action to reduce instability related to your working copy. There are
+    two cases for this action. First, if the parent of your working copy is
+    obsolete, evolve updates to the parent's successor. Second, if the working
+    copy parent is not obsolete but has obsolete predecessors, then evolve
+    determines if there is an unstable changeset that can be rebased onto the
+    working copy parent in order to reduce instability.
+    If so, evolve rebases that changeset. If not, evolve refuses to guess your
+    intention, and gives a hint about what you might want to do next.
+
+    Any time evolve creates a changeset, it updates the working copy to the new
+    changeset. (Currently, every successful evolve operation involves an update
+    as well; this may change in future.)
+
+    Automatic mode only handles common use cases. For example, it avoids taking
+    action in the case of ambiguity, and it ignores unstable changesets that
+    are not related to your working copy.
+    It also refuses to solve bumped or divergent changesets unless you
+    explicitly request such behavior (see below).
+
+    Eliminating all instability around your working copy may require multiple
+    invocations of :hg:`evolve`. Alternately, use ``--all`` to recursively
+    select and evolve all unstable changesets that can be rebased onto the
+    working copy parent.
+    This is more powerful than successive invocations, since ``--all`` handles
+    ambiguous cases (e.g. unstable changesets with multiple children) by
+    evolving all branches.
+
+    When your repository cannot be handled by automatic mode, you might need to
+    use ``--rev`` to specify a changeset to evolve. For example, if you have
+    an unstable changeset that is not related to the working copy parent,
+    you could use ``--rev`` to evolve it. Or, if some changeset has multiple
+    unstable children, evolve in automatic mode refuses to guess which one to
+    evolve; you have to use ``--rev`` in that case.
+
+    Alternately, ``--any`` makes evolve search for the next evolvable changeset
+    regardless of whether it is related to the working copy parent.
+
+    You can supply multiple revisions to evolve multiple troubled changesets
+    in a single invocation. In revset terms, ``--any`` is equivalent to ``--rev
+    first(unstable())``. ``--rev`` and ``--all`` are mutually exclusive, as are
+    ``--rev`` and ``--any``.
+
+    ``hg evolve --any --all`` is useful for cleaning up instability across all
+    branches, letting evolve figure out the appropriate order and destination.
+
+    When you have troubled changesets that are not unstable, :hg:`evolve`
+    refuses to consider them unless you specify the category of trouble you
+    wish to resolve, with ``--bumped`` or ``--divergent``. These options are
+    currently mutually exclusive with each other and with ``--unstable``
+    (the default). You can combine ``--bumped`` or ``--divergent`` with
+    ``--rev``, ``--all``, or ``--any``.
+
+    You can also use the evolve command to list the troubles affecting your
+    repository by using the --list flag. You can choose to display only some
+    categories of troubles with the --unstable, --divergent or --bumped flags.
+    """
+
+    opts = _checkevolveopts(repo, opts)
+    # Options
+    contopt = opts['continue']
+    anyopt = opts['any']
+    allopt = opts['all']
+    startnode = repo['.']
+    dryrunopt = opts['dry_run']
+    confirmopt = opts['confirm']
+    revopt = opts['rev']
+    stopopt = opts['stop']
+
+    troublecategories = ['phase_divergent', 'content_divergent', 'orphan']
+    specifiedcategories = [t.replace('_', '')
+                           for t in troublecategories
+                           if opts[t]]
+    if opts['list']:
+        compat.startpager(ui, 'evolve')
+        listtroubles(ui, repo, specifiedcategories, **opts)
+        return
+
+    targetcat = 'orphan'
+    if 1 < len(specifiedcategories):
+        msg = _('cannot specify more than one trouble category to solve (yet)')
+        raise error.Abort(msg)
+    elif len(specifiedcategories) == 1:
+        targetcat = specifiedcategories[0]
+    elif repo['.'].obsolete():
+        displayer = compat.changesetdisplayer(ui, repo,
+                                              {'template': shorttemplate})
+        # no args and parent is obsolete, update to successors
+        try:
+            ctx = repo[utility._singlesuccessor(repo, repo['.'])]
+        except utility.MultipleSuccessorsError as exc:
+            repo.ui.write_err(_('parent is obsolete with multiple'
+                                ' successors:\n'))
+            for ln in exc.successorssets:
+                for n in ln:
+                    displayer.show(repo[n])
+            return 2
+
+        ui.status(_('update:'))
+        if not ui.quiet:
+            displayer.show(ctx)
+
+        if dryrunopt:
+            return 0
+        res = hg.update(repo, ctx.rev())
+        if ctx != startnode:
+            ui.status(_('working directory is now at %s\n') % ctx)
+        return res
+
+    ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'evolve')
+    troubled = set(repo.revs('troubled()'))
+
+    # Progress handling
+    seen = 1
+    count = allopt and len(troubled) or 1
+    showprogress = allopt
+
+    def progresscb():
+        if revopt or allopt:
+            ui.progress(_('evolve'), seen, unit=_('changesets'), total=count)
+
+    evolvestate = state.cmdstate(repo)
+    # Continuation handling
+    if contopt:
+        if not evolvestate:
+            raise error.Abort(_('no interrupted evolve to continue'))
+        evolvestate.load()
+        continueevolve(ui, repo, evolvestate, progresscb)
+        if evolvestate['command'] != 'evolve':
+            evolvestate.delete()
+            return
+        startnode = repo.unfiltered()[evolvestate['startnode']]
+        evolvestate.delete()
+    elif stopopt:
+        if not evolvestate:
+            raise error.Abort(_('no interrupted evolve to stop'))
+        evolvestate.load()
+        pctx = repo['.']
+        hg.updaterepo(repo, pctx.node(), True)
+        ui.status(_('stopped the interrupted evolve\n'))
+        ui.status(_('working directory is now at %s\n') % pctx)
+        evolvestate.delete()
+        return
+    else:
+        cmdutil.bailifchanged(repo)
+
+        revs = _selectrevs(repo, allopt, revopt, anyopt, targetcat)
+
+        if not revs:
+            return _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat)
+
+        # For the progress bar to show
+        count = len(revs)
+        # Order the revisions
+        if targetcat == 'orphan':
+            revs = _orderrevs(repo, revs)
+
+        # cbor does not know how to serialize sets, using list for skippedrevs
+        stateopts = {'category': targetcat, 'replacements': {}, 'revs': revs,
+                     'confirm': confirmopt, 'startnode': startnode.node(),
+                     'skippedrevs': [], 'command': 'evolve', 'orphanmerge': False}
+        evolvestate.addopts(stateopts)
+        for rev in revs:
+            curctx = repo[rev]
+            progresscb()
+            ret = _solveone(ui, repo, curctx, evolvestate, dryrunopt, confirmopt,
+                            progresscb, targetcat)
+            seen += 1
+            if ret[0]:
+                evolvestate['replacements'][curctx.node()] = [ret[1]]
+            else:
+                evolvestate['skippedrevs'].append(curctx.node())
+
+            if evolvestate['orphanmerge']:
+                # we were processing an orphan merge with both parents obsolete,
+                # stabilized for second parent, re-stabilize for the first parent
+                ret = _solveone(ui, repo, repo[ret[1]], evolvestate, dryrunopt,
+                                confirmopt, progresscb, targetcat)
+                if ret[0]:
+                    evolvestate['replacements'][curctx.node()] = [ret[1]]
+                else:
+                    evolvestate['skippedrevs'].append(curctx.node())
+
+                evolvestate['orphanmerge'] = False
+
+    progresscb()
+    _cleanup(ui, repo, startnode, showprogress)
+
+def continueevolve(ui, repo, evolvestate, progresscb):
+    """logic for handling of `hg evolve --continue`"""
+    orig = repo[evolvestate['current']]
+    with repo.wlock(), repo.lock():
+        ctx = orig
+        source = ctx.extra().get('source')
+        extra = {}
+        if source:
+            extra['source'] = source
+            extra['intermediate-source'] = ctx.hex()
+        else:
+            extra['source'] = ctx.hex()
+        user = ctx.user()
+        date = ctx.date()
+        message = ctx.description()
+        ui.status(_('evolving %d:%s "%s"\n') % (ctx.rev(), ctx,
+                                                message.split('\n', 1)[0]))
+        targetphase = max(ctx.phase(), phases.draft)
+        overrides = {('phases', 'new-commit'): targetphase}
+
+        ctxparents = orig.parents()
+        if len(ctxparents) == 2:
+            currentp1 = repo.dirstate.parents()[0]
+            p1obs = ctxparents[0].obsolete()
+            p2obs = ctxparents[1].obsolete()
+            # asumming that the parent of current wdir is successor of one
+            # of p1 or p2 of the original changeset
+            if p1obs and not p2obs:
+                # p1 is obsolete and p2 is not obsolete, current working
+                # directory parent should be successor of p1, so we should
+                # set dirstate parents to (succ of p1, p2)
+                with repo.dirstate.parentchange():
+                    repo.dirstate.setparents(currentp1,
+                                             ctxparents[1].node())
+            elif p2obs and not p1obs:
+                # p2 is obsolete and p1 is not obsolete, current working
+                # directory parent should be successor of p2, so we should
+                # set dirstate parents to (succ of p2, p1)
+                with repo.dirstate.parentchange():
+                    repo.dirstate.setparents(ctxparents[0].node(),
+                                             currentp1)
+
+            else:
+                # both the parents were obsoleted, if orphanmerge is set, we
+                # are processing the second parent first (to keep parent order)
+                if evolvestate.get('orphanmerge'):
+                    with repo.dirstate.parentchange():
+                        repo.dirstate.setparents(ctxparents[0].node(),
+                                                 currentp1)
+                pass
+
+        with repo.ui.configoverride(overrides, 'evolve-continue'):
+            node = repo.commit(text=message, user=user,
+                               date=date, extra=extra)
+
+        # resolving conflicts can lead to empty wdir and node can be None in
+        # those cases
+        newctx = repo[node] if node is not None else repo['.']
+        compat.createmarkers(repo, [(ctx, (newctx,))], operation='evolve')
+
+        # make sure we are continuing evolve and not `hg next --evolve`
+        if evolvestate['command'] == 'evolve':
+            evolvestate['replacements'][ctx.node()] = node
+            category = evolvestate['category']
+            confirm = evolvestate['confirm']
+            unfi = repo.unfiltered()
+            if evolvestate['orphanmerge']:
+                # processing a merge changeset with both parents obsoleted,
+                # stabilized on second parent, insert in front of list to
+                # re-process to stabilize on first parent
+                evolvestate['revs'].insert(0, repo[node].rev())
+                evolvestate['orphanmerge'] = False
+            for rev in evolvestate['revs']:
+                # XXX: prevent this lookup by storing nodes instead of revnums
+                curctx = unfi[rev]
+                if (curctx.node() not in evolvestate['replacements'] and
+                    curctx.node() not in evolvestate['skippedrevs']):
+                    newnode = _solveone(ui, repo, curctx, evolvestate, False,
+                                        confirm, progresscb, category)
+                    if newnode[0]:
+                        evolvestate['replacements'][curctx.node()] = newnode[1]
+                    else:
+                        evolvestate['skippedrevs'].append(curctx.node())
+        return
--- a/hgext3rd/evolve/evolvestate.py	Sat Jan 20 12:38:11 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-"""
-This file contains class to wrap the state for hg evolve command and other
-related logic.
-
-All the data related to the command state is stored as dictionary in the object.
-The class has methods using which the data can be stored to disk in
-.hg/evolvestate file.
-
-We store the data on disk in cbor, for which we use cbor library to serialize
-and deserialize data.
-"""
-
-from __future__ import absolute_import
-
-from .thirdparty import cbor
-
-from mercurial import (
-    util,
-)
-
-class evolvestate():
-    """a wrapper class to store the state of `hg evolve` command
-
-    All the data for the state is stored in the form of key-value pairs in a
-    dictionary.
-
-    The class object can write all the data to .hg/evolvestate file and also can
-    populate the object data reading that file
-    """
-
-    def __init__(self, repo, path='evolvestate', opts={}):
-        self._repo = repo
-        self.path = path
-        self.opts = opts
-
-    def __nonzero__(self):
-        return self.exists()
-
-    def __getitem__(self, key):
-        return self.opts[key]
-
-    def load(self):
-        """load the existing evolvestate file into the class object"""
-        op = self._read()
-        self.opts.update(op)
-
-    def addopts(self, opts):
-        """add more key-value pairs to the data stored by the object"""
-        self.opts.update(opts)
-
-    def save(self):
-        """write all the evolvestate data stored in .hg/evolvestate file
-
-        we use third-party library cbor to serialize data to write in the file.
-        """
-        with self._repo.vfs(self.path, 'wb', atomictemp=True) as fp:
-            cbor.dump(self.opts, fp)
-
-    def _read(self):
-        """reads the evolvestate file and returns a dictionary which contain
-        data in the same format as it was before storing"""
-        with self._repo.vfs(self.path, 'rb') as fp:
-            return cbor.load(fp)
-
-    def delete(self):
-        """drop the evolvestate file if exists"""
-        util.unlinkpath(self._repo.vfs.join(self.path), ignoremissing=True)
-
-    def exists(self):
-        """check whether the evolvestate file exists or not"""
-        return self._repo.vfs.exists(self.path)
--- a/hgext3rd/evolve/exthelper.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/exthelper.py	Wed Mar 21 16:35:18 2018 +0100
@@ -128,8 +128,11 @@
         revset.loadpredicate(ui, 'evolve', revsetpredicate)
 
         templatekeyword = registrar.templatekeyword()
-        for name, kw in self._templatekws:
-            templatekeyword(name)(kw)
+        for name, kw, requires in self._templatekws:
+            if requires is not None:
+                templatekeyword(name, requires=requires)(kw)
+            else:
+                templatekeyword(name)(kw)
         templatekw.loadkeyword(ui, 'evolve', templatekeyword)
 
         for ext, command, wrapper, opts in self._extcommandwrappers:
@@ -215,7 +218,7 @@
             return symbol
         return dec
 
-    def templatekw(self, keywordname):
+    def templatekw(self, keywordname, requires=None):
         """Decorated function is a template keyword
 
         The name of the keyword must be given as the decorator argument.
@@ -228,7 +231,7 @@
                 return 'babar'
         """
         def dec(keyword):
-            self._templatekws.append((keywordname, keyword))
+            self._templatekws.append((keywordname, keyword, requires))
             return keyword
         return dec
 
--- a/hgext3rd/evolve/firstmergecache.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/firstmergecache.py	Wed Mar 21 16:35:18 2018 +0100
@@ -49,11 +49,11 @@
 
         if util.safehasattr(repo, 'updatecaches'):
             @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None):
+            def updatecaches(self, tr=None, **kwargs):
                 if utility.shouldwarmcache(self, tr):
                     self.firstmergecache.update(self)
                     self.firstmergecache.save(self)
-                super(firstmergecacherepo, self).updatecaches(tr)
+                super(firstmergecacherepo, self).updatecaches(tr, **kwargs)
 
         else:
             def transaction(self, *args, **kwargs):
--- a/hgext3rd/evolve/legacy.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/legacy.py	Wed Mar 21 16:35:18 2018 +0100
@@ -30,6 +30,12 @@
 from mercurial import registrar
 from mercurial import util
 
+try:
+    from mercurial.utils.dateutil import makedate
+except ImportError as e:
+    # compat with hg < 4.6
+    from mercurial.util import makedate
+
 if util.safehasattr(registrar, 'command'):
     commandfunc = registrar.command
 else: # compat with hg < 4.3
@@ -105,7 +111,7 @@
                         prec = bin(objhex)
                         sucs = (suc == nullid) and [] or [suc]
                         meta = {
-                            'date': '%i %i' % util.makedate(),
+                            'date': '%i %i' % makedate(),
                             'user': ui.username(),
                             }
                         try:
--- a/hgext3rd/evolve/metadata.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/metadata.py	Wed Mar 21 16:35:18 2018 +0100
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-__version__ = '7.2.1'
-testedwith = '4.1.3 4.2.3 4.3.2 4.4.2'
+__version__ = '7.3.0'
+testedwith = '4.1.3 4.2.3 4.3.2 4.4.2 4.5.2'
 minimumhgversion = '4.1'
 buglink = 'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obscache.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/obscache.py	Wed Mar 21 16:35:18 2018 +0100
@@ -225,6 +225,8 @@
             self.clear(reset=True)
 
         starttime = timer()
+        revs = list(revs)
+        obsmarkers = list(obsmarkers)
         self._updatefrom(repo, revs, obsmarkers)
         duration = timer() - starttime
         repo.ui.log('evoext-cache', 'updated %s in %.4f seconds (%sr, %so)\n',
@@ -523,8 +525,8 @@
 
         if util.safehasattr(repo, 'updatecaches'):
             @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None):
-                super(obscacherepo, self).updatecaches(tr)
+            def updatecaches(self, tr=None, **kwargs):
+                super(obscacherepo, self).updatecaches(tr, **kwargs)
                 self.obsstore.obscache.update(repo)
                 self.obsstore.obscache.save(repo)
 
--- a/hgext3rd/evolve/obsdiscovery.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/obsdiscovery.py	Wed Mar 21 16:35:18 2018 +0100
@@ -44,10 +44,10 @@
     util,
     wireproto,
 )
-from mercurial.hgweb import hgweb_mod
 from mercurial.i18n import _
 
 from . import (
+    compat,
     exthelper,
     obscache,
     utility,
@@ -95,7 +95,8 @@
     common = set()
     undecided = set(probeset)
     totalnb = len(undecided)
-    ui.progress(_("comparing with other"), 0, total=totalnb)
+    ui.progress(_("comparing with other"), 0, total=totalnb,
+                unit=_("changesets"))
     _takefullsample = setdiscovery._takefullsample
     if remote.capable('_evoext_obshash_1'):
         getremotehash = remote.evoext_obshash1
@@ -114,7 +115,7 @@
 
         roundtrips += 1
         ui.progress(_("comparing with other"), totalnb - len(undecided),
-                    total=totalnb)
+                    total=totalnb, unit=_("changesets"))
         ui.debug("query %i; still undecided: %i, sample size is: %i\n"
                  % (roundtrips, len(undecided), len(sample)))
         # indices between sample and externalized version must match
@@ -175,7 +176,8 @@
 
     local.obsstore.rangeobshashcache.update(local)
     querycount = 0
-    ui.progress(_("comparing obsmarker with other"), querycount)
+    ui.progress(_("comparing obsmarker with other"), querycount,
+                unit=_("queries"))
     overflow = []
     while sample or overflow:
         if overflow:
@@ -230,7 +232,8 @@
                     addentry(new)
         assert nbsample == nbreplies
         querycount += 1
-        ui.progress(_("comparing obsmarker with other"), querycount)
+        ui.progress(_("comparing obsmarker with other"), querycount,
+                    unit=_("queries"))
     ui.progress(_("comparing obsmarker with other"), None)
     local.obsstore.rangeobshashcache.save(local)
     duration = timer() - starttime
@@ -597,11 +600,11 @@
 
         if util.safehasattr(repo, 'updatecaches'):
             @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None):
+            def updatecaches(self, tr=None, **kwargs):
                 if utility.shouldwarmcache(self, tr):
                     self.obsstore.rangeobshashcache.update(self)
                     self.obsstore.rangeobshashcache.save(self)
-                super(obshashrepo, self).updatecaches(tr)
+                super(obshashrepo, self).updatecaches(tr, **kwargs)
 
         else:
             def transaction(self, *args, **kwargs):
@@ -675,6 +678,7 @@
     except ValueError:
         self._abort(error.ResponseError(_("unexpected response:"), d))
 
+@compat.wireprotocommand(eh, 'evoext_obshashrange_v1', 'ranges')
 def srv_obshashrange_v1(repo, proto, ranges):
     ranges = wireproto.decodelist(ranges)
     ranges = [_decrange(r) for r in ranges]
@@ -698,17 +702,26 @@
     caps = orig(repo, proto)
     enabled = _useobshashrange(repo)
     if obsolete.isenabled(repo, obsolete.exchangeopt) and enabled:
+
+        # Compat hg 4.6+ (2f7290555c96)
+        bytesresponse = False
+        if util.safehasattr(caps, 'data'):
+            bytesresponse = True
+            caps = caps.data
+
         caps = caps.split()
-        caps.append('_evoext_obshashrange_v1')
+        caps.append(b'_evoext_obshashrange_v1')
         caps.sort()
-        caps = ' '.join(caps)
+        caps = b' '.join(caps)
+
+        # Compat hg 4.6+ (2f7290555c96)
+        if bytesresponse:
+            from mercurial import wireprototypes
+            caps = wireprototypes.bytesresponse(caps)
     return caps
 
 @eh.extsetup
 def obshashrange_extsetup(ui):
-    hgweb_mod.perms['evoext_obshashrange_v1'] = 'pull'
-
-    wireproto.commands['evoext_obshashrange_v1'] = (srv_obshashrange_v1, 'ranges')
     ###
     extensions.wrapfunction(wireproto, 'capabilities', _obshashrange_capabilities)
     # wrap command content
@@ -759,7 +772,8 @@
     cache = []
     unfi = repo.unfiltered()
     markercache = {}
-    repo.ui.progress(_("preparing locally"), 0, total=len(unfi))
+    repo.ui.progress(_("preparing locally"), 0, total=len(unfi),
+                     unit=_("changesets"))
     for i in unfi:
         ctx = unfi[i]
         entry = 0
@@ -789,7 +803,8 @@
             cache.append((ctx.node(), sha.digest()))
         else:
             cache.append((ctx.node(), node.nullid))
-        repo.ui.progress(_("preparing locally"), i, total=len(unfi))
+        repo.ui.progress(_("preparing locally"), i, total=len(unfi),
+                         unit=_("changesets"))
     repo.ui.progress(_("preparing locally"), None)
     return cache
 
@@ -828,9 +843,11 @@
     except ValueError:
         self._abort(error.ResponseError(_("unexpected response:"), d))
 
+@compat.wireprotocommand(eh, 'evoext_obshash', 'nodes')
 def srv_obshash(repo, proto, nodes):
     return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes)))
 
+@compat.wireprotocommand(eh, 'evoext_obshash1', 'nodes')
 def srv_obshash1(repo, proto, nodes):
     return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes),
                                 version=1))
@@ -840,20 +857,27 @@
     caps = orig(repo, proto)
     if (obsolete.isenabled(repo, obsolete.exchangeopt)
         and repo.ui.configbool('experimental', 'evolution.obsdiscovery', True)):
+
+        # Compat hg 4.6+ (2f7290555c96)
+        bytesresponse = False
+        if util.safehasattr(caps, 'data'):
+            bytesresponse = True
+            caps = caps.data
+
         caps = caps.split()
-        caps.append('_evoext_obshash_0')
-        caps.append('_evoext_obshash_1')
+        caps.append(b'_evoext_obshash_0')
+        caps.append(b'_evoext_obshash_1')
         caps.sort()
-        caps = ' '.join(caps)
+        caps = b' '.join(caps)
+
+        # Compat hg 4.6+ (2f7290555c96)
+        if bytesresponse:
+            from mercurial import wireprototypes
+            caps = wireprototypes.bytesresponse(caps)
     return caps
 
 @eh.extsetup
 def obshash_extsetup(ui):
-    hgweb_mod.perms['evoext_obshash'] = 'pull'
-    hgweb_mod.perms['evoext_obshash1'] = 'pull'
-
-    wireproto.commands['evoext_obshash'] = (srv_obshash, 'nodes')
-    wireproto.commands['evoext_obshash1'] = (srv_obshash1, 'nodes')
     extensions.wrapfunction(wireproto, 'capabilities', _obshash_capabilities)
     # wrap command content
     oldcap, args = wireproto.commands['capabilities']
--- a/hgext3rd/evolve/obsexchange.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/obsexchange.py	Wed Mar 21 16:35:18 2018 +0100
@@ -27,6 +27,12 @@
     wireproto,
 )
 
+try:
+    from mercurial import wireprotoserver
+    wireprotoserver.handlewsgirequest
+except (ImportError, AttributeError):
+    wireprotoserver = None
+
 from mercurial.hgweb import common as hgwebcommon
 
 from . import (
@@ -106,10 +112,22 @@
     """wrapper to advertise new capability"""
     caps = orig(repo, proto)
     if obsolete.isenabled(repo, obsolete.exchangeopt):
+
+        # Compat hg 4.6+ (2f7290555c96)
+        bytesresponse = False
+        if util.safehasattr(caps, 'data'):
+            bytesresponse = True
+            caps = caps.data
+
         caps = caps.split()
-        caps.append('_evoext_getbundle_obscommon')
+        caps.append(b'_evoext_getbundle_obscommon')
         caps.sort()
-        caps = ' '.join(caps)
+        caps = b' '.join(caps)
+
+        # Compat hg 4.6+ (2f7290555c96)
+        if bytesresponse:
+            from mercurial import wireprototypes
+            caps = wireprototypes.bytesresponse(caps)
     return caps
 
 @eh.extsetup
--- a/hgext3rd/evolve/obshistory.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/obshistory.py	Wed Mar 21 16:35:18 2018 +0100
@@ -10,11 +10,9 @@
 import re
 
 from mercurial import (
-    cmdutil,
     commands,
     error,
     graphmod,
-    mdiff,
     patch,
     obsolete,
     node as nodemod,
@@ -58,7 +56,7 @@
     ] + commands.formatteropts,
     _('hg olog [OPTION]... [REV]'))
 def cmdobshistory(ui, repo, *revs, **opts):
-    """show the obsolescence history of the specified revisions.
+    """show the obsolescence history of the specified revisions
 
     If no revision range is specified, we display the log for the current
     working copy parent.
@@ -97,7 +95,7 @@
     revs.reverse()
     _debugobshistoryrevs(ui, repo, revs, opts)
 
-class obsmarker_printer(cmdutil.changeset_printer):
+class obsmarker_printer(compat.changesetprinter):
     """show (available) information about a node
 
     We display the node, description (if available) and various information
@@ -173,7 +171,7 @@
     basename = "changeset-description"
     succname = "changeset-description"
 
-    d = mdiff.unidiff(basedesc, '', succdesc, '', basename, succname)
+    d = compat.strdiff(basedesc, succdesc, basename, succname)
     # mercurial 4.1 and before return the patch directly
     if not isinstance(d, tuple):
         patch = d
@@ -356,7 +354,7 @@
     displayer = obsmarker_printer(ui, repo.unfiltered(), matchfn, opts, buffered=True)
     edges = graphmod.asciiedges
     walker = _obshistorywalker(repo.unfiltered(), revs, opts.get('all', False))
-    cmdutil.displaygraph(ui, repo, walker, displayer, edges)
+    compat.displaygraph(ui, repo, walker, displayer, edges)
 
 def _debugobshistoryrevs(ui, repo, revs, opts):
     """ Display the obsolescence history for revset
@@ -404,7 +402,7 @@
              label="evolve.node")
     fm.plain(' ')
 
-    fm.write('rev', '(%d)', int(ctx),
+    fm.write('rev', '(%d)', ctx.rev(),
              label="evolve.rev")
     fm.plain(' ')
 
@@ -473,6 +471,11 @@
         fm.write('succnodes', '%s', nodes,
                  label="evolve.node")
 
+    operation = metadata.get('operation')
+    if operation:
+        fm.plain(' using ')
+        fm.write('operation', '%s', operation, label="evolve.operation")
+
     fm.plain(' by ')
 
     fm.write('user', '%s', metadata['user'],
--- a/hgext3rd/evolve/rewriteutil.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/rewriteutil.py	Wed Mar 21 16:35:18 2018 +0100
@@ -24,6 +24,7 @@
     phases,
     repair,
     revset,
+    util,
 )
 
 from mercurial.i18n import _
@@ -60,6 +61,10 @@
         msg = _("cannot %s the null revision") % (action)
         hint = _("no changeset checked out")
         raise error.Abort(msg, hint=hint)
+    if any(util.safehasattr(r, 'rev') for r in revs):
+        msg = "rewriteutil.precheck called with ctx not revs"
+        repo.ui.develwarn(msg)
+        revs = (r.rev() for r in revs)
     publicrevs = repo.revs('%ld and public()', revs)
     if publicrevs:
         summary = _formatrevs(repo, publicrevs)
--- a/hgext3rd/evolve/serveronly.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/serveronly.py	Wed Mar 21 16:35:18 2018 +0100
@@ -58,4 +58,6 @@
         evolveopts = 'all'
         repo.ui.setconfig('experimental', 'evolution', evolveopts)
     if obsolete.isenabled(repo, 'exchange'):
-        repo.ui.setconfig('server', 'bundle1', False)
+        # if no config explicitly set, disable bundle1
+        if not isinstance(repo.ui.config('server', 'bundle1'), str):
+            repo.ui.setconfig('server', 'bundle1', False)
--- a/hgext3rd/evolve/stablerangecache.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/stablerangecache.py	Wed Mar 21 16:35:18 2018 +0100
@@ -366,11 +366,11 @@
 
         if util.safehasattr(repo, 'updatecaches'):
             @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None):
+            def updatecaches(self, tr=None, **kwargs):
                 if utility.shouldwarmcache(self, tr):
                     self.stablerange.update(self)
                     self.stablerange.save(self)
-                super(stablerangerepo, self).updatecaches(tr)
+                super(stablerangerepo, self).updatecaches(tr, **kwargs)
 
         else:
             def transaction(self, *args, **kwargs):
--- a/hgext3rd/evolve/stablesort.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/stablesort.py	Wed Mar 21 16:35:18 2018 +0100
@@ -14,7 +14,6 @@
 
 from mercurial import (
     commands,
-    cmdutil,
     localrepo,
     error,
     node as nodemod,
@@ -76,7 +75,7 @@
         raise error.Abort('unknown sorting method: "%s"' % method,
                           hint='pick one of: %s' % valid_method)
 
-    displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
+    displayer = compat.changesetdisplayer(ui, repo, opts, buffered=True)
     kwargs = {}
     if opts['limit']:
         kwargs['limit'] = int(opts['limit'])
@@ -673,11 +672,11 @@
 
         if util.safehasattr(repo, 'updatecaches'):
             @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None):
+            def updatecaches(self, tr=None, **kwargs):
                 if utility.shouldwarmcache(self, tr):
                     self.stablesort.update(self)
                     self.stablesort.save(self)
-                super(stablesortrepo, self).updatecaches(tr)
+                super(stablesortrepo, self).updatecaches(tr, **kwargs)
 
         else:
             def transaction(self, *args, **kwargs):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/state.py	Wed Mar 21 16:35:18 2018 +0100
@@ -0,0 +1,135 @@
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+"""
+This file contains class to wrap the state for commands and other
+related logic.
+
+All the data related to the command state is stored as dictionary in the object.
+The class has methods using which the data can be stored to disk in a file under
+.hg/ directory.
+
+We store the data on disk in cbor, for which we use cbor library to serialize
+and deserialize data.
+"""
+
+from __future__ import absolute_import
+
+import errno
+import struct
+
+from .thirdparty import cbor
+
+from mercurial import (
+    error,
+    util,
+)
+
+from mercurial.i18n import _
+
+class cmdstate():
+    """a wrapper class to store the state of commands like `evolve`, `grab`
+
+    All the data for the state is stored in the form of key-value pairs in a
+    dictionary.
+
+    The class object can write all the data to a file in .hg/ directory and also
+    can populate the object data reading that file
+    """
+
+    def __init__(self, repo, path='evolvestate', opts={}):
+        self._repo = repo
+        self.path = path
+        self.opts = opts
+
+    def __nonzero__(self):
+        return self.exists()
+
+    def __getitem__(self, key):
+        return self.opts[key]
+
+    def __setitem__(self, key, value):
+        updates = {key: value}
+        self.opts.update(updates)
+
+    def load(self):
+        """load the existing evolvestate file into the class object"""
+        op = self._read()
+        if isinstance(op, dict):
+            self.opts.update(op)
+        elif self.path == 'evolvestate':
+            # it is the old evolvestate file
+            oldop = _oldevolvestateread(self._repo)
+            self.opts.update(oldop)
+
+    def addopts(self, opts):
+        """add more key-value pairs to the data stored by the object"""
+        self.opts.update(opts)
+
+    def save(self):
+        """write all the evolvestate data stored in .hg/evolvestate file
+
+        we use third-party library cbor to serialize data to write in the file.
+        """
+        with self._repo.vfs(self.path, 'wb', atomictemp=True) as fp:
+            cbor.dump(self.opts, fp)
+
+    def _read(self):
+        """reads the evolvestate file and returns a dictionary which contain
+        data in the same format as it was before storing"""
+        with self._repo.vfs(self.path, 'rb') as fp:
+            return cbor.load(fp)
+
+    def delete(self):
+        """drop the evolvestate file if exists"""
+        util.unlinkpath(self._repo.vfs.join(self.path), ignoremissing=True)
+
+    def exists(self):
+        """check whether the evolvestate file exists or not"""
+        return self._repo.vfs.exists(self.path)
+
+def _oldevolvestateread(repo):
+    """function to read the old evolvestate file
+
+    This exists for BC reasons."""
+    try:
+        f = repo.vfs('evolvestate')
+    except IOError as err:
+        if err.errno != errno.ENOENT:
+            raise
+    try:
+        versionblob = f.read(4)
+        if len(versionblob) < 4:
+            repo.ui.debug('ignoring corrupted evolvestate (file contains %i bits)'
+                          % len(versionblob))
+            return None
+        version = struct._unpack('>I', versionblob)[0]
+        if version != 0:
+            msg = _('unknown evolvestate version %i') % version
+            raise error.Abort(msg, hint=_('upgrade your evolve'))
+        records = []
+        data = f.read()
+        off = 0
+        end = len(data)
+        while off < end:
+            rtype = data[off]
+            off += 1
+            length = struct._unpack('>I', data[off:(off + 4)])[0]
+            off += 4
+            record = data[off:(off + length)]
+            off += length
+            if rtype == 't':
+                rtype, record = record[0], record[1:]
+            records.append((rtype, record))
+        state = {}
+        for rtype, rdata in records:
+            if rtype == 'C':
+                state['current'] = rdata
+            elif rtype.lower():
+                repo.ui.debug('ignore evolve state record type %s' % rtype)
+            else:
+                raise error.Abort(_('unknown evolvestate field type %r')
+                                  % rtype, hint=_('upgrade your evolve'))
+        return state
+    finally:
+        f.close()
--- a/hgext3rd/evolve/templatekw.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/templatekw.py	Wed Mar 21 16:35:18 2018 +0100
@@ -9,13 +9,13 @@
 """
 
 from . import (
+    compat,
     error,
     exthelper,
     obshistory
 )
 
 from mercurial import (
-    cmdutil,
     templatekw,
     node,
     util
@@ -34,21 +34,29 @@
         return 'obsolete'
     return ''
 
-@eh.templatekw('troubles')
-def showtroubles(**args):
-    """List of strings. Evolution troubles affecting the changeset
-    (zero or more of "unstable", "divergent" or "bumped")."""
-    ctx = args['ctx']
-    try:
-        # specify plural= explicitly to trigger TypeError on hg < 4.2
-        return templatekw.showlist('trouble', ctx.instabilities(), args,
-                                   plural='troubles')
-    except TypeError:
-        return templatekw.showlist('trouble', ctx.instabilities(), plural='troubles',
-                                   **args)
+if util.safehasattr(templatekw, 'compatlist'):
+    @eh.templatekw('troubles', requires=set(['ctx', 'templ']))
+    def showtroubles(context, mapping):
+        ctx = context.resource(mapping, 'ctx')
+        return templatekw.compatlist(context, mapping, 'trouble',
+                                     ctx.instabilities(), plural='troubles')
+else:
+    # older template API in hg < 4.6
+    @eh.templatekw('troubles')
+    def showtroubles(**args):
+        """List of strings. Evolution troubles affecting the changeset
+        (zero or more of "unstable", "divergent" or "bumped")."""
+        ctx = args['ctx']
+        try:
+            # specify plural= explicitly to trigger TypeError on hg < 4.2
+            return templatekw.showlist('trouble', ctx.instabilities(), args,
+                                       plural='troubles')
+        except TypeError:
+            return templatekw.showlist('trouble', ctx.instabilities(), plural='troubles',
+                                       **args)
 
 if util.safehasattr(templatekw, 'showpredecessors'):
-    eh.templatekw("precursors")(templatekw.showpredecessors)
+    templatekw.keywords["precursors"] = templatekw.showpredecessors
 else:
     # for version <= hg4.3
     def closestprecursors(repo, nodeid):
@@ -97,7 +105,7 @@
     return directsuccessorssets(repo, nodeid)
 
 if util.safehasattr(templatekw, 'showsuccessorssets'):
-    eh.templatekw("successors")(templatekw.showsuccessorssets)
+    templatekw.keywords["successors"] = templatekw.showsuccessorssets
 else:
     # for version <= hg4.3
 
@@ -252,13 +260,33 @@
 
     return "\n".join(lines)
 
-@eh.templatekw("obsfatedata")
-def showobsfatedata(repo, ctx, **args):
-    # Get the needed obsfate data
-    values = obsfatedata(repo, ctx)
+
+if util.safehasattr(templatekw, 'compatlist'):
+    @eh.templatekw('obsfatedata', requires=set(['ctx', 'templ']))
+    def showobsfatedata(context, mapping):
+        ctx = context.resource(mapping, 'ctx')
+        repo = ctx.repo()
+        values = obsfatedata(repo, ctx)
 
-    if values is None:
-        return templatekw.showlist("obsfatedata", [], args)
+        if values is None:
+            return templatekw.compatlist(context, mapping, "obsfatedata", [])
+        args = mapping.copy()
+        args.pop('ctx')
+        args['templ'] = context.resource(mapping, 'templ')
+        return _showobsfatedata(repo, ctx, values, **args)
+else:
+    # pre hg-4.6
+    @eh.templatekw("obsfatedata")
+    def showobsfatedata(repo, ctx, **args):
+        # Get the needed obsfate data
+        values = obsfatedata(repo, ctx)
+
+        if values is None:
+            return templatekw.showlist("obsfatedata", [], args)
+
+        return _showobsfatedata(repo, ctx, values, **args)
+
+def _showobsfatedata(repo, ctx, values, **args):
 
     # Format each successorset successors list
     for raw in values:
@@ -316,10 +344,10 @@
     def showobsfate(*args, **kwargs):
         return showobsfatedata(*args, **kwargs)
 
-if util.safehasattr(cmdutil.changeset_printer, '_showobsfate'):
+if util.safehasattr(compat.changesetprinter, '_showobsfate'):
     pass # already included by default
-elif util.safehasattr(cmdutil.changeset_printer, '_exthook'):
-    @eh.wrapfunction(cmdutil.changeset_printer, '_exthook')
+elif util.safehasattr(compat.changesetprinter, '_exthook'):
+    @eh.wrapfunction(compat.changesetprinter, '_exthook')
     def exthook(original, self, ctx):
         # Call potential other extensions
         original(self, ctx)
--- a/hgext3rd/evolve/utility.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/evolve/utility.py	Wed Mar 21 16:35:18 2018 +0100
@@ -5,8 +5,16 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+import collections
+
+from mercurial.i18n import _
+
 from mercurial.node import nullrev
 
+from . import (
+    compat,
+)
+
 shorttemplate = "[{label('evolve.rev', rev)}] {desc|firstline}\n"
 
 def obsexcmsg(ui, message, important=False):
@@ -66,3 +74,107 @@
     if maxrevs is not None and maxrevs < len(repo.unfiltered()):
         return False
     return True
+
+class MultipleSuccessorsError(RuntimeError):
+    """Exception raised by _singlesuccessor when multiple successor sets exists
+
+    The object contains the list of successorssets in its 'successorssets'
+    attribute to call to easily recover.
+    """
+
+    def __init__(self, successorssets):
+        self.successorssets = successorssets
+
+def builddependencies(repo, revs):
+    """returns dependency graphs giving an order to solve instability of revs
+    (see _orderrevs for more information on usage)"""
+
+    # For each troubled revision we keep track of what instability if any should
+    # be resolved in order to resolve it. Example:
+    # dependencies = {3: [6], 6:[]}
+    # Means that: 6 has no dependency, 3 depends on 6 to be solved
+    dependencies = {}
+    # rdependencies is the inverted dict of dependencies
+    rdependencies = collections.defaultdict(set)
+
+    for r in revs:
+        dependencies[r] = set()
+        for p in repo[r].parents():
+            try:
+                succ = _singlesuccessor(repo, p)
+            except MultipleSuccessorsError as exc:
+                dependencies[r] = exc.successorssets
+                continue
+            if succ in revs:
+                dependencies[r].add(succ)
+                rdependencies[succ].add(r)
+    return dependencies, rdependencies
+
+def _singlesuccessor(repo, p):
+    """returns p (as rev) if not obsolete or its unique latest successors
+
+    fail if there are no such successor"""
+
+    if not p.obsolete():
+        return p.rev()
+    obs = repo[p]
+    ui = repo.ui
+    newer = compat.successorssets(repo, obs.node())
+    # search of a parent which is not killed
+    while not newer:
+        ui.debug("stabilize target %s is plain dead,"
+                 " trying to stabilize on its parent\n" %
+                 obs)
+        obs = obs.parents()[0]
+        newer = compat.successorssets(repo, obs.node())
+    if len(newer) > 1 or len(newer[0]) > 1:
+        raise MultipleSuccessorsError(newer)
+
+    return repo[newer[0][0]].rev()
+
+def revselectionprompt(ui, repo, revs, customheader=""):
+    """function to prompt user to choose a revision from all the revs and return
+    that revision for further tasks
+
+    revs is a list of rev number of revision from which one revision should be
+    choosed by the user
+    customheader is a text which the caller wants as the header of the prompt
+    which will list revisions to select
+
+    returns value is:
+        rev number of revision choosed: if user choose a revision
+        None: if user entered a wrong input, user quit the prompt,
+              ui.interactive is not set
+    """
+
+    # ui.interactive is not set, fallback to default behavior and avoid showing
+    # the prompt
+    if not ui.configbool('ui', 'interactive'):
+        return None
+
+    promptmsg = customheader + "\n"
+    for idx, rev in enumerate(revs):
+        curctx = repo[rev]
+        revmsg = _("%d: [%s] %s\n" % (idx, curctx,
+                                      curctx.description().split("\n")[0]))
+        promptmsg += revmsg
+
+    promptmsg += _("q: quit the prompt\n")
+    promptmsg += _("enter the index of the revision you want to select:")
+    idxselected = ui.prompt(promptmsg)
+
+    intidx = None
+    try:
+        intidx = int(idxselected)
+    except ValueError:
+        if idxselected == 'q':
+            return None
+        ui.write_err(_("invalid value '%s' entered for index\n") % idxselected)
+        return None
+
+    if intidx >= len(revs) or intidx < 0:
+        # we can make this error message better
+        ui.write_err(_("invalid value '%d' entered for index\n") % intidx)
+        return None
+
+    return revs[intidx]
--- a/hgext3rd/serverminitopic.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/serverminitopic.py	Wed Mar 21 16:35:18 2018 +0100
@@ -68,7 +68,7 @@
     def branchinfo(self, rev):
         """return branch name and close flag for rev, using and updating
         persistent cache."""
-        phase = self._repo._phasecache.phase(self, rev)
+        phase = self._repo._phasecache.phase(self._repo, rev)
         if phase:
             ctx = self._repo[rev]
             return ctx.branch(), ctx.closesbranch()
@@ -163,7 +163,7 @@
         valid = super(_topiccache, self).validfor(repo)
         if not valid:
             return False
-        elif not mighttopic(repo) and self.phaseshash is None:
+        elif self.phaseshash is None:
             # phasehash at None means this is a branchmap
             # coming from a public only set
             return True
--- a/hgext3rd/topic/__init__.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/topic/__init__.py	Wed Mar 21 16:35:18 2018 +0100
@@ -134,13 +134,14 @@
 from . import (
     compat,
     constants,
+    destination,
+    discovery,
+    evolvebits,
     flow,
+    randomname,
     revset as topicrevset,
-    destination,
     stack,
     topicmap,
-    discovery,
-    randomname
 )
 
 if util.safehasattr(registrar, 'command'):
@@ -175,9 +176,9 @@
               'topic.active': 'green',
              }
 
-__version__ = '0.7.0'
+__version__ = '0.8.0'
 
-testedwith = '4.1.3 4.2.3 4.3.3 4.4.2'
+testedwith = '4.1.3 4.2.3 4.3.3 4.4.2 4.5.2'
 minimumhgversion = '4.1'
 buglink = 'https://bz.mercurial-scm.org/'
 
@@ -240,6 +241,12 @@
     revlist = stack.stack(self._repo, topic=topic)
     try:
         return revlist.index(self.rev())
+    except ValueError:
+        if self.obsolete():
+            succ = evolvebits._singlesuccessor(self._repo, self)
+            if succ not in revlist:
+                return None
+            return revlist.index(succ)
     except IndexError:
         # Lets move to the last ctx of the current topic
         return None
--- a/hgext3rd/topic/compat.py	Sat Jan 20 12:38:11 2018 +0100
+++ b/hgext3rd/topic/compat.py	Wed Mar 21 16:35:18 2018 +0100
@@ -7,6 +7,8 @@
 """
 from __future__ import absolute_import
 
+import functools
+
 from mercurial import (
     obsolete,
     scmutil,
@@ -27,6 +29,17 @@
 if successorssets is None:
     successorssets = obsolete.successorssets
 
+# Wrap obsolete.creatmarkers and make it accept but ignore "operation" argument
+# for hg < 4.3
+createmarkers = obsolete.createmarkers
+originalcreatemarkers = createmarkers
+while isinstance(originalcreatemarkers, functools.partial):
+    originalcreatemarkers = originalcreatemarkers.func
+if originalcreatemarkers.__code__.co_argcount < 6:
+    def createmarkers(repo, relations, flag=0, date=None, metadata=None,
+                      operation=None):
+        return obsolete.createmarkers(repo, relations, flag, date, metadata)
+
 def startpager(ui, cmd):
     """function to start a pager in case ui.pager() exists"""
     try:
@@ -44,4 +57,4 @@
     else:
         relations = [(repo[o], tuple(repo[n] for n in new))
                      for (o, new) in replacements.iteritems()]
-        obsolete.createmarkers(repo, relations)
+        createmarkers(repo, relations, operation=operation)
--- a/tests/test-discovery-obshashrange.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-discovery-obshashrange.t	Wed Mar 21 16:35:18 2018 +0100
@@ -189,7 +189,7 @@
   running python "*/dummyssh" user@dummy 'hg -R server serve --stdio' (glob)
   sending hello command
   sending between command
-  remote: 466
+  remote: * (glob)
   remote: capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v1 batch * (glob)
   remote: 1
   preparing listkeys for "phases"
@@ -224,7 +224,7 @@
   45f8b879de922f6a6e620ba04205730335b6fc7e
   sending unbundle command
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 172 bytes payload
+  bundle2-output-part: "replycaps" * bytes payload (glob)
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-output-part: "obsmarkers" streamed payload
--- a/tests/test-evolve-phase.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-evolve-phase.t	Wed Mar 21 16:35:18 2018 +0100
@@ -115,8 +115,10 @@
   $ echo c2 > a
   $ hg resolve -m
   (no more unresolved files)
+  continue: hg evolve --continue
   $ hg evolve -c
   evolving 2:13833940840c "c"
+  working directory is now at 3d2080c198e5
 
   $ hg glog
   @  5 - 3d2080c198e5 c (secret)
--- a/tests/test-evolve.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-evolve.t	Wed Mar 21 16:35:18 2018 +0100
@@ -794,10 +794,10 @@
                  user:               10
   marker size:
       format v1:
-          smallest length:           75
-          longer length:             76
-          median length:             76
-          mean length:               75
+          smallest length:           * (glob)
+          longer length:             * (glob)
+          median length:             * (glob)
+          mean length:               * (glob)
       format v0:
           smallest length:           * (glob)
           longer length:             * (glob)
@@ -1243,6 +1243,20 @@
   (use 'hg help' for the full list of commands or 'hg -v' for details)
   [255]
 
+Shows "use 'hg evolve' to..." hints iff the evolve command is enabled
+
+  $ hg --hidden up 14
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory parent is obsolete! (cce26b684bfe)
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolutioncommands=evolve
+  > EOF
+  $ hg --hidden up 15
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (27247fcb2df6)
+  (use 'hg evolve' to update to its successor: 24e63b319adf)
+
 Restore all of the evolution features
 
   $ cat >> $HGRCPATH <<EOF
@@ -1252,7 +1266,7 @@
 
 Check hg evolve --rev on singled out commit
   $ hg up 24e63b319adf -C
-  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ mkcommit j1
   $ mkcommit j2
   $ mkcommit j3
--- a/tests/test-fold.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-fold.t	Wed Mar 21 16:35:18 2018 +0100
@@ -8,7 +8,7 @@
   > [extensions]
   > evolve=
   > [ui]
-  > logtemplate = '{rev} - {node|short} {desc|firstline} [{author}] ({phase})\n'
+  > logtemplate = '{rev} - {node|short} {desc|firstline} [{author}] ({phase}) {bookmarks}\n'
   > EOF
 
   $ hg init fold-tests
@@ -16,6 +16,7 @@
   $ hg debugbuilddag .+3:branchpoint+4*branchpoint+2
   $ hg up 'desc("r7")'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg bookmark bm1
   $ hg log -G
   o  10 - a8407f9a3dc1 r10 [debugbuilddag] (draft)
   |
@@ -23,7 +24,7 @@
   |
   o  8 - abf57d94268b r8 [debugbuilddag] (draft)
   |
-  | @  7 - 4de32a90b66c r7 [debugbuilddag] (draft)
+  | @  7 - 4de32a90b66c r7 [debugbuilddag] (draft) bm1
   | |
   | o  6 - f69452c5b1af r6 [debugbuilddag] (draft)
   | |
@@ -87,6 +88,27 @@
   3 changesets folded
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
+Checking whether the bookmarks are moved or not
+
+  $ hg log -G
+  @  11 - 198b5c405d01 r5 [debugbuilddag] (draft) bm1
+  |
+  | o  10 - a8407f9a3dc1 r10 [debugbuilddag] (draft)
+  | |
+  | o  9 - 529dfc5bb875 r9 [debugbuilddag] (draft)
+  | |
+  | o  8 - abf57d94268b r8 [debugbuilddag] (draft)
+  | |
+  o |  4 - bebd167eb94d r4 [debugbuilddag] (draft)
+  |/
+  o  3 - 2dc09a01254d r3 [debugbuilddag] (draft)
+  |
+  o  2 - 01241442b3c2 r2 [debugbuilddag] (draft)
+  |
+  o  1 - 66f7d451a68b r1 [debugbuilddag] (draft)
+  |
+  o  0 - 1ea73414a91b r0 [debugbuilddag] (public)
+  
 (test inherited from test-evolve.t)
 
   $ hg fold --from 6 # want to run hg fold 6
@@ -95,7 +117,7 @@
   [255]
 
   $ hg log -G
-  @  11 - 198b5c405d01 r5 [debugbuilddag] (draft)
+  @  11 - 198b5c405d01 r5 [debugbuilddag] (draft) bm1
   |
   | o  10 - a8407f9a3dc1 r10 [debugbuilddag] (draft)
   | |
@@ -124,7 +146,7 @@
   $ hg log -G
   o  12 - b568edbee6e0 r8 [debugbuilddag] (draft)
   |
-  | @  11 - 198b5c405d01 r5 [debugbuilddag] (draft)
+  | @  11 - 198b5c405d01 r5 [debugbuilddag] (draft) bm1
   | |
   | o  4 - bebd167eb94d r4 [debugbuilddag] (draft)
   |/
@@ -144,8 +166,9 @@
   $ hg commit '-m r11'
   $ hg up '.^'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (leaving bookmark bm1)
   $ hg log -G
-  o  13 - 14d0e0da8e91 r11 [test] (draft)
+  o  13 - 14d0e0da8e91 r11 [test] (draft) bm1
   |
   | o  12 - b568edbee6e0 r8 [debugbuilddag] (draft)
   | |
@@ -190,7 +213,7 @@
   2 changesets folded
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -G
-  @  14 - 29b470a33594 r5 [Victor Rataxes <victor@rhino.savannah>] (draft)
+  @  14 - 29b470a33594 r5 [Victor Rataxes <victor@rhino.savannah>] (draft) bm1
   |
   | o  12 - b568edbee6e0 r8 [debugbuilddag] (draft)
   | |
@@ -209,7 +232,7 @@
   2 changesets folded
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -G
-  @  15 - 91880abed0f2 r4 [test] (draft)
+  @  15 - 91880abed0f2 r4 [test] (draft) bm1
   |
   | o  12 - b568edbee6e0 r8 [debugbuilddag] (draft)
   |/
--- a/tests/test-issue-5720.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-issue-5720.t	Wed Mar 21 16:35:18 2018 +0100
@@ -70,10 +70,12 @@
   $ echo c2 > a
   $ hg resolve -m
   (no more unresolved files)
+  continue: hg evolve --continue
 
 Continue the evolution
   $ hg evolve --continue
   evolving 2:13833940840c "c"
+  working directory is now at 3d2080c198e5
 
 Tip should stay in secret phase
   $ hg log -G -T "{rev}: {phase}"
--- a/tests/test-prev-next.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-prev-next.t	Wed Mar 21 16:35:18 2018 +0100
@@ -1,4 +1,6 @@
   $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > interactive = True
   > [extensions]
   > color =
   > EOF
@@ -36,8 +38,8 @@
 
 hg next -B should move active bookmark
   $ hg next -B --dry-run
-  hg update 1;
-  hg bookmark mark -r 1;
+  hg update 6e742c9127b3;
+  hg bookmark mark -r 6e742c9127b3;
   [1] added b
   $ hg next -B
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -47,7 +49,7 @@
 
 hg prev should unset active bookmark
   $ hg prev --dry-run
-  hg update 0;
+  hg update a154386e50d1;
   [0] added a
   $ hg prev
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -61,7 +63,7 @@
      mark                      1:6e742c9127b3
    * mark2                     0:a154386e50d1
   $ hg next --dry-run --color=debug
-  hg update 1;
+  hg update 6e742c9127b3;
   [[evolve.rev|1]] added b
   $ hg next
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -173,7 +175,7 @@
   no children
   [1]
   $ hg prev --dry-run --color=debug
-  hg update 1;
+  hg update 6e742c9127b3;
   [[evolve.rev|1]] added b
   $ hg prev
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -191,12 +193,11 @@
   move:[2] added c
   atop:[3] added b (2)
   hg rebase -r 4e26ef31f919 -d 9ad178109a19
-  working directory now at 9ad178109a19
 
 (add color output for smoke testing)
 
   $ hg next --evolve --color debug
-  move:[[evolve.rev|2]] added c
+  [evolve.operation|move:][[evolve.rev|2]] added c
   atop:[[evolve.rev|3]] added b (2)
   [ ui.status|working directory now at [evolve.node|e3b6d5df389b]]
 
@@ -212,12 +213,20 @@
   $ hg prev
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   [3] added b (2)
-  $ hg next
-  ambiguous next changeset:
-  [4] added c
+  $ hg next <<EOF
+  > 1
+  > EOF
+  ambiguous next changeset, choose one to update:
+  0: [e3b6d5df389b] added c
+  1: [9df671ccd2c7] added d
+  q: quit the prompt
+  enter the index of the revision you want to select: 1
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   [5] added d
-  explicitly update to one of them
-  [1]
+
+  $ hg prev
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [3] added b (2)
 
 next with ambiguity in aspiring children
 
@@ -227,24 +236,100 @@
   no children
   (2 unstable changesets to be evolved here, do you want --evolve?)
   [1]
-  $ hg next --evolve
-  ambiguous next (unstable) changeset:
-  [4] added c
-  [5] added d
-  (run 'hg evolve --rev REV' on one of them)
-  [1]
+  $ hg next --evolve <<EOF
+  > 0
+  > EOF
+  ambiguous next (unstable) changeset, choose one to evolve and update:
+  0: [e3b6d5df389b] added c
+  1: [9df671ccd2c7] added d
+  q: quit the prompt
+  enter the index of the revision you want to select: 0
+  move:[4] added c
+  atop:[6] added b (3)
+  working directory now at 5ce67c2407b0
+
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  7:5ce67c2407b0 added c
+  |
+  o  6:d7f119adc759 added b (3)
+  |
+  | o  5:9df671ccd2c7 added d
+  | |
+  | x  3:9ad178109a19 added b (2)
+  |/
+  o  0:a154386e50d1 added a
+  
+
   $ hg evolve -r 5
   move:[5] added d
   atop:[6] added b (3)
   working directory is now at 47ea25be8aea
 
+prev with multiple parents
+
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  8:47ea25be8aea added d
+  |
+  | o  7:5ce67c2407b0 added c
+  |/
+  o  6:d7f119adc759 added b (3)
+  |
+  o  0:a154386e50d1 added a
+  
+  $ hg merge -r 5ce67c2407b0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m "merge commit"
+
+  $ hg prev <<EOF
+  > q
+  > EOF
+  multiple parents, choose one to update:
+  0: [47ea25be8aea] added d
+  1: [5ce67c2407b0] added c
+  q: quit the prompt
+  enter the index of the revision you want to select: q
+  [8] added d
+  [7] added c
+  multiple parents, explicitly update to one
+  [1]
+
+  $ hg prev --config ui.interactive=False
+  [8] added d
+  [7] added c
+  multiple parents, explicitly update to one
+  [1]
+
+  $ hg prev <<EOF
+  > 1
+  > EOF
+  multiple parents, choose one to update:
+  0: [47ea25be8aea] added d
+  1: [5ce67c2407b0] added c
+  q: quit the prompt
+  enter the index of the revision you want to select: 1
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [7] added c
+
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  o    9:a4b8c25a87d3 merge commit
+  |\
+  | o  8:47ea25be8aea added d
+  | |
+  @ |  7:5ce67c2407b0 added c
+  |/
+  o  6:d7f119adc759 added b (3)
+  |
+  o  0:a154386e50d1 added a
+  
+
   $ cd ..
 
 prev and next should lock properly against other commands
 
   $ hg init repo
   $ cd repo
-  $ HGEDITOR=${TESTDIR}/fake-editor.sh
+  $ HGEDITOR="sh ${TESTDIR}/fake-editor.sh"
   $ echo hi > foo
   $ hg ci -Am 'one'
   adding foo
--- a/tests/test-prune.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-prune.t	Wed Mar 21 16:35:18 2018 +0100
@@ -358,10 +358,10 @@
                  user:                7
   marker size:
       format v1:
-          smallest length:           75
-          longer length:             75
-          median length:             75
-          mean length:               75
+          smallest length:          * (glob)
+          longer length:            * (glob)
+          median length:            * (glob)
+          mean length:              * (glob)
       format v0:
           smallest length:          * (glob)
           longer length:            * (glob)
--- a/tests/test-stabilize-conflict.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-stabilize-conflict.t	Wed Mar 21 16:35:18 2018 +0100
@@ -166,8 +166,10 @@
   $ safesed 's/dix/ten/' babar
   $ hg resolve --all -m
   (no more unresolved files)
+  continue: hg evolve --continue
   $ hg evolve --continue
   evolving 5:71c18f70c34f "babar count up to fifteen"
+  working directory is now at 1836b91c6c1d
   $ hg resolve -l
   $ hg log -G
   @  changeset:   8:1836b91c6c1d
--- a/tests/test-stabilize-result.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-stabilize-result.t	Wed Mar 21 16:35:18 2018 +0100
@@ -101,8 +101,10 @@
   [255]
   $ hg resolve -m a
   (no more unresolved files)
+  continue: hg evolve --continue
   $ hg evolve --continue
   evolving 5:3655f0f50885 "newer a"
+  working directory is now at 1cf0aacfd363
 
 Stabilize latecomer with different parent
 =========================================
@@ -130,8 +132,7 @@
 Get a successors of 8 on it
 
   $ hg grab 1cf0aacfd363
-  rebasing 8:1cf0aacfd363 "newer a"
-  ? files updated, 0 files merged, 0 files removed, 0 files unresolved (glob)
+  grabbing 8:1cf0aacfd363 "newer a"
 
 Add real change to the successors
 
@@ -143,7 +144,7 @@
   $ hg phase --hidden --public 1cf0aacfd363
   1 new bumped changesets
   $ glog
-  @  12:(73b15c7566e9|d5c7ef82d003)@default\(draft\) bk:\[\] newer a (re)
+  @  12:99c21c89bcef@default(draft) bk:[] newer a
   |
   o  9:7bc2f5967f5e@default(draft) bk:[] add c
   |
@@ -159,10 +160,10 @@
   $ hg evolve --any --dry-run --phase-divergent
   recreate:[12] newer a
   atop:[8] newer a
-  hg rebase --rev (73b15c7566e9|d5c7ef82d003) --dest 66719795a494; (re)
+  hg rebase --rev 99c21c89bcef --dest 66719795a494;
   hg update 1cf0aacfd363;
-  hg revert --all --rev (73b15c7566e9|d5c7ef82d003); (re)
-  hg commit --msg "bumped update to d5c7ef82d003"
+  hg revert --all --rev 99c21c89bcef;
+  hg commit --msg "bumped update to 99c21c89bcef"
   $ hg evolve --any --confirm --phase-divergent
   recreate:[12] newer a
   atop:[8] newer a
@@ -175,10 +176,10 @@
   perform evolve? [Ny] y
   rebasing to destination parent: 66719795a494
   computing new diff
-  committed as c2c1151aa854
-  working directory is now at c2c1151aa854
+  committed as e34e87ea7b83
+  working directory is now at e34e87ea7b83
   $ glog
-  @  14:c2c1151aa854@default(draft) bk:[] bumped update to 1cf0aacfd363:
+  @  14:e34e87ea7b83@default(draft) bk:[] bumped update to 1cf0aacfd363:
   |
   | o  9:7bc2f5967f5e@default(draft) bk:[] add c
   | |
--- a/tests/test-tutorial.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-tutorial.t	Wed Mar 21 16:35:18 2018 +0100
@@ -637,11 +637,10 @@
   $ hg up 'p1(10b8aeaa8cc8)' # going on "bathroom stuff" parent
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg grab fac207dec9f5 # moving "SPAM SPAM" to the working directory parent
-  rebasing 10:fac207dec9f5 "SPAM SPAM" (tip)
+  grabbing 10:fac207dec9f5 "SPAM SPAM"
   merging shopping
-  ? files updated, 0 files merged, 0 files removed, 0 files unresolved (glob)
   $ hg log -G
-  @  a224f2a4fb9f (draft): SPAM SPAM
+  @  57e9caedbcb8 (draft): SPAM SPAM
   |
   | o  10b8aeaa8cc8 (draft): bathroom stuff
   |/
@@ -709,7 +708,7 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID a224f2a4fb9f9f828f608959912229d7b38b26de
+  # Node ID 57e9caedbcb8575a01c128db9d1bcbd624ef2115
   # Parent  41aff6a42b7578ec7ec3cb2041633f1ca43cca96
   SPAM SPAM
   
@@ -742,14 +741,13 @@
 for simplicity sake we get the bathroom change in line again
 
   $ hg grab 10b8aeaa8cc8
-  rebasing 9:10b8aeaa8cc8 "bathroom stuff"
+  grabbing 9:10b8aeaa8cc8 "bathroom stuff"
   merging shopping
-  ? files updated, 0 files merged, 0 files removed, 0 files unresolved (glob)
   $ hg phase --draft .
   $ hg log -G
-  @  75954b8cd933 (draft): bathroom stuff
+  @  4710c0968793 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  57e9caedbcb8 (public): SPAM SPAM
   |
   o  41aff6a42b75 (public): adding fruit
   |
@@ -914,9 +912,9 @@
   1 new obsolescence markers
   (run 'hg update' to get a working copy)
   $ hg log -G
-  o  75954b8cd933 (public): bathroom stuff
+  o  4710c0968793 (public): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  57e9caedbcb8 (public): SPAM SPAM
   |
   o  41aff6a42b75 (public): adding fruit
   |
@@ -934,7 +932,7 @@
   $ hg rollback
   repository tip rolled back to revision 4 (undo pull)
   $ hg log -G
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  57e9caedbcb8 (public): SPAM SPAM
   |
   o  41aff6a42b75 (public): adding fruit
   |
@@ -969,9 +967,9 @@
   1 new obsolescence markers
   (run 'hg update' to get a working copy)
   $ hg log -G
-  o  75954b8cd933 (draft): bathroom stuff
+  o  4710c0968793 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  57e9caedbcb8 (public): SPAM SPAM
   |
   o  41aff6a42b75 (public): adding fruit
   |
@@ -987,7 +985,7 @@
 
 Remotely someone add a new changeset on top of the mutable "bathroom" on.
 
-  $ hg up 75954b8cd933 -q
+  $ hg up 4710c0968793 -q
   $ cat >> shopping << EOF
   > Giraffe
   > Rhino
@@ -1000,12 +998,14 @@
 
   $ cd ../local
   $ hg up 75954b8cd933 -q
+  abort: unknown revision '75954b8cd933'!
+  [255]
   $ sed -i'' -e 's/... More bathroom stuff to come/Bath Robe/' shopping
   $ hg commit --amend
   $ hg log -G
-  @  a44c85f957d3 (draft): bathroom stuff
+  @  682004e81e71 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  57e9caedbcb8 (public): SPAM SPAM
   |
   o  41aff6a42b75 (public): adding fruit
   |
@@ -1071,13 +1071,13 @@
 see both version showing up in the log.
 
   $ hg log -G
-  o  bf1b0d202029 (draft): animals
+  o  e4e4fa805d92 (draft): animals
   |
-  | @  a44c85f957d3 (draft): bathroom stuff
+  | @  682004e81e71 (draft): bathroom stuff
   | |
-  x |  75954b8cd933 (draft): bathroom stuff
+  x |  4710c0968793 (draft): bathroom stuff
   |/
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  57e9caedbcb8 (public): SPAM SPAM
   |
   o  41aff6a42b75 (public): adding fruit
   |
@@ -1121,7 +1121,7 @@
   $ hg push other
   pushing to $TESTTMP/other (glob)
   searching for changes
-  abort: push includes unstable changeset: bf1b0d202029!
+  abort: push includes unstable changeset: e4e4fa805d92!
   (use 'hg evolve' to get a stable history or --force to ignore warnings)
   [255]
  
@@ -1134,7 +1134,7 @@
   $ hg evolve --dry-run
   move:[15] animals
   atop:[14] bathroom stuff
-  hg rebase -r bf1b0d202029 -d a44c85f957d3
+  hg rebase -r e4e4fa805d92 -d 682004e81e71
 
 Let's do it
 
@@ -1142,16 +1142,16 @@
   move:[15] animals
   atop:[14] bathroom stuff
   merging shopping
-  working directory is now at ee942144f952
+  working directory is now at 2a2b36e14660
 
 The old version of bathroom is hidden again.
 
   $ hg log -G
-  @  ee942144f952 (draft): animals
+  @  2a2b36e14660 (draft): animals
   |
-  o  a44c85f957d3 (draft): bathroom stuff
+  o  682004e81e71 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  57e9caedbcb8 (public): SPAM SPAM
   |
   o  41aff6a42b75 (public): adding fruit
   |
@@ -1220,13 +1220,13 @@
 Now let's see where we are, and update to the successor.
 
   $ hg parents
-  bf1b0d202029 (draft): animals
-  working directory parent is obsolete! (bf1b0d202029)
-  (use 'hg evolve' to update to its successor: ee942144f952)
+  e4e4fa805d92 (draft): animals
+  working directory parent is obsolete! (e4e4fa805d92)
+  (use 'hg evolve' to update to its successor: 2a2b36e14660)
   $ hg evolve
   update:[8] animals
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  working directory is now at ee942144f952
+  working directory is now at 2a2b36e14660
 
 Relocating unstable change after prune
 ----------------------------------------------
@@ -1248,13 +1248,13 @@
   added 1 changesets with 1 changes to 1 files
   (run 'hg update' to get a working copy)
   $ hg log -G
-  o  99f039c5ec9e (draft): SPAM SPAM SPAM
+  o  fc41faf45288 (draft): SPAM SPAM SPAM
   |
-  @  ee942144f952 (draft): animals
+  @  2a2b36e14660 (draft): animals
   |
-  o  a44c85f957d3 (draft): bathroom stuff
+  o  682004e81e71 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  57e9caedbcb8 (public): SPAM SPAM
   |
   o  41aff6a42b75 (public): adding fruit
   |
@@ -1340,9 +1340,9 @@
 
 In the mean time I noticed you can't buy animals in a super market and I prune the animal changeset:
 
-  $ hg prune ee942144f952
+  $ hg prune 2a2b36e14660
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  working directory now at a44c85f957d3
+  working directory now at 682004e81e71
   1 changesets pruned
   1 new unstable changesets
 
@@ -1351,13 +1351,13 @@
 is neither dead or obsolete. My repository is in an unstable state again.
 
   $ hg log -G
-  o  99f039c5ec9e (draft): SPAM SPAM SPAM
+  o  fc41faf45288 (draft): SPAM SPAM SPAM
   |
-  x  ee942144f952 (draft): animals
+  x  2a2b36e14660 (draft): animals
   |
-  @  a44c85f957d3 (draft): bathroom stuff
+  @  682004e81e71 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  57e9caedbcb8 (public): SPAM SPAM
   |
   o  41aff6a42b75 (public): adding fruit
   |
@@ -1453,7 +1453,7 @@
 #endif
 
   $ hg log -r "orphan()"
-  99f039c5ec9e (draft): SPAM SPAM SPAM
+  fc41faf45288 (draft): SPAM SPAM SPAM
 
 #if docgraph-ext
   $ hg docgraph -r "orphan()" --sphinx-directive --rankdir LR #rest-ignore
@@ -1479,14 +1479,14 @@
   move:[17] SPAM SPAM SPAM
   atop:[14] bathroom stuff
   merging shopping
-  working directory is now at 40aa40daeefb
+  working directory is now at e6cfcb672150
 
   $ hg log -G
-  @  40aa40daeefb (draft): SPAM SPAM SPAM
+  @  e6cfcb672150 (draft): SPAM SPAM SPAM
   |
-  o  a44c85f957d3 (draft): bathroom stuff
+  o  682004e81e71 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  57e9caedbcb8 (public): SPAM SPAM
   |
   o  41aff6a42b75 (public): adding fruit
   |
--- a/tests/test-unstable.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-unstable.t	Wed Mar 21 16:35:18 2018 +0100
@@ -153,18 +153,17 @@
   
 
   $ hg evo --all --any --orphan
-  warning: no support for evolving merge changesets with two obsolete parents yet
-  (Redo the merge (6b4280e33286) and use `hg prune <old> --succ <new>` to obsolete the old one)
+  move:[3] merge
+  atop:[4] aprime
+  move:[6] merge
+  atop:[5] cprime
+  working directory is now at 2d30b910830b
   $ hg log -G
-  @  5:2db39fda7e2f@default(draft) cprime
-  |
-  | o  4:47127ea62e5f@default(draft) aprime
-  |/
-  | o    3:6b4280e33286@default(draft) merge
-  | |\
-  +---x  2:474da87dd33b@default(draft) add _c
+  @    7:2d30b910830b@default(draft) merge
+  |\
+  | o  5:2db39fda7e2f@default(draft) cprime
   | |
-  | x  1:b3264cec9506@default(draft) add _a
+  o |  4:47127ea62e5f@default(draft) aprime
   |/
   o  0:b4952fcf48cf@default(draft) add base
   
@@ -302,7 +301,7 @@
   
 
   $ hg evo --all --any --orphan
-  cannot solve split across two branches
+  could not solve instability, ambiguous destination: parent split across two branches
   $ hg log -G
   @  4:3c69ea6aa93e@default(draft) add bprimesplit2
   |
--- a/tests/test-wireproto.t	Sat Jan 20 12:38:11 2018 +0100
+++ b/tests/test-wireproto.t	Wed Mar 21 16:35:18 2018 +0100
@@ -194,6 +194,7 @@
   $ hg debugpushkey http://localhost:$HGPORT/ obsolete
   abort: HTTP Error 410: won't exchange obsmarkers through pushkey
   [255]
+  $ cat errors.log
   $ hg debugpushkey ssh://user@dummy/server obsolete
   remote: abort: won't exchange obsmarkers through pushkey
   remote: (upgrade your client or server to use the bundle2 protocol)