changeset 2626:606722a686ef stable

merge with default
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Wed, 31 May 2017 14:12:33 +0200
parents c0bfddf89c62 (current diff) 537058b433ef (diff)
children d3d686f05458 6da4ca7b3e4f
files hgext3rd/evolve/templatekw.py tests/test-discovery-obshashrange.t tests/test-metaedit.t tests/test-stablesort-criss-cross.t
diffstat 34 files changed, 2840 insertions(+), 6760 deletions(-) [+]
line wrap: on
line diff
--- a/README
+++ b/README
@@ -112,6 +112,26 @@
 Changelog
 =========
 
+6.3.0 - in progress
+-------------------
+
+ - olog: add an 'obslog' alias
+ - olog: add an '--all' option to show the whole obsolescence history tree.
+ - evolution: add an experiment to track the effect of rewrites.
+   (See hg help - evolve for details)
+ - exchange: fix the "relevant-markers" algorithm to include inline prune.
+   This will impact discovery of obsmarkers between server and client if one
+   still uses the old algorithm. Please upgrade both clients and servers as
+   soon as possible.
+   (See changeset 176d1a0ce385 in core Mercurial for details)
+ - obsdiscovery: add a config flag to disable all obsmarkers discovery
+   (See hg help - evolve for details)
+ - template: add a 'precursors' template that display the closests precursors of changesets
+ - template: add a 'successors' template that display the closests successors of changesets
+ - template: add a 'obsfate' template that display how a changeset has evolved
+ - new discovery experiment: add options to restrict memory consumption on
+   large repository (see "hg help -e evolve" for details).
+
 6.2.2 - in progress
 -------------------
 
--- a/hgext3rd/evolve/__init__.py
+++ b/hgext3rd/evolve/__init__.py
@@ -44,6 +44,12 @@
     # * abort: abort the push
     auto-publish = ignore
 
+    # For some large repository with few markers, the current  for obsolescence
+    # markers discovery can get in the way. You can disable it with the
+    # configuration option below. This means all pushes and pulls will
+    # re-exchange all markers every time.
+    evolution.obsdiscovery = yes
+
 Obsolescence Markers Discovery Experiment
 =========================================
 
@@ -77,6 +83,30 @@
 
     [extensions]
     blackbox =
+
+Finally some extra option are available to help tame the experimental
+implementation of some of the algorithms:
+
+    [experimental]
+    # restrict cache size to reduce memory consumption
+    obshashrange.lru-size = 2000 # default is 2000
+
+Effect Flag Experiment
+======================
+
+We are experimenting with a way to register what changed between a precursor
+and its successors (content, description, parent, etc...). For example, having
+this information is helpful to show what changed between an obsolete changeset
+and its tipmost successors.
+
+The following config control the experiment::
+
+  [experimental]
+  # activate the registration of effect flags in obs markers
+  evolution.effect-flags = yes
+
+The effect flags are shown in the obglog command output without particular
+configuration of you want to inspect them.
 """
 
 evolutionhelptext = """
@@ -160,12 +190,11 @@
     lock as lockmod,
     merge,
     node,
+    obsolete,
     patch,
     phases,
     revset,
     scmutil,
-    templatekw,
-    obsolete
 )
 
 from mercurial.commands import walkopts, commitopts, commitopts2, mergetoolopts
@@ -174,14 +203,16 @@
 
 from . import (
     checkheads,
+    compat,
     debugcmd,
     exthelper,
     metadata,
     obscache,
     obsexchange,
+    obshistory,
     safeguard,
+    templatekw,
     utility,
-    obshistory
 )
 
 __version__ = metadata.__version__
@@ -224,6 +255,8 @@
 eh.merge(safeguard.eh)
 eh.merge(obscache.eh)
 eh.merge(obshistory.eh)
+eh.merge(templatekw.eh)
+eh.merge(compat.eh)
 uisetup = eh.final_uisetup
 extsetup = eh.final_extsetup
 reposetup = eh.final_reposetup
@@ -515,29 +548,6 @@
     s.sort()
     return subset & s
 
-### template keywords
-# XXX it does not handle troubles well :-/
-
-@eh.templatekw('obsolete')
-def obsoletekw(repo, ctx, templ, **args):
-    """:obsolete: String. Whether the changeset is ``obsolete``.
-    """
-    if ctx.obsolete():
-        return 'obsolete'
-    return ''
-
-@eh.templatekw('troubles')
-def showtroubles(**args):
-    """:troubles: 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.troubles(), args,
-                                   plural='troubles')
-    except TypeError:
-        return templatekw.showlist('trouble', ctx.troubles(), plural='troubles',
-                                   **args)
 
 #####################################################################
 ### Various trouble warning                                       ###
@@ -545,29 +555,6 @@
 
 # This section take care of issue warning to the user when troubles appear
 
-
-def _getobsoletereason(repo, revnode):
-    """ Return a tuple containing:
-    - the reason a revision is obsolete (diverged, pruned or superseed)
-    - the list of successors short node if the revision is neither pruned
-    or has diverged
-    """
-    successorssets = obsolete.successorssets(repo, revnode)
-
-    if len(successorssets) == 0:
-        # The commit has been pruned
-        return ('pruned', [])
-    elif len(successorssets) > 1:
-        return ('diverged', [])
-    else:
-        # No divergence, only one set of successors
-        successors = [node.short(node_id) for node_id in successorssets[0]]
-
-        if len(successors) == 1:
-            return ('superseed', successors)
-        else:
-            return ('superseed_split', successors)
-
 def _warnobsoletewc(ui, repo):
     rev = repo['.']
 
@@ -584,7 +571,7 @@
         return
 
     # Show a warning for helping the user to solve the issue
-    reason, successors = _getobsoletereason(repo, rev.node())
+    reason, successors = obshistory._getobsfateandsuccs(repo, rev.node())
 
     if reason == 'pruned':
         solvemsg = _("use 'hg evolve' to update to its parent successor")
@@ -622,7 +609,7 @@
 
             unfilteredrepo = repo.unfiltered()
             rev = unfilteredrepo[changeid]
-            reason, successors = _getobsoletereason(unfilteredrepo, rev.node())
+            reason, successors = obshistory._getobsfateandsuccs(unfilteredrepo, rev.node())
 
             # Be more precise in cqse the revision is superseed
             if reason == 'superseed':
new file mode 100644
--- /dev/null
+++ b/hgext3rd/evolve/compat.py
@@ -0,0 +1,57 @@
+# Copyright 2017 Octobus <contact@octobus.net>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""
+Compatibility module
+"""
+
+from mercurial import (
+    hg,
+    obsolete
+)
+
+from . import (
+    exthelper,
+)
+
+eh = exthelper.exthelper()
+
+if not hasattr(hg, '_copycache'):
+    # exact copy of relevantmarkers as in Mercurial-176d1a0ce385
+    # this fixes relevant markers computation for version < hg-4.3
+    @eh.wrapfunction(obsolete.obsstore, 'relevantmarkers')
+    def relevantmarkers(orig, self, nodes):
+        """return a set of all obsolescence markers relevant to a set of nodes.
+
+        "relevant" to a set of nodes mean:
+
+        - marker that use this changeset as successor
+        - prune marker of direct children on this changeset
+        - recursive application of the two rules on precursors of these markers
+
+        It is a set so you cannot rely on order.
+
+        Backport of mercurial changeset 176d1a0ce385 for version < 4.3
+        """
+
+        pendingnodes = set(nodes)
+        seenmarkers = set()
+        seennodes = set(pendingnodes)
+        precursorsmarkers = self.precursors
+        succsmarkers = self.successors
+        children = self.children
+        while pendingnodes:
+            direct = set()
+            for current in pendingnodes:
+                direct.update(precursorsmarkers.get(current, ()))
+                pruned = [m for m in children.get(current, ()) if not m[1]]
+                direct.update(pruned)
+                pruned = [m for m in succsmarkers.get(current, ()) if not m[1]]
+                direct.update(pruned)
+            direct -= seenmarkers
+            pendingnodes = set([m[0] for m in direct])
+            seenmarkers |= direct
+            pendingnodes -= seennodes
+            seennodes |= pendingnodes
+        return seenmarkers
--- a/hgext3rd/evolve/debugcmd.py
+++ b/hgext3rd/evolve/debugcmd.py
@@ -10,7 +10,10 @@
 #  * We could have the same code in core as `hg debugobsolete --stat`,
 #  * We probably want a way for the extension to hook in for extra data.
 
-from mercurial import node
+from mercurial import (
+    obsolete,
+    node,
+)
 
 from mercurial.i18n import _
 
@@ -41,7 +44,8 @@
     store = repo.obsstore
     unfi = repo.unfiltered()
     nm = unfi.changelog.nodemap
-    ui.write(_('markers total:              %9i\n') % len(store._all))
+    nbmarkers = len(store._all)
+    ui.write(_('markers total:              %9i\n') % nbmarkers)
     sucscount = [0, 0, 0, 0]
     known = 0
     parentsdata = 0
@@ -51,6 +55,8 @@
     clustersmap = {}
     # same data using parent information
     pclustersmap = {}
+    size_v0 = []
+    size_v1 = []
     for mark in store:
         if mark[0] in nm:
             known += 1
@@ -72,6 +78,8 @@
         # same with parent data
         nodes.update(parents)
         _updateclustermap(nodes, mark, pclustersmap)
+        size_v0.append(len(obsolete._fm0encodeonemarker(mark)))
+        size_v1.append(len(obsolete._fm1encodeonemarker(mark)))
 
     # freezing the result
     for c in clustersmap.values():
@@ -95,6 +103,27 @@
     for key in sorted(metakeys):
         ui.write(('    %15s:        %9i\n' % (key, metakeys[key])))
 
+    size_v0.sort()
+    size_v1.sort()
+    if size_v0:
+        ui.write('marker size:\n')
+        # format v1
+        ui.write('    format v1:\n')
+        ui.write(('        smallest length:    %9i\n' % size_v1[0]))
+        ui.write(('        longer length:      %9i\n' % size_v1[-1]))
+        median = size_v1[nbmarkers // 2]
+        ui.write(('        median length:      %9i\n' % median))
+        mean = sum(size_v1) // nbmarkers
+        ui.write(('        mean length:        %9i\n' % mean))
+        # format v0
+        ui.write('    format v0:\n')
+        ui.write(('        smallest length:    %9i\n' % size_v0[0]))
+        ui.write(('        longer length:      %9i\n' % size_v0[-1]))
+        median = size_v0[nbmarkers // 2]
+        ui.write(('        median length:      %9i\n' % median))
+        mean = sum(size_v0) // nbmarkers
+        ui.write(('        mean length:        %9i\n' % mean))
+
     allclusters = list(set(clustersmap.values()))
     allclusters.sort(key=lambda x: len(x[1]))
     ui.write(('disconnected clusters:      %9i\n' % len(allclusters)))
--- a/hgext3rd/evolve/metadata.py
+++ b/hgext3rd/evolve/metadata.py
@@ -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__ = '6.2.2.dev'
+__version__ = '6.3.0.dev'
 testedwith = '3.8.4 3.9.2 4.0.2 4.1.2 4.2'
 minimumhgversion = '3.8'
 buglink = 'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obsdiscovery.py
+++ b/hgext3rd/evolve/obsdiscovery.py
@@ -72,97 +72,6 @@
 eh.merge(stablerange.eh)
 obsexcmsg = utility.obsexcmsg
 
-##########################################
-###  trigger discovery during exchange ###
-##########################################
-
-@eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers')
-def _pushdiscoveryobsmarkers(orig, pushop):
-    if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
-        and pushop.repo.obsstore
-        and 'obsolete' in pushop.remote.listkeys('namespaces')):
-        repo = pushop.repo
-        obsexcmsg(repo.ui, "computing relevant nodes\n")
-        revs = list(repo.revs('::%ln', pushop.futureheads))
-        unfi = repo.unfiltered()
-        cl = unfi.changelog
-        if not pushop.remote.capable('_evoext_obshash_0'):
-            # do not trust core yet
-            # return orig(pushop)
-            nodes = [cl.node(r) for r in revs]
-            if nodes:
-                obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n"
-                                   % len(nodes))
-                pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
-            else:
-                obsexcmsg(repo.ui, "markers already in sync\n")
-                pushop.outobsmarkers = []
-                pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
-            return
-
-        common = []
-        missing = None
-        obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
-                           % len(revs))
-        commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads))
-        if _canobshashrange(repo, pushop.remote):
-            missing = findmissingrange(pushop.ui, unfi, pushop.remote,
-                                       commonrevs)
-        else:
-            common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote,
-                                          commonrevs)
-        if missing is None:
-            revs = list(unfi.revs('%ld - (::%ln)', revs, common))
-            nodes = [cl.node(r) for r in revs]
-        else:
-            revs = list(repo.revs('only(%ln, %ln)', pushop.futureheads,
-                        pushop.outgoing.commonheads))
-            nodes = [cl.node(r) for r in revs]
-            nodes += missing
-
-        if nodes:
-            obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n"
-                               % len(nodes))
-            pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
-        else:
-            obsexcmsg(repo.ui, "markers already in sync\n")
-            pushop.outobsmarkers = []
-
-@eh.extsetup
-def _installobsmarkersdiscovery(ui):
-    olddisco = exchange.pushdiscoverymapping['obsmarker']
-
-    def newdisco(pushop):
-        _pushdiscoveryobsmarkers(olddisco, pushop)
-    exchange.pushdiscoverymapping['obsmarker'] = newdisco
-
-def buildpullobsmarkersboundaries(pullop, bundle2=True):
-    """small function returning the argument for pull markers call
-    may to contains 'heads' and 'common'. skip the key for None.
-
-    It is a separed function to play around with strategy for that."""
-    repo = pullop.repo
-    remote = pullop.remote
-    unfi = repo.unfiltered()
-    revs = unfi.revs('::(%ln - null)', pullop.common)
-    boundaries = {'heads': pullop.pulledsubset}
-    if not revs: # nothing common
-        boundaries['common'] = [node.nullid]
-        return boundaries
-
-    if bundle2 and _canobshashrange(repo, remote):
-        obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
-                  % len(revs))
-        boundaries['missing'] = findmissingrange(repo.ui, repo, pullop.remote,
-                                                 revs)
-    elif remote.capable('_evoext_obshash_0'):
-        obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
-                           % len(revs))
-        boundaries['common'] = findcommonobsmarkers(repo.ui, repo, remote, revs)
-    else:
-        boundaries['common'] = [node.nullid]
-    return boundaries
-
 ##################################
 ###  Code performing discovery ###
 ##################################
@@ -439,7 +348,7 @@
 
 class _obshashcache(obscache.dualsourcecache):
 
-    _schemaversion = 1
+    _schemaversion = 2
 
     _cachename = 'evo-ext-obshashrange' # used for error message
 
@@ -794,6 +703,9 @@
 # the obshash of its parents.  This is similar to what happend for changeset
 # node where the parent is used in the computation
 
+def _canobshashtree(repo, remote):
+    return remote.capable('_evoext_obshash_0')
+
 @eh.command(
     'debugobsrelsethashtree',
     [('', 'v0', None, 'hash on marker format "0"'),
@@ -925,3 +837,126 @@
     def newcap(repo, proto):
         return _obshash_capabilities(oldcap, repo, proto)
     wireproto.commands['capabilities'] = (newcap, args)
+
+##########################################
+###  trigger discovery during exchange ###
+##########################################
+
+def _dopushmarkers(pushop):
+    return (# we have any markers to push
+            pushop.repo.obsstore
+            # exchange of obsmarkers is enabled locally
+            and obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
+            # remote server accept markers
+            and 'obsolete' in pushop.remote.listkeys('namespaces'))
+
+def _pushobshashrange(pushop, commonrevs):
+    repo = pushop.repo.unfiltered()
+    remote = pushop.remote
+    missing = findmissingrange(pushop.ui, repo, remote, commonrevs)
+    missing += pushop.outgoing.missing
+    return missing
+
+def _pushobshashtree(pushop, commonrevs):
+    repo = pushop.repo.unfiltered()
+    remote = pushop.remote
+    node = repo.changelog.node
+    common = findcommonobsmarkers(pushop.ui, repo, remote, commonrevs)
+    revs = list(repo.revs('only(%ln, %ln)', pushop.futureheads, common))
+    return [node(r) for r in revs]
+
+# available discovery method, first valid is used
+# tuple (canuse, perform discovery))
+obsdiscoveries = [
+    (_canobshashrange, _pushobshashrange),
+    (_canobshashtree, _pushobshashtree),
+]
+
+obsdiscovery_skip_message = """\
+(skipping discovery of obsolescence markers, will exchange everything)
+(controled by 'experimental.evolution.obsdiscovery' configuration)
+"""
+
+def usediscovery(repo):
+    return repo.ui.configbool('experimental', 'evolution.obsdiscovery', True)
+
+@eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers')
+def _pushdiscoveryobsmarkers(orig, pushop):
+    if _dopushmarkers(pushop):
+        repo = pushop.repo
+        remote = pushop.remote
+        obsexcmsg(repo.ui, "computing relevant nodes\n")
+        revs = list(repo.revs('::%ln', pushop.futureheads))
+        unfi = repo.unfiltered()
+
+        if not usediscovery(repo):
+            # discovery disabled by user
+            repo.ui.status(obsdiscovery_skip_message)
+            return orig(pushop)
+
+        # look for an obs-discovery protocol we can use
+        discovery = None
+        for candidate in obsdiscoveries:
+            if candidate[0](repo, remote):
+                discovery = candidate[1]
+                break
+
+        if discovery is None:
+            # no discovery available, rely on core to push all relevants
+            # obs markers.
+            return orig(pushop)
+
+        obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
+                           % len(revs))
+        commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads))
+        # find the nodes where the relevant obsmarkers mismatches
+        nodes = discovery(pushop, commonrevs)
+
+        if nodes:
+            obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n"
+                               % len(nodes))
+            pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
+        else:
+            obsexcmsg(repo.ui, "markers already in sync\n")
+            pushop.outobsmarkers = []
+
+@eh.extsetup
+def _installobsmarkersdiscovery(ui):
+    olddisco = exchange.pushdiscoverymapping['obsmarker']
+
+    def newdisco(pushop):
+        _pushdiscoveryobsmarkers(olddisco, pushop)
+    exchange.pushdiscoverymapping['obsmarker'] = newdisco
+
+def buildpullobsmarkersboundaries(pullop, bundle2=True):
+    """small function returning the argument for pull markers call
+    may to contains 'heads' and 'common'. skip the key for None.
+
+    It is a separed function to play around with strategy for that."""
+    repo = pullop.repo
+    remote = pullop.remote
+    unfi = repo.unfiltered()
+    revs = unfi.revs('::(%ln - null)', pullop.common)
+    boundaries = {'heads': pullop.pulledsubset}
+    if not revs: # nothing common
+        boundaries['common'] = [node.nullid]
+        return boundaries
+
+    if not usediscovery(repo):
+        # discovery disabled by users.
+        repo.ui.status(obsdiscovery_skip_message)
+        boundaries['common'] = [node.nullid]
+        return boundaries
+
+    if bundle2 and _canobshashrange(repo, remote):
+        obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
+                  % len(revs))
+        boundaries['missing'] = findmissingrange(repo.ui, repo, pullop.remote,
+                                                 revs)
+    elif remote.capable('_evoext_obshash_0'):
+        obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
+                           % len(revs))
+        boundaries['common'] = findcommonobsmarkers(repo.ui, repo, remote, revs)
+    else:
+        boundaries['common'] = [node.nullid]
+    return boundaries
--- a/hgext3rd/evolve/obsexchange.py
+++ b/hgext3rd/evolve/obsexchange.py
@@ -18,6 +18,7 @@
 import socket
 
 from mercurial import (
+    bundle2,
     error,
     exchange,
     extensions,
@@ -98,7 +99,10 @@
             subset = [c.node() for c in repo.unfiltered().set('only(%ln, %ln)', heads, common)]
             subset += kwargs['evo_missing_nodes']
         markers = repo.obsstore.relevantmarkers(subset)
-        exchange.buildobsmarkerspart(bundler, markers)
+        if util.safehasattr(bundle2, 'buildobsmarkerspart'):
+            bundle2.buildobsmarkerspart(bundler, markers)
+        else:
+            exchange.buildobsmarkerspart(bundler, markers)
 
 # manual wrap up in extsetup because of the wireproto.commands mapping
 def _obscommon_capabilities(orig, repo, proto):
--- a/hgext3rd/evolve/obshistory.py
+++ b/hgext3rd/evolve/obshistory.py
@@ -7,11 +7,14 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+import re
+
 from mercurial import (
     cmdutil,
     commands,
     error,
     graphmod,
+    obsolete,
     node as nodemod,
     scmutil,
 )
@@ -25,9 +28,10 @@
 eh = exthelper.exthelper()
 
 @eh.command(
-    'olog',
+    'obslog|olog',
     [('G', 'graph', True, _("show the revision DAG")),
-     ('r', 'rev', [], _('show the specified revision or revset'), _('REV'))
+     ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
+     ('a', 'all', False, _('show all related changesets, not only precursors'))
     ] + commands.formatteropts,
     _('hg olog [OPTION]... [REV]'))
 def cmdobshistory(ui, repo, *revs, **opts):
@@ -157,14 +161,14 @@
             stack.pop()
     return False
 
-def _obshistorywalker(repo, revs):
+def _obshistorywalker(repo, revs, walksuccessors=False):
     """ Directly inspired by graphmod.dagwalker,
     walk the obs marker tree and yield
     (id, CHANGESET, ctx, [parentinfo]) tuples
     """
 
     # Get the list of nodes and links between them
-    candidates, nodesucc, nodeprec = _obshistorywalker_links(repo, revs)
+    candidates, nodesucc, nodeprec = _obshistorywalker_links(repo, revs, walksuccessors)
 
     # Shown, set of nodes presents in items
     shown = set()
@@ -215,15 +219,19 @@
             childrens = [(graphmod.PARENT, x) for x in nodeprec.get(cand, ())]
             yield (cand, 'M', changectx, childrens)
 
-def _obshistorywalker_links(repo, revs):
+def _obshistorywalker_links(repo, revs, walksuccessors=False):
     """ Iterate the obs history tree starting from revs, traversing
     each revision precursors recursively.
+    If walksuccessors is True, also check that every successor has been
+    walked, which ends up walking on all connected obs markers. It helps
+    getting a better view with splits and divergences.
     Return a tuple of:
     - The list of node crossed
     - The dictionnary of each node successors, values are a set
     - The dictionnary of each node precursors, values are a list
     """
     precursors = repo.obsstore.precursors
+    successors = repo.obsstore.successors
     nodec = repo.changelog.node
 
     # Parents, set of parents nodes seen during walking the graph for node
@@ -256,12 +264,21 @@
                 seen.add(precnode)
                 nodes.append(precnode)
 
+        # Also walk on successors if the option is enabled
+        if walksuccessors:
+            for successor in successors.get(node, ()):
+                for succnodeid in successor[1]:
+                    if succnodeid not in seen:
+                        seen.add(succnodeid)
+                        nodes.append(succnodeid)
+
     return sorted(seen), nodesucc, nodeprec
 
 def _debugobshistorygraph(ui, repo, revs, opts):
     displayer = obsmarker_printer(ui, repo.unfiltered(), None, opts, buffered=True)
     edges = graphmod.asciiedges
-    cmdutil.displaygraph(ui, repo, _obshistorywalker(repo.unfiltered(), revs), displayer, edges)
+    walker = _obshistorywalker(repo.unfiltered(), revs, opts.get('all', False))
+    cmdutil.displaygraph(ui, repo, walker, displayer, edges)
 
 def _debugobshistorysingle(fm, repo, revs):
     """ Display the obsolescence history for a single revision
@@ -337,6 +354,36 @@
 
     fm.write('debugobshistory.verb', '%s', verb,
              label="evolve.verb")
+
+    effectflag = metadata.get('ef1')
+    if effectflag is not None:
+        try:
+            effectflag = int(effectflag)
+        except ValueError:
+            effectflag = None
+    if effectflag:
+        effect = []
+
+        # XXX should be a dict
+        if effectflag & DESCCHANGED:
+            effect.append('description')
+        if effectflag & METACHANGED:
+            effect.append('meta')
+        if effectflag & USERCHANGED:
+            effect.append('user')
+        if effectflag & DATECHANGED:
+            effect.append('date')
+        if effectflag & BRANCHCHANGED:
+            effect.append('branch')
+        if effectflag & PARENTCHANGED:
+            effect.append('parent')
+        if effectflag & DIFFCHANGED:
+            effect.append('content')
+
+        if effect:
+            fmteffect = fm.formatlist(effect, 'debugobshistory.effect', sep=', ')
+            fm.write('debugobshistory.effect', '(%s)', fmteffect)
+
     fm.plain(' by ')
 
     fm.write('debugobshistory.marker_user', '%s', metadata['user'],
@@ -355,3 +402,205 @@
                  label="evolve.node")
 
     fm.plain("\n")
+
+# logic around storing and using effect flags
+DESCCHANGED = 1 << 0 # action changed the description
+METACHANGED = 1 << 1 # action change the meta
+PARENTCHANGED = 1 << 2 # action change the parent
+DIFFCHANGED = 1 << 3 # action change diff introduced by the changeset
+USERCHANGED = 1 << 4 # the user changed
+DATECHANGED = 1 << 5 # the date changed
+BRANCHCHANGED = 1 << 6 # the branch changed
+
+METABLACKLIST = [
+    re.compile('^__touch-noise__$'),
+    re.compile('^branch$'),
+    re.compile('^.*-source$'),
+    re.compile('^.*_source$'),
+    re.compile('^source$'),
+]
+
+def ismetablacklisted(metaitem):
+    """ Check that the key of a meta item (extrakey, extravalue) does not
+    match at least one of the blacklist pattern
+    """
+    metakey = metaitem[0]
+    for pattern in METABLACKLIST:
+        if pattern.match(metakey):
+            return False
+
+    return True
+
+def geteffectflag(relation):
+    """compute the effect flag by comparing the source and destination"""
+    effects = 0
+
+    source = relation[0]
+
+    for changectx in relation[1]:
+        # Check if description has changed
+        if changectx.description() != source.description():
+            effects |= DESCCHANGED
+
+        # Check if known meta has changed
+        if changectx.user() != source.user():
+            effects |= USERCHANGED
+
+        if changectx.date() != source.date():
+            effects |= DATECHANGED
+
+        if changectx.branch() != source.branch():
+            effects |= BRANCHCHANGED
+
+        # Check if other meta has changed
+        changeextra = changectx.extra().items()
+        ctxmeta = filter(ismetablacklisted, changeextra)
+
+        sourceextra = source.extra().items()
+        srcmeta = filter(ismetablacklisted, sourceextra)
+
+        if ctxmeta != srcmeta:
+            effects |= METACHANGED
+
+        # Check if at least one of the parent has changes
+        if changectx.parents() != source.parents():
+            effects |= PARENTCHANGED
+
+        if not _cmpdiff(source, changectx):
+            effects |= DIFFCHANGED
+
+    return effects
+
+def _prepare_hunk(hunk):
+    """Drop all information but the username and patch"""
+    cleanunk = []
+    for line in hunk.splitlines():
+        if line.startswith(b'# User') or not line.startswith(b'#'):
+            if line.startswith(b'@@'):
+                line = b'@@\n'
+            cleanunk.append(line)
+    return cleanunk
+
+def _getdifflines(iterdiff):
+    """return a cleaned up lines"""
+    try:
+        lines = iterdiff.next()
+    except StopIteration:
+        return None
+    return _prepare_hunk(lines)
+
+def _cmpdiff(leftctx, rightctx):
+    """return True if both ctx introduce the "same diff"
+
+    This is a first and basic implementation, with many shortcoming.
+    """
+    leftdiff = leftctx.diff(git=1)
+    rightdiff = rightctx.diff(git=1)
+    left, right = (0, 0)
+    while None not in (left, right):
+        left = _getdifflines(leftdiff)
+        right = _getdifflines(rightdiff)
+
+        if left != right:
+            return False
+    return True
+
+@eh.wrapfunction(obsolete, 'createmarkers')
+def createmarkerswithbits(orig, repo, relations, flag=0, date=None,
+                          metadata=None, **kwargs):
+    """compute 'effect-flag' and augment the created markers
+
+    Wrap obsolete.createmarker in order to compute the effect of each
+    relationship and store them as flag in the metadata.
+
+    While we experiment, we store flag in a metadata field. This field is
+    "versionned" to easilly allow moving to other meaning for flags.
+
+    The comparison of description or other infos just before creating the obs
+    marker might induce overhead in some cases. However it is a good place to
+    start since it automatically makes all markers creation recording more
+    meaningful data. In the future, we can introduce way for commands to
+    provide precomputed effect to avoid the overhead.
+    """
+    if not repo.ui.configbool('experimental', 'evolution.effect-flags', False):
+        return orig(repo, relations, flag, date, metadata, **kwargs)
+    if metadata is None:
+        metadata = {}
+    tr = repo.transaction('add-obsolescence-marker')
+    try:
+        for r in relations:
+            # Compute the effect flag for each obsmarker
+            effect = geteffectflag(r)
+
+            # Copy the metadata in order to add them, we copy because the
+            # effect flag might be different per relation
+            m = metadata.copy()
+            # we store the effect even if "0". This disctinct markers created
+            # without the feature with markers recording a no-op.
+            m['ef1'] = "%d" % effect
+
+            # And call obsolete.createmarkers for creating the obsmarker for real
+            orig(repo, [r], flag, date, m, **kwargs)
+
+        tr.close()
+    finally:
+        tr.release()
+
+def _getobsfate(successorssets):
+    """ Compute a changeset obsolescence fate based on his successorssets.
+    Successors can be the tipmost ones or the immediate ones.
+    Returns one fate in the following list:
+    - pruned
+    - diverged
+    - superseed
+    - superseed_split
+    """
+
+    if len(successorssets) == 0:
+        # The commit has been pruned
+        return 'pruned'
+    elif len(successorssets) > 1:
+        return 'diverged'
+    else:
+        # No divergence, only one set of successors
+        successors = successorssets[0]
+
+        if len(successors) == 1:
+            return 'superseed'
+        else:
+            return 'superseed_split'
+
+def _getobsfateandsuccs(repo, revnode, successorssets=None):
+    """ Return a tuple containing:
+    - the reason a revision is obsolete (diverged, pruned or superseed)
+    - the list of successors short node if the revision is neither pruned
+    or has diverged
+    """
+    if successorssets is None:
+        successorssets = obsolete.successorssets(repo, revnode)
+
+    fate = _getobsfate(successorssets)
+
+    # Apply node.short if we have no divergence
+    if len(successorssets) == 1:
+        successors = [nodemod.short(node_id) for node_id in successorssets[0]]
+    else:
+        successors = []
+        for succset in successorssets:
+            successors.append([nodemod.short(node_id) for node_id in succset])
+
+    return (fate, successors)
+
+def _humanizedobsfate(fate, successors):
+    """ Returns a humanized string for a changeset fate and its successors
+    """
+
+    if fate == 'pruned':
+        return 'pruned'
+    elif fate == 'diverged':
+        msgs = []
+        for successorsset in successors:
+            msgs.append('superseed as %s' % ','.join(successorsset))
+        return ' + '.join(msgs)
+    elif fate in ('superseed', 'superseed_split'):
+        return 'superseed as %s' % ','.join(successors)
--- a/hgext3rd/evolve/stablerange.py
+++ b/hgext3rd/evolve/stablerange.py
@@ -196,6 +196,8 @@
     depth = stablerange.depthrev
     length = stablerange.rangelength
     subranges = stablerange.subranges
+    if not revs:
+        raise error.Abort('no revisions specified')
     repo.stablerange.warmup(repo, max(revs))
     if opts['subranges']:
         ranges = subrangesclosure(repo, revs)
@@ -241,7 +243,7 @@
 
 class stablerange(object):
 
-    def __init__(self):
+    def __init__(self, lrusize=2000):
         # The point up to which we have data in cache
         self._tiprev = None
         self._tipnode = None
@@ -254,10 +256,10 @@
         # and then use the relevant top most part. This order is going to be
         # the same for all ranges headed at the same merge. So we cache these
         # value to reuse them accross the same invocation.
-        self._stablesortcache = {}
+        self._stablesortcache = util.lrucachedict(lrusize)
         # something useful to compute the above
         # mergerev -> stablesort, length
-        self._stablesortprepared = {}
+        self._stablesortprepared = util.lrucachedict(lrusize)
         # caching parent call # as we do so many of them
         self._parentscache = {}
         # The first part of the stable sorted list of revision of a merge will
@@ -304,12 +306,21 @@
 
         original = set(rangeheap)
         seen = 0
+        # progress report is showing up in the profile for small and fast
+        # repository so we build that complicated work around.
+        progress_each = 100
+        progress_last = time.time()
         heapify(rangeheap)
         while rangeheap:
             value = heappop(rangeheap)
             if value in original:
-                if not seen % 1000:
+                if not seen % progress_each:
+                    # if a lot of time passed, report more often
+                    progress_new = time.time()
+                    if (1 < progress_each) and (0.1 < progress_new - progress_last):
+                        progress_each /= 10
                     ui.progress(_("filling stablerange cache"), seen, total=nbrevs)
+                    progress_last = progress_new
                 seen += 1
                 original.remove(value) # might have been added from other source
             __, rangeid = value
@@ -383,7 +394,14 @@
             # note: In the general case we can just walk down and then request
             # data about the merge. But I'm not sure this function will be even
             # call for the general case.
-            allrevs = self._stablesortcache.get(headrev)
+
+            # Lrudict.get in hg-3.9 returns the lrunode instead of the
+            # value, use __getitem__ instead and catch the exception directly
+            try:
+                allrevs = self._stablesortcache[headrev]
+            except KeyError:
+                allrevs = None
+
             if allrevs is None:
                 allrevs = self._getrevsfrommerge(repo, headrev)
                 if allrevs is None:
@@ -432,8 +450,11 @@
             self._stablesortprepared[merge] = (sortedrevs, len(sortedrevs))
 
     def _getrevsfrommerge(self, repo, merge):
-        prepared = self._stablesortprepared.get(merge)
-        if prepared is None:
+        # Lrudict.get in hg-3.9 returns the lrunode instead of the
+        # value, use __getitem__ instead and catch the exception directly
+        try:
+            prepared = self._stablesortprepared[merge]
+        except KeyError:
             return None
 
         mergedepth = self.depthrev(repo, merge)
@@ -731,7 +752,9 @@
     _schemaversion = 0
 
     def __init__(self, repo):
-        super(sqlstablerange, self).__init__()
+        lrusize = repo.ui.configint('experimental', 'obshashrange.lru-size',
+                                    2000)
+        super(sqlstablerange, self).__init__(lrusize=lrusize)
         self._vfs = repo.vfs
         self._path = repo.vfs.join('cache/evoext_stablerange_v0.sqlite')
         self._cl = repo.unfiltered().changelog # (okay to keep an old one)
@@ -922,6 +945,9 @@
             tr = super(stablerangerepo, self).transaction(*args, **kwargs)
             if not repo.ui.configbool('experimental', 'obshashrange', False):
                 return tr
+            if not repo.ui.configbool('experimental', 'obshashrange.warm-cache',
+                                      True):
+                return tr
             reporef = weakref.ref(self)
 
             def _warmcache(tr):
copy from hgext3rd/evolve/__init__.py
copy to hgext3rd/evolve/templatekw.py
--- a/hgext3rd/evolve/__init__.py
+++ b/hgext3rd/evolve/templatekw.py
@@ -5,515 +5,20 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-"""extends Mercurial feature related to Changeset Evolution
-
-This extension:
-
-- provides several commands to mutate history and deal with resulting issues,
-- enable the changeset-evolution feature for Mercurial,
-- improves some aspect of the early implementation in Mercurial core,
-
-Note that a version dedicated to server usage only (no local working copy) is
-available as 'evolve.serveronly'.
-
-While many feature related to changeset evolution are directly handled by core
-this extensions contains significant additions recommended to any user of
-changeset evolution.
-
-With the extensions various evolution events will display warning (new unstable
-changesets, obsolete working copy parent, improved error when accessing hidden
-revision, etc).
-
-In addition, the extension contains better discovery protocol for obsolescence
-markers. This means less obs-markers will have to be pushed and pulled around,
-speeding up such operation.
-
-Some improvement and bug fixes available in newer version of Mercurial are also
-backported to older version of Mercurial by this extension. Some older
-experimental protocol are also supported for a longer time in the extensions to
-help people transitioning. (The extensions is currently compatible down to
-Mercurial version 3.8).
-
-New Config:
-
-    [experimental]
-    # Set to control the behavior when pushing draft changesets to a publishing
-    # repository. Possible value:
-    # * ignore: current core behavior (default)
-    # * warn: proceed with the push, but issue a warning
-    # * abort: abort the push
-    auto-publish = ignore
-
-Obsolescence Markers Discovery Experiment
-=========================================
-
-We are experimenting with a new protocol to discover common markers during the
-local and remote repository. This experiment is still at an early stage but is
-already raising better result than the previous version when usable.
-
-Large" repositories (hundreds of thousand) are currently unsupported. Some key
-algorithm has a naive implementation with too agressive caching, creating
-memory consumption issue (this will get fixed).
-
-Medium sized repositories works fine, but be prepared for a noticable initial
-cache filling. for the Mercurial repository, this is around 20 seconds
-
-The following config control the experiment::
-
-  [experimental]
-
-  # enable new discovery protocol
-  # (needed on both client and server)
-  obshashrange = yes
-
-  # avoid cache warming after transaction
-  # (recommended 'off' for developer repositories)
-  # (recommended 'yes' for server (default))
-  obshashrange.warm-cache = no
-
-It is recommended to enable the blackbox extension to gather useful
-data about the experiment. It is shipped with Mercurial so no extra
-install needed.
-
-    [extensions]
-    blackbox =
+"""evolve templates
 """
 
-evolutionhelptext = """
-Obsolescence markers make it possible to mark changesets that have been
-deleted or superset in a new version of the changeset.
-
-Unlike the previous way of handling such changes, by stripping the old
-changesets from the repository, obsolescence markers can be propagated
-between repositories. This allows for a safe and simple way of exchanging
-mutable history and altering it after the fact. Changeset phases are
-respected, such that only draft and secret changesets can be altered (see
-:hg:`help phases` for details).
-
-Obsolescence is tracked using "obsolete markers", a piece of metadata
-tracking which changesets have been made obsolete, potential successors for
-a given changeset, the moment the changeset was marked as obsolete, and the
-user who performed the rewriting operation. The markers are stored
-separately from standard changeset data can be exchanged without any of the
-precursor changesets, preventing unnecessary exchange of obsolescence data.
-
-The complete set of obsolescence markers describes a history of changeset
-modifications that is orthogonal to the repository history of file
-modifications. This changeset history allows for detection and automatic
-resolution of edge cases arising from multiple users rewriting the same part
-of history concurrently.
-
-Current feature status
-======================
-
-This feature is still in development.  If you see this help, you have enabled an
-extension that turned this feature on.
-
-Obsolescence markers will be exchanged between repositories that explicitly
-assert support for the obsolescence feature (this can currently only be done
-via an extension).""".strip()
-
-
-import os
-import sys
-import random
-import re
-import collections
-import errno
-import struct
-
-try:
-    import StringIO as io
-    StringIO = io.StringIO
-except ImportError:
-    import io
-    StringIO = io.StringIO
-
-
-try:
-    from mercurial import registrar
-    registrar.templatekeyword # new in hg-3.8
-except ImportError:
-    from . import metadata
-    raise ImportError('evolve needs version %s or above' %
-                      min(metadata.testedwith.split()))
-
-import mercurial
-from mercurial import util
-from mercurial import repair
-
-from mercurial import obsolete
-if not obsolete._enabled:
-    obsolete._enabled = True
-
-from mercurial import (
-    bookmarks as bookmarksmod,
-    cmdutil,
-    commands,
-    context,
-    copies,
-    dirstate,
-    error,
-    extensions,
-    help,
-    hg,
-    lock as lockmod,
-    merge,
-    node,
-    patch,
-    phases,
-    revset,
-    scmutil,
-    templatekw,
-    obsolete
-)
-
-from mercurial.commands import walkopts, commitopts, commitopts2, mergetoolopts
-from mercurial.i18n import _
-from mercurial.node import nullid
-
 from . import (
-    checkheads,
-    debugcmd,
     exthelper,
-    metadata,
-    obscache,
-    obsexchange,
-    safeguard,
-    utility,
     obshistory
 )
 
-__version__ = metadata.__version__
-testedwith = metadata.testedwith
-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'
-
-obsexcmsg = utility.obsexcmsg
-
-colortable = {'evolve.node': 'yellow',
-              'evolve.user': 'green',
-              'evolve.rev': 'blue',
-              'evolve.short_description': '',
-              'evolve.date': 'cyan',
-              'evolve.current_rev': 'bold',
-              'evolve.verb': '',
-              }
-
-_pack = struct.pack
-_unpack = struct.unpack
-
-aliases, entry = cmdutil.findcmd('commit', commands.table)
-interactiveopt = [['i', 'interactive', None, _('use interactive mode')]]
-# This extension contains the following code
-#
-# - Extension Helper code
-# - Obsolescence cache
-# - ...
-# - Older format compat
+from mercurial import (
+    templatekw,
+    node,
+)
 
 eh = exthelper.exthelper()
-eh.merge(debugcmd.eh)
-eh.merge(obsexchange.eh)
-eh.merge(checkheads.eh)
-eh.merge(safeguard.eh)
-eh.merge(obscache.eh)
-eh.merge(obshistory.eh)
-uisetup = eh.final_uisetup
-extsetup = eh.final_extsetup
-reposetup = eh.final_reposetup
-cmdtable = eh.cmdtable
-
-# pre hg 4.0 compat
-
-if not util.safehasattr(dirstate.dirstate, 'parentchange'):
-    import contextlib
-
-    @contextlib.contextmanager
-    def parentchange(self):
-        '''Context manager for handling dirstate parents.
-
-        If an exception occurs in the scope of the context manager,
-        the incoherent dirstate won't be written when wlock is
-        released.
-        '''
-        self._parentwriters += 1
-        yield
-        # Typically we want the "undo" step of a context manager in a
-        # finally block so it happens even when an exception
-        # occurs. In this case, however, we only want to decrement
-        # parentwriters if the code in the with statement exits
-        # normally, so we don't have a try/finally here on purpose.
-        self._parentwriters -= 1
-    dirstate.dirstate.parentchange = parentchange
-
-#####################################################################
-### Option configuration                                          ###
-#####################################################################
-
-@eh.reposetup # must be the first of its kin.
-def _configureoptions(ui, repo):
-    # If no capabilities are specified, enable everything.
-    # This is so existing evolve users don't need to change their config.
-    evolveopts = ui.configlist('experimental', 'evolution')
-    if not evolveopts:
-        evolveopts = ['all']
-        ui.setconfig('experimental', 'evolution', evolveopts, 'evolve')
-
-@eh.uisetup
-def _configurecmdoptions(ui):
-    # Unregister evolve commands if the command capability is not specified.
-    #
-    # This must be in the same function as the option configuration above to
-    # guarantee it happens after the above configuration, but before the
-    # extsetup functions.
-    evolvecommands = ui.configlist('experimental', 'evolutioncommands')
-    evolveopts = ui.configlist('experimental', 'evolution')
-    if evolveopts and (commandopt not in evolveopts and
-                       'all' not in evolveopts):
-        # We build whitelist containing the commands we want to enable
-        whitelist = set()
-        for cmd in evolvecommands:
-            matchingevolvecommands = [e for e in cmdtable.keys() if cmd in e]
-            if not matchingevolvecommands:
-                raise error.Abort(_('unknown command: %s') % cmd)
-            elif len(matchingevolvecommands) > 1:
-                msg = _('ambiguous command specification: "%s" matches %r')
-                raise error.Abort(msg % (cmd, matchingevolvecommands))
-            else:
-                whitelist.add(matchingevolvecommands[0])
-        for disabledcmd in set(cmdtable) - whitelist:
-            del cmdtable[disabledcmd]
-
-#####################################################################
-### experimental behavior                                         ###
-#####################################################################
-
-commitopts3 = [
-    ('D', 'current-date', None,
-     _('record the current date as commit date')),
-    ('U', 'current-user', None,
-     _('record the current user as committer')),
-]
-
-def _resolveoptions(ui, opts):
-    """modify commit options dict to handle related options
-
-    For now, all it does is figure out the commit date: respect -D unless
-    -d was supplied.
-    """
-    # 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()
-    if not opts.get('user') and opts.get('current_user'):
-        opts['user'] = ui.username()
-
-getrevs = obsolete.getrevs
-
-#####################################################################
-### Additional Utilities                                          ###
-#####################################################################
-
-# This section contains a lot of small utility function and method
-
-# - Function to create markers
-# - useful alias pstatus and pdiff (should probably go in evolve)
-# - "troubles" method on changectx
-# - function to travel through the obsolescence graph
-# - function to find useful changeset to stabilize
-
-
-### Useful alias
-
-@eh.uisetup
-def _installalias(ui):
-    if ui.config('alias', 'pstatus', None) is None:
-        ui.setconfig('alias', 'pstatus', 'status --rev .^', 'evolve')
-    if ui.config('alias', 'pdiff', None) is None:
-        ui.setconfig('alias', 'pdiff', 'diff --rev .^', 'evolve')
-    if ui.config('alias', 'odiff', None) is None:
-        ui.setconfig('alias', 'odiff',
-                     "diff --hidden --rev 'limit(precursors(.),1)' --rev .",
-                     'evolve')
-    if ui.config('alias', 'grab', None) is None:
-        if os.name == 'nt':
-            ui.setconfig('alias', 'grab',
-                         "! " + util.hgexecutable()
-                         + " rebase --dest . --rev $@ && "
-                         + util.hgexecutable() + " up tip",
-                         'evolve')
-        else:
-            ui.setconfig('alias', 'grab',
-                         "! $HG rebase --dest . --rev $@ && $HG up tip",
-                         'evolve')
-
-
-### Troubled revset symbol
-
-@eh.revset('troubled')
-def revsettroubled(repo, subset, x):
-    """``troubled()``
-    Changesets with troubles.
-    """
-    revset.getargs(x, 0, 0, 'troubled takes no arguments')
-    troubled = set()
-    troubled.update(getrevs(repo, 'unstable'))
-    troubled.update(getrevs(repo, 'bumped'))
-    troubled.update(getrevs(repo, 'divergent'))
-    troubled = revset.baseset(troubled)
-    troubled.sort() # set is non-ordered, enforce order
-    return subset & troubled
-
-### Obsolescence graph
-
-# XXX SOME MAJOR CLEAN UP TO DO HERE XXX
-
-def _precursors(repo, s):
-    """Precursor of a changeset"""
-    cs = set()
-    nm = repo.changelog.nodemap
-    markerbysubj = repo.obsstore.precursors
-    node = repo.changelog.node
-    for r in s:
-        for p in markerbysubj.get(node(r), ()):
-            pr = nm.get(p[0])
-            if pr is not None:
-                cs.add(pr)
-    cs -= repo.changelog.filteredrevs # nodemap has no filtering
-    return cs
-
-def _allprecursors(repo, s):  # XXX we need a better naming
-    """transitive precursors of a subset"""
-    node = repo.changelog.node
-    toproceed = [node(r) for r in s]
-    seen = set()
-    allsubjects = repo.obsstore.precursors
-    while toproceed:
-        nc = toproceed.pop()
-        for mark in allsubjects.get(nc, ()):
-            np = mark[0]
-            if np not in seen:
-                seen.add(np)
-                toproceed.append(np)
-    nm = repo.changelog.nodemap
-    cs = set()
-    for p in seen:
-        pr = nm.get(p)
-        if pr is not None:
-            cs.add(pr)
-    cs -= repo.changelog.filteredrevs # nodemap has no filtering
-    return cs
-
-def _successors(repo, s):
-    """Successors of a changeset"""
-    cs = set()
-    node = repo.changelog.node
-    nm = repo.changelog.nodemap
-    markerbyobj = repo.obsstore.successors
-    for r in s:
-        for p in markerbyobj.get(node(r), ()):
-            for sub in p[1]:
-                sr = nm.get(sub)
-                if sr is not None:
-                    cs.add(sr)
-    cs -= repo.changelog.filteredrevs # nodemap has no filtering
-    return cs
-
-def _allsuccessors(repo, s, haltonflags=0):  # XXX we need a better naming
-    """transitive successors of a subset
-
-    haltonflags allows to provide flags which prevent the evaluation of a
-    marker.  """
-    node = repo.changelog.node
-    toproceed = [node(r) for r in s]
-    seen = set()
-    allobjects = repo.obsstore.successors
-    while toproceed:
-        nc = toproceed.pop()
-        for mark in allobjects.get(nc, ()):
-            if mark[2] & haltonflags:
-                continue
-            for sub in mark[1]:
-                if sub == nullid:
-                    continue # should not be here!
-                if sub not in seen:
-                    seen.add(sub)
-                    toproceed.append(sub)
-    nm = repo.changelog.nodemap
-    cs = set()
-    for s in seen:
-        sr = nm.get(s)
-        if sr is not None:
-            cs.add(sr)
-    cs -= repo.changelog.filteredrevs # nodemap has no filtering
-    return cs
-
-#####################################################################
-### Extending revset and template                                 ###
-#####################################################################
-
-# this section add several useful revset symbol not yet in core.
-# they are subject to changes
-
-
-### XXX I'm not sure this revset is useful
-@eh.revset('suspended')
-def revsetsuspended(repo, subset, x):
-    """``suspended()``
-    Obsolete changesets with non-obsolete descendants.
-    """
-    revset.getargs(x, 0, 0, 'suspended takes no arguments')
-    suspended = revset.baseset(getrevs(repo, 'suspended'))
-    suspended.sort()
-    return subset & suspended
-
-
-@eh.revset('precursors')
-def revsetprecursors(repo, subset, x):
-    """``precursors(set)``
-    Immediate precursors of changesets in set.
-    """
-    s = revset.getset(repo, revset.fullreposet(repo), x)
-    s = revset.baseset(_precursors(repo, s))
-    s.sort()
-    return subset & s
-
-
-@eh.revset('allprecursors')
-def revsetallprecursors(repo, subset, x):
-    """``allprecursors(set)``
-    Transitive precursors of changesets in set.
-    """
-    s = revset.getset(repo, revset.fullreposet(repo), x)
-    s = revset.baseset(_allprecursors(repo, s))
-    s.sort()
-    return subset & s
-
-
-@eh.revset('successors')
-def revsetsuccessors(repo, subset, x):
-    """``successors(set)``
-    Immediate successors of changesets in set.
-    """
-    s = revset.getset(repo, revset.fullreposet(repo), x)
-    s = revset.baseset(_successors(repo, s))
-    s.sort()
-    return subset & s
-
-@eh.revset('allsuccessors')
-def revsetallsuccessors(repo, subset, x):
-    """``allsuccessors(set)``
-    Transitive successors of changesets in set.
-    """
-    s = revset.getset(repo, revset.fullreposet(repo), x)
-    s = revset.baseset(_allsuccessors(repo, s))
-    s.sort()
-    return subset & s
 
 ### template keywords
 # XXX it does not handle troubles well :-/
@@ -539,2777 +44,155 @@
         return templatekw.showlist('trouble', ctx.troubles(), plural='troubles',
                                    **args)
 
-#####################################################################
-### Various trouble warning                                       ###
-#####################################################################
-
-# This section take care of issue warning to the user when troubles appear
-
-
-def _getobsoletereason(repo, revnode):
-    """ Return a tuple containing:
-    - the reason a revision is obsolete (diverged, pruned or superseed)
-    - the list of successors short node if the revision is neither pruned
-    or has diverged
-    """
-    successorssets = obsolete.successorssets(repo, revnode)
-
-    if len(successorssets) == 0:
-        # The commit has been pruned
-        return ('pruned', [])
-    elif len(successorssets) > 1:
-        return ('diverged', [])
-    else:
-        # No divergence, only one set of successors
-        successors = [node.short(node_id) for node_id in successorssets[0]]
-
-        if len(successors) == 1:
-            return ('superseed', successors)
-        else:
-            return ('superseed_split', successors)
-
-def _warnobsoletewc(ui, repo):
-    rev = repo['.']
-
-    if not rev.obsolete():
-        return
-
-    msg = _("working directory parent is obsolete! (%s)\n")
-    shortnode = node.short(rev.node())
-
-    ui.warn(msg % shortnode)
-
-    # Check that evolve is activated for performance reasons
-    if ui.quiet or not obsolete.isenabled(repo, commandopt):
-        return
-
-    # Show a warning for helping the user to solve the issue
-    reason, successors = _getobsoletereason(repo, rev.node())
-
-    if reason == 'pruned':
-        solvemsg = _("use 'hg evolve' to update to its parent successor")
-    elif reason == 'diverged':
-        debugcommand = "hg evolve -list --divergent"
-        basemsg = _("%s has diverged, use '%s' to resolve the issue")
-        solvemsg = basemsg % (shortnode, debugcommand)
-    elif reason == 'superseed':
-        msg = _("use 'hg evolve' to update to its successor: %s")
-        solvemsg = msg % successors[0]
-    elif reason == 'superseed_split':
-        msg = _("use 'hg evolve' to update to its tipmost successor: %s")
-
-        if len(successors) <= 2:
-            solvemsg = msg % ", ".join(successors)
-        else:
-            firstsuccessors = ", ".join(successors[:2])
-            remainingnumber = len(successors) - 2
-            successorsmsg = _("%s and %d more") % (firstsuccessors, remainingnumber)
-            solvemsg = msg % successorsmsg
-    else:
-        raise ValueError(reason)
-
-    ui.warn("(%s)\n" % solvemsg)
-
-if util.safehasattr(context, '_filterederror'):
-    # if < hg-4.2 we do not update the message
-    @eh.wrapfunction(context, '_filterederror')
-    def evolve_filtererror(original, repo, changeid):
-        """build an exception to be raised about a filtered changeid
-
-        This is extracted in a function to help extensions (eg: evolve) to
-        experiment with various message variants."""
-        if repo.filtername.startswith('visible'):
-
-            unfilteredrepo = repo.unfiltered()
-            rev = unfilteredrepo[changeid]
-            reason, successors = _getobsoletereason(unfilteredrepo, rev.node())
-
-            # Be more precise in cqse the revision is superseed
-            if reason == 'superseed':
-                reason = _("successor: %s") % successors[0]
-            elif reason == 'superseed_split':
-                if len(successors) <= 2:
-                    reason = _("successors: %s") % ", ".join(successors)
-                else:
-                    firstsuccessors = ", ".join(successors[:2])
-                    remainingnumber = len(successors) - 2
-                    successorsmsg = _("%s and %d more") % (firstsuccessors, remainingnumber)
-                    reason = _("successors: %s") % successorsmsg
-
-            msg = _("hidden revision '%s'") % changeid
-            hint = _('use --hidden to access hidden revisions; %s') % reason
-            return error.FilteredRepoLookupError(msg, hint=hint)
-        msg = _("filtered revision '%s' (not in '%s' subset)")
-        msg %= (changeid, repo.filtername)
-        return error.FilteredRepoLookupError(msg)
-
-@eh.wrapcommand("update")
-@eh.wrapcommand("pull")
-def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
-    """Warn that the working directory parent is an obsolete changeset"""
-    def warnobsolete():
-        _warnobsoletewc(ui, repo)
-    wlock = None
-    try:
-        wlock = repo.wlock()
-        repo._afterlock(warnobsolete)
-        res = origfn(ui, repo, *args, **opts)
-    finally:
-        lockmod.release(wlock)
-    return res
-
-@eh.wrapcommand("parents")
-def wrapparents(origfn, ui, repo, *args, **opts):
-    res = origfn(ui, repo, *args, **opts)
-    _warnobsoletewc(ui, repo)
-    return res
-
-# XXX this could wrap transaction code
-# XXX (but this is a bit a layer violation)
-@eh.wrapcommand("commit")
-@eh.wrapcommand("import")
-@eh.wrapcommand("push")
-@eh.wrapcommand("pull")
-@eh.wrapcommand("graft")
-@eh.wrapcommand("phase")
-@eh.wrapcommand("unbundle")
-def warnobserrors(orig, ui, repo, *args, **kwargs):
-    """display warning is the command resulted in more instable changeset"""
-    # part of the troubled stuff may be filtered (stash ?)
-    # This needs a better implementation but will probably wait for core.
-    filtered = repo.changelog.filteredrevs
-    priorunstables = len(set(getrevs(repo, 'unstable')) - filtered)
-    priorbumpeds = len(set(getrevs(repo, 'bumped')) - filtered)
-    priordivergents = len(set(getrevs(repo, 'divergent')) - filtered)
-    ret = orig(ui, repo, *args, **kwargs)
-    filtered = repo.changelog.filteredrevs
-    newunstables = \
-        len(set(getrevs(repo, 'unstable')) - filtered) - priorunstables
-    newbumpeds = \
-        len(set(getrevs(repo, 'bumped')) - filtered) - priorbumpeds
-    newdivergents = \
-        len(set(getrevs(repo, 'divergent')) - filtered) - priordivergents
-    if newunstables > 0:
-        ui.warn(_('%i new unstable changesets\n') % newunstables)
-    if newbumpeds > 0:
-        ui.warn(_('%i new bumped changesets\n') % newbumpeds)
-    if newdivergents > 0:
-        ui.warn(_('%i new divergent changesets\n') % newdivergents)
-    return ret
-
-@eh.wrapfunction(mercurial.exchange, 'push')
-def push(orig, repo, *args, **opts):
-    """Add a hint for "hg evolve" when troubles make push fails
-    """
-    try:
-        return orig(repo, *args, **opts)
-    except error.Abort as ex:
-        hint = _("use 'hg evolve' to get a stable history "
-                 "or --force to ignore warnings")
-        if (len(ex.args) >= 1
-            and ex.args[0].startswith('push includes ')
-            and ex.hint is None):
-            ex.hint = hint
-        raise
-
-def summaryhook(ui, repo):
-    def write(fmt, count):
-        s = fmt % count
-        if count:
-            ui.write(s)
-        else:
-            ui.note(s)
-
-    state = _evolvestateread(repo)
-    if state is not None:
-        # i18n: column positioning for "hg summary"
-        ui.write(_('evolve: (evolve --continue)\n'))
-
-@eh.extsetup
-def obssummarysetup(ui):
-    cmdutil.summaryhooks.add('evolve', summaryhook)
-
-
-#####################################################################
-### Core Other extension compat                                   ###
-#####################################################################
-
-
-@eh.extsetup
-def _rebasewrapping(ui):
-    # warning about more obsolete
-    try:
-        rebase = extensions.find('rebase')
-        if rebase:
-            extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
-    except KeyError:
-        pass # rebase not found
-    try:
-        histedit = extensions.find('histedit')
-        if histedit:
-            extensions.wrapcommand(histedit.cmdtable, 'histedit', warnobserrors)
-    except KeyError:
-        pass # histedit not found
-
-#####################################################################
-### Old Evolve extension content                                  ###
-#####################################################################
-
-# XXX need clean up and proper sorting in other section
-
-### changeset rewriting logic
-#############################
-
-def rewrite(repo, old, updates, head, newbases, commitopts):
-    """Return (nodeid, created) where nodeid is the identifier of the
-    changeset generated by the rewrite process, and created is True if
-    nodeid was actually created. If created is False, nodeid
-    references a changeset existing before the rewrite call.
-    """
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction('rewrite')
-        if len(old.parents()) > 1: # XXX remove this unnecessary limitation.
-            raise error.Abort(_('cannot amend merge changesets'))
-        base = old.p1()
-        updatebookmarks = _bookmarksupdater(repo, old.node(), tr)
-
-        # commit a new version of the old changeset, including the update
-        # collect all files which might be affected
-        files = set(old.files())
-        for u in updates:
-            files.update(u.files())
-
-        # Recompute copies (avoid recording a -> b -> a)
-        copied = copies.pathcopies(base, head)
-
-        # prune files which were reverted by the updates
-        def samefile(f):
-            if f in head.manifest():
-                a = head.filectx(f)
-                if f in base.manifest():
-                    b = base.filectx(f)
-                    return (a.data() == b.data()
-                            and a.flags() == b.flags())
-                else:
-                    return False
-            else:
-                return f not in base.manifest()
-        files = [f for f in files if not samefile(f)]
-        # commit version of these files as defined by head
-        headmf = head.manifest()
-
-        def filectxfn(repo, ctx, path):
-            if path in headmf:
-                fctx = head[path]
-                flags = fctx.flags()
-                mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
-                                          islink='l' in flags,
-                                          isexec='x' in flags,
-                                          copied=copied.get(path))
-                return mctx
-            return None
-
-        message = cmdutil.logmessage(repo.ui, commitopts)
-        if not message:
-            message = old.description()
-
-        user = commitopts.get('user') or old.user()
-        # TODO: In case not date is given, we should take the old commit date
-        # if we are working one one changeset or mimic the fold behavior about
-        # date
-        date = commitopts.get('date') or None
-        extra = dict(commitopts.get('extra', old.extra()))
-        extra['branch'] = head.branch()
-
-        new = context.memctx(repo,
-                             parents=newbases,
-                             text=message,
-                             files=files,
-                             filectxfn=filectxfn,
-                             user=user,
-                             date=date,
-                             extra=extra)
-
-        if commitopts.get('edit'):
-            new._text = cmdutil.commitforceeditor(repo, new, [])
-        revcount = len(repo)
-        newid = repo.commitctx(new)
-        new = repo[newid]
-        created = len(repo) != revcount
-        updatebookmarks(newid)
-
-        tr.close()
-        return newid, created
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-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 = obsolete.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
-            copies.duplicatecopies(repo, 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 _bookmarksupdater(repo, oldid, tr):
-    """Return a callable update(newid) updating the current bookmark
-    and bookmarks bound to oldid to newid.
-    """
-    def updatebookmarks(newid):
-        dirty = False
-        oldbookmarks = repo.nodebookmarks(oldid)
-        if oldbookmarks:
-            for b in oldbookmarks:
-                repo._bookmarks[b] = newid
-            dirty = True
-        if dirty:
-            repo._bookmarks.recordchange(tr)
-    return updatebookmarks
-
-### new command
-#############################
-metadataopts = [
-    ('d', 'date', '',
-     _('record the specified date in metadata'), _('DATE')),
-    ('u', 'user', '',
-     _('record the specified user in metadata'), _('USER')),
-]
-
-@eh.uisetup
-def _installimportobsolete(ui):
-    entry = cmdutil.findcmd('import', commands.table)[1]
-    entry[1].append(('', 'obsolete', False,
-                    _('mark the old node as obsoleted by '
-                      'the created commit')))
-
-@eh.wrapfunction(mercurial.cmdutil, 'tryimportone')
-def tryimportone(orig, ui, repo, hunk, parents, opts, *args, **kwargs):
-    extracted = patch.extract(ui, hunk)
-    expected = extracted.get('nodeid')
-    if expected is not None:
-        expected = node.bin(expected)
-    oldextract = patch.extract
-    try:
-        patch.extract = lambda ui, hunk: extracted
-        ret = orig(ui, repo, hunk, parents, opts, *args, **kwargs)
-    finally:
-        patch.extract = oldextract
-    created = ret[1]
-    if (opts['obsolete'] and None not in (created, expected)
-        and created != expected):
-            tr = repo.transaction('import-obs')
-            try:
-                metadata = {'user': ui.username()}
-                repo.obsstore.create(tr, expected, (created,),
-                                     metadata=metadata)
-                tr.close()
-            finally:
-                tr.release()
-    return ret
-
-
-def _deprecatealias(oldalias, newalias):
-    '''Deprecates an alias for a command in favour of another
-
-    Creates a new entry in the command table for the old alias. It creates a
-    wrapper that has its synopsis set to show that is has been deprecated.
-    The documentation will be replace with a pointer to the new alias.
-    If a user invokes the command a deprecation warning will be printed and
-    the command of the *new* alias will be invoked.
-
-    This function is loosely based on the extensions.wrapcommand function.
-    '''
-    try:
-        aliases, entry = cmdutil.findcmd(newalias, cmdtable)
-    except error.UnknownCommand:
-        # Commands may be disabled
-        return
-    for alias, e in cmdtable.items():
-        if e is entry:
-            break
-
-    synopsis = '(DEPRECATED)'
-    if len(entry) > 2:
-        fn, opts, _syn = entry
-    else:
-        fn, opts, = entry
-    deprecationwarning = _('%s have been deprecated in favor of %s\n') % (
-        oldalias, newalias)
-
-    def newfn(*args, **kwargs):
-        ui = args[0]
-        ui.warn(deprecationwarning)
-        util.checksignature(fn)(*args, **kwargs)
-    newfn.__doc__ = deprecationwarning
-    cmdwrapper = eh.command(oldalias, opts, synopsis)
-    cmdwrapper(newfn)
-
-@eh.extsetup
-def deprecatealiases(ui):
-    _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 'unstable' == category:
-            result = _solveunstable(ui, repo, ctx, dryrun, confirm, progresscb)
-        elif 'bumped' == category:
-            result = _solvebumped(ui, repo, ctx, dryrun, confirm, progresscb)
-        elif 'divergent' == 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 = ['bumped', 'divergent', 'unstable']
-    unselectedcategories = [c for c in troublecategories if c != targetcat]
-    msg = None
-    hint = None
-
-    troubled = {
-        "unstable": repo.revs("unstable()"),
-        "divergent": repo.revs("divergent()"),
-        "bumped": repo.revs("bumped()"),
-        "all": repo.revs("troubled()"),
-    }
-
-    hintmap = {
-        'bumped': _("do you want to use --bumped"),
-        'bumped+divergent': _("do you want to use --bumped or --divergent"),
-        'bumped+unstable': _("do you want to use --bumped or --unstable"),
-        'divergent': _("do you want to use --divergent"),
-        'divergent+unstable': _("do you want to use --divergent"
-                                " or --unstable"),
-        'unstable': _("do you want to use --unstable"),
-        'any+bumped': _("do you want to use --any (or --rev) and --bumped"),
-        'any+bumped+divergent': _("do you want to use --any (or --rev) and"
-                                  " --bumped or --divergent"),
-        'any+bumped+unstable': _("do you want to use --any (or --rev) and"
-                                 "--bumped or --unstable"),
-        'any+divergent': _("do you want to use --any (or --rev) and"
-                           " --divergent"),
-        'any+divergent+unstable': _("do you want to use --any (or --rev)"
-                                    " and --divergent or --unstable"),
-        'any+unstable': _("do you want to use --any (or --rev)"
-                          "and --unstable"),
-    }
-
-    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 == 'unstable':
-            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:
-            l = len(troubled[targetcat])
-            if l:
-                hint = _("%d other %s in the repository, do you want --any "
-                         "or --rev") % (l, 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 closestprecursors(repo, nodeid):
+    """ Yield the list of next precursors pointing on visible changectx nodes
     """
 
-    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"""
+    precursors = repo.obsstore.precursors
+    stack = [nodeid]
 
-    if not p.obsolete():
-        return p.rev()
-    obs = repo[p]
-    ui = repo.ui
-    newer = obsolete.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 = obsolete.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)"""
+    while stack:
+        current = stack.pop()
+        currentpreccs = precursors.get(current, ())
 
-    # 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 prec in currentpreccs:
+            precnodeid = prec[0]
 
-    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
+            if precnodeid in repo:
+                yield precnodeid
+            else:
+                stack.append(precnodeid)
 
-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 == 'unstable':
-                revs = _aspiringdescendant(repo,
-                                           repo.revs('(.::) - obsolete()::'))
-                revs = set(revs)
-        if targetcat == 'divergent':
-            # Pick one divergent per group of divergents
-            revs = _dedupedivergents(repo, revs)
-    elif anyopt:
-        revs = repo.revs('first(%s())' % (targetcat))
-    elif targetcat == 'unstable':
-        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 targetcat in repo['.'].troubles():
-        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.
+@eh.templatekw("precursors")
+def shownextvisibleprecursors(repo, ctx, **args):
+    """Returns a string containing the list if the closest successors
+    displayed
     """
-    # 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
+    precursors = sorted(closestprecursors(repo, ctx.node()))
 
-def divergentsets(repo, ctx):
-    """Compute sets of commits divergent with a given one"""
-    cache = {}
-    base = {}
-    for n in obsolete.allprecursors(repo.obsstore, [ctx.node()]):
-        if n == ctx.node():
-            # a node can't be a base for divergence with itself
-            continue
-        nsuccsets = obsolete.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
-        })
+    # <= hg-4.1 requires an explicite gen.
+    # we can use None once the support is dropped
+    #
+    # They also requires an iterator instead of an iterable.
+    gen = iter(" ".join(map(node.short, precursors)))
+    return templatekw._hybrid(gen.__iter__(), precursors, lambda x: {'precursor': x},
+                              lambda d: "%s" % node.short(d['precursor']))
 
-    return divergence
-
-def _preparelistctxs(items, condition):
-    return [item.hex() for item in items if condition(item)]
+def closestsuccessors(repo, nodeid):
+    """ returns the closest visible successors sets instead.
+    """
+    return directsuccessorssets(repo, nodeid)
 
-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 ['divergent', 'unstable', 'bumped']
-    showunstable = 'unstable' in troublecategories
-    showbumped = 'bumped' in troublecategories
-    showdivergent = 'divergent' 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.unstable())
-        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())
+@eh.templatekw("successors")
+def shownextvisiblesuccessors(repo, ctx, templ, **args):
+    """Returns a string of sets of successors for a changectx in this format:
+    [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2 while
+    also diverged into ctx3"""
+    if not ctx.obsolete():
+        return ''
 
-        for unpar in unpars if showunstable else []:
-            fm.plain('  unstable: %s (unstable parent)\n' % unpar[:hashlen])
-        for obspar in obspars if showunstable else []:
-            fm.plain('  unstable: %s (obsolete parent)\n' % obspar[:hashlen])
-        for imprec in imprecs if showbumped else []:
-            fm.plain('  bumped: %s (immutable precursor)\n' % imprec[:hashlen])
-
-        if dsets and showdivergent:
-            for dset in dsets:
-                fm.plain('  divergent: ')
-                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': 'unstable', 'sourcenode': unpar,
-                             'sourcetype': 'unstableparent'})
-        for obspar in obspars:
-            troubles.append({'troubletype': 'unstable', 'sourcenode': obspar,
-                             'sourcetype': 'obsoleteparent'})
-        for imprec in imprecs:
-            troubles.append({'troubletype': 'bumped', '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': 'divergent',
-                             'commonprecursor': node.hex(dset['commonprecursor']),
-                             'divergentnodes': divnodes})
-        fm.data(troubles=troubles)
-
-    fm.end()
+    ssets = closestsuccessors(repo, ctx.node())
 
-@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')),
-     ('', 'divergent', False, _('solves only divergent changesets')),
-     ('', 'unstable', False, _('solves only unstable 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
+    data = []
+    gen = []
+    for ss in ssets:
+        subgen = '[%s]' % ', '.join(map(node.short, ss))
+        gen.append(subgen)
+        h = templatekw._hybrid(iter(subgen), ss, lambda x: {'successor': x},
+                               lambda d: "%s" % d["successor"])
+        data.append(h)
 
-    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.
+    gen = ', '.join(gen)
+    return templatekw._hybrid(iter(gen), data, lambda x: {'successorset': x},
+                              lambda d: d["successorset"])
 
-    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 explicity
-    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.
+@eh.templatekw("obsfate_quiet")
+def showobsfate_quiet(repo, ctx, templ, **args):
+    if not ctx.obsolete():
+        return ''
 
-    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.
+    successorssets = closestsuccessors(repo, ctx.node())
+    return obshistory._humanizedobsfate(*obshistory._getobsfateandsuccs(repo, ctx, successorssets))
 
-    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.
+# copy from mercurial.obsolete with a small change to stop at first known changeset.
 
-    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.
+def directsuccessorssets(repo, initialnode, cache=None):
+    """return set of all direct successors of initial nodes
     """
 
-    # Options
-    listopt = opts['list']
-    contopt = opts['continue']
-    anyopt = opts['any']
-    allopt = opts['all']
-    startnode = repo['.']
-    dryrunopt = opts['dry_run']
-    confirmopt = opts['confirm']
-    revopt = opts['rev']
-    troublecategories = ['bumped', 'divergent', 'unstable']
-    specifiedcategories = [t for t in troublecategories if opts[t]]
-    if listopt:
-        listtroubles(ui, repo, specifiedcategories, **opts)
-        return
-
-    targetcat = 'unstable'
-    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:
-        if anyopt:
-            raise error.Abort('cannot specify both "--any" and "--continue"')
-        if allopt:
-            raise error.Abort('cannot specify both "--all" and "--continue"')
-        state = _evolvestateread(repo)
-        if state is None:
-            raise error.Abort('no evolve to continue')
-        orig = repo[state['current']]
-        # XXX This is a terrible terrible hack, please get rid of it.
-        lock = repo.wlock()
-        try:
-            repo.vfs.write('graftstate', orig.hex() + '\n')
-            try:
-                graftcmd = commands.table['graft'][0]
-                ret = graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
-                _evolvestatedelete(repo)
-                return ret
-            finally:
-                util.unlinkpath(repo.vfs.join('graftstate'), ignoremissing=True)
-        finally:
-            lock.release()
-    cmdutil.bailifchanged(repo)
-
-    if revopt and allopt:
-        raise error.Abort('cannot specify both "--rev" and "--all"')
-    if revopt and anyopt:
-        raise error.Abort('cannot specify both "--rev" and "--any"')
-
-    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 == 'unstable':
-        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 _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 = obsolete.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('unstable() - %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('unstable() - %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()
-    if len(orig.parents()) == 2:
-        if not pctx.obsolete():
-            pctx = orig.p2()  # second parent is obsolete ?
-        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 = obsolete.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 = obsolete.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 accross 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()
-        keepbranch = orig.p1().branch() != orig.branch()
-        try:
-            relocate(repo, orig, target, pctx, keepbranch)
-        except MergeFailure:
-            _evolvestatewrite(repo, {'current': orig.node()})
-            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 "bumped update to %s"'))
-        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 = context.memfilectx(repo, fctx.path(), fctx.data(),
-                                          islink='l' in flags,
-                                          isexec='x' in flags,
-                                          copied=copied.get(path))
-                return mctx
-            return None
-        text = 'bumped update to %s:\n\n' % 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:divergent with a changeset that got splitted"
-                " 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, othersstr)
-        ui.write_err(msg)
-        return 2
-    other = others[0]
-    if len(other.parents()) > 1:
-        msg = _("skipping %s: divergent changeset can't be "
-                "a merge (yet)\n") % divergent
-        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 divergent changeset\n'))
-    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)
-        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 = obsolete.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')
-
-shorttemplate = "[{label('evolve.rev', rev)}] {desc|firstline}\n"
-
-@eh.command(
-    '^previous',
-    [('B', 'move-bookmark', False,
-        _('move active bookmark after update')),
-     ('', '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'))],
-    '[OPTION]...')
-def cmdprevious(ui, repo, **opts):
-    """update to parent revision
-
-    Displays the summary line of the destination for clarity."""
-    wlock = None
-    dryrunopt = opts['dry_run']
-    if not dryrunopt:
-        wlock = repo.wlock()
-    try:
-        wkctx = repo[None]
-        wparents = wkctx.parents()
-        if len(wparents) != 1:
-            raise error.Abort('merge in progress')
-        if not opts['merge']:
-            try:
-                cmdutil.bailifchanged(repo)
-            except error.Abort as exc:
-                exc.hint = _('do you want --merge?')
-                raise
-
-        parents = wparents[0].parents()
-        topic = getattr(repo, 'currenttopic', '')
-        if topic and not opts.get("no_topic", False):
-            parents = [ctx for ctx in parents if ctx.topic() == topic]
-        displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-        if not parents:
-            ui.warn(_('no parent in topic "%s"\n') % topic)
-            ui.warn(_('(do you want --no-topic)\n'))
-        elif len(parents) == 1:
-            p = parents[0]
-            bm = repo._activebookmark
-            shouldmove = opts.get('move_bookmark') and bm is not None
-            if dryrunopt:
-                ui.write(('hg update %s;\n' % p.rev()))
-                if shouldmove:
-                    ui.write(('hg bookmark %s -r %s;\n' % (bm, p.rev())))
-            else:
-                ret = hg.update(repo, p.rev())
-                if not ret:
-                    tr = lock = None
-                    try:
-                        lock = repo.lock()
-                        tr = repo.transaction('previous')
-                        if shouldmove:
-                            repo._bookmarks[bm] = p.node()
-                            repo._bookmarks.recordchange(tr)
-                        else:
-                            bookmarksmod.deactivate(repo)
-                        tr.close()
-                    finally:
-                        lockmod.release(tr, lock)
-
-            displayer.show(p)
-            return 0
-        else:
-            for p in parents:
-                displayer.show(p)
-            ui.warn(_('multiple parents, explicitly update to one\n'))
-            return 1
-    finally:
-        lockmod.release(wlock)
-
-@eh.command(
-    '^next',
-    [('B', 'move-bookmark', False,
-        _('move active bookmark after update')),
-     ('', '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,
-      _('do not perform actions, just print what would be done'))],
-    '[OPTION]...')
-def cmdnext(ui, repo, **opts):
-    """update to next child revision
-
-    Use the ``--evolve`` flag to evolve unstable children on demand.
-
-    Displays the summary line of the destination for clarity.
-    """
-    wlock = None
-    dryrunopt = opts['dry_run']
-    if not dryrunopt:
-        wlock = repo.wlock()
-    try:
-        wkctx = repo[None]
-        wparents = wkctx.parents()
-        if len(wparents) != 1:
-            raise error.Abort('merge in progress')
-        if not opts['merge']:
-            try:
-                cmdutil.bailifchanged(repo)
-            except error.Abort as exc:
-                exc.hint = _('do you want --merge?')
-                raise
-
-        children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()]
-        topic = getattr(repo, 'currenttopic', '')
-        filtered = []
-        if topic and not opts.get("no_topic", False):
-            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})
-        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())))
-            else:
-                ret = hg.update(repo, c.rev())
-                if not ret:
-                    lock = tr = None
-                    try:
-                        lock = repo.lock()
-                        tr = repo.transaction('next')
-                        if shouldmove:
-                            repo._bookmarks[bm] = c.node()
-                            repo._bookmarks.recordchange(tr)
-                        else:
-                            bookmarksmod.deactivate(repo)
-                        tr.close()
-                    finally:
-                        lockmod.release(tr, lock)
-            displayer.show(c)
-            result = 0
-        elif children:
-            ui.warn(_("ambigious next changeset:\n"))
-            for c in children:
-                displayer.show(c)
-            ui.warn(_('explicitly update to one of them\n'))
-            result = 1
-        else:
-            aspchildren = _aspiringchildren(repo, [repo['.'].rev()])
-            if topic:
-                filtered.extend(repo[c] for c in children
-                                if repo[c].topic() != topic)
-                # XXX N-square membership on children
-                aspchildren = [ctx for ctx in aspchildren if ctx not in filtered]
-            if not opts['evolve'] or not aspchildren:
-                if filtered:
-                    ui.warn(_('no children on topic "%s"\n') % topic)
-                    ui.warn(_('do you want --no-topic\n'))
-                else:
-                    ui.warn(_('no children\n'))
-                if aspchildren:
-                    msg = _('(%i unstable changesets to be evolved here, '
-                            'do you want --evolve?)\n')
-                    ui.warn(msg % len(aspchildren))
-                result = 1
-            elif 1 < len(aspchildren):
-                ui.warn(_("ambigious 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:
-                cmdutil.bailifchanged(repo)
-                result = _solveone(ui, repo, repo[aspchildren[0]], dryrunopt,
-                                   False, lambda: None, category='unstable')
-                if not result:
-                    ui.status(_('working directory now at %s\n')
-                              % ui.label(str(repo['.']), 'evolve.node'))
-                return result
-            return 1
-        return result
-    finally:
-        lockmod.release(wlock)
-
-def _reachablefrombookmark(repo, revs, bookmarks):
-    """filter revisions and bookmarks reachable from the given bookmark
-    yoinked from mq.py
-    """
-    repomarks = repo._bookmarks
-    if not bookmarks.issubset(repomarks):
-        raise error.Abort(_("bookmark '%s' not found") %
-                          ','.join(sorted(bookmarks - set(repomarks.keys()))))
-
-    # If the requested bookmark is not the only one pointing to a
-    # a revision we have to only delete the bookmark and not strip
-    # anything. revsets cannot detect that case.
-    nodetobookmarks = {}
-    for mark, bnode in repomarks.iteritems():
-        nodetobookmarks.setdefault(bnode, []).append(mark)
-    for marks in nodetobookmarks.values():
-        if bookmarks.issuperset(marks):
-            rsrevs = repair.stripbmrevset(repo, marks[0])
-            revs = set(revs)
-            revs.update(set(rsrevs))
-            revs = sorted(revs)
-    return repomarks, revs
-
-def _deletebookmark(repo, repomarks, bookmarks):
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction('prune')
-        for bookmark in bookmarks:
-            del repomarks[bookmark]
-        repomarks.recordchange(tr)
-        tr.close()
-        for bookmark in sorted(bookmarks):
-            repo.ui.write(_("bookmark '%s' deleted\n") % bookmark)
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-def _getmetadata(**opts):
-    metadata = {}
-    date = opts.get('date')
-    user = opts.get('user')
-    if date:
-        metadata['date'] = '%i %i' % util.parsedate(date)
-    if user:
-        metadata['user'] = user
-    return metadata
-
-@eh.command(
-    '^prune|obsolete',
-    [('n', 'new', [], _("successor changeset (DEPRECATED)")),
-     ('s', 'succ', [], _("successor changeset")),
-     ('r', 'rev', [], _("revisions to prune")),
-     ('k', 'keep', None, _("does not modify working copy during prune")),
-     ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")),
-     ('', 'fold', False,
-      _("record a fold (multiple precursors, one successors)")),
-     ('', 'split', False,
-      _("record a split (on precursor, multiple successors)")),
-     ('B', 'bookmark', [], _("remove revs only reachable from given"
-                             " bookmark"))] + metadataopts,
-    _('[OPTION] [-r] REV...'))
-# XXX -U  --noupdate option to prevent wc update and or bookmarks update ?
-def cmdprune(ui, repo, *revs, **opts):
-    """hide changesets by marking them obsolete
-
-    Pruned changesets are obsolete with no successors. If they also have no
-    descendants, they are hidden (invisible to all commands).
-
-    Non-obsolete descendants of pruned changesets become "unstable". Use :hg:`evolve`
-    to handle this situation.
-
-    When you prune the parent of your working copy, Mercurial updates the working
-    copy to a non-obsolete parent.
-
-    You can use ``--succ`` to tell Mercurial that a newer version (successor) of the
-    pruned changeset exists. Mercurial records successor revisions in obsolescence
-    markers.
-
-    You can use the ``--biject`` option to specify a 1-1 mapping (bijection) between
-    revisions to pruned (precursor) and successor changesets. This option may be
-    removed in a future release (with the functionality provided automatically).
-
-    If you specify multiple revisions in ``--succ``, you are recording a "split" and
-    must acknowledge it by passing ``--split``. Similarly, when you prune multiple
-    changesets with a single successor, you must pass the ``--fold`` option.
-    """
-    revs = scmutil.revrange(repo, list(revs) + opts.get('rev'))
-    succs = opts['new'] + opts['succ']
-    bookmarks = set(opts.get('bookmark'))
-    metadata = _getmetadata(**opts)
-    biject = opts.get('biject')
-    fold = opts.get('fold')
-    split = opts.get('split')
-
-    options = [o for o in ('biject', 'fold', 'split') if opts.get(o)]
-    if 1 < len(options):
-        raise error.Abort(_("can only specify one of %s") % ', '.join(options))
-
-    if bookmarks:
-        repomarks, revs = _reachablefrombookmark(repo, revs, bookmarks)
-        if not revs:
-            # no revisions to prune - delete bookmark immediately
-            _deletebookmark(repo, repomarks, bookmarks)
-
-    if not revs:
-        raise error.Abort(_('nothing to prune'))
-
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction('prune')
-        # defines pruned changesets
-        precs = []
-        revs.sort()
-        for p in revs:
-            cp = repo[p]
-            if not cp.mutable():
-                # note: createmarkers() would have raised something anyway
-                raise error.Abort('cannot prune immutable changeset: %s' % cp,
-                                  hint="see 'hg help phases' for details")
-            precs.append(cp)
-        if not precs:
-            raise error.Abort('nothing to prune')
-
-        if _disallowednewunstable(repo, revs):
-            raise error.Abort(_("cannot prune in the middle of a stack"),
-                              hint=_("new unstable changesets are not allowed"))
-
-        # defines successors changesets
-        sucs = scmutil.revrange(repo, succs)
-        sucs.sort()
-        sucs = tuple(repo[n] for n in sucs)
-        if not biject and len(sucs) > 1 and len(precs) > 1:
-            msg = "Can't use multiple successors for multiple precursors"
-            hint = _("use --biject to mark a series as a replacement"
-                     " for another")
-            raise error.Abort(msg, hint=hint)
-        elif biject and len(sucs) != len(precs):
-            msg = "Can't use %d successors for %d precursors" \
-                % (len(sucs), len(precs))
-            raise error.Abort(msg)
-        elif (len(precs) == 1 and len(sucs) > 1) and not split:
-            msg = "please add --split if you want to do a split"
-            raise error.Abort(msg)
-        elif len(sucs) == 1 and len(precs) > 1 and not fold:
-            msg = "please add --fold if you want to do a fold"
-            raise error.Abort(msg)
-        elif biject:
-            relations = [(p, (s,)) for p, s in zip(precs, sucs)]
-        else:
-            relations = [(p, sucs) for p in precs]
-
-        wdp = repo['.']
-
-        if len(sucs) == 1 and len(precs) == 1 and wdp in precs:
-            # '.' killed, so update to the successor
-            newnode = sucs[0]
-        else:
-            # update to an unkilled parent
-            newnode = wdp
-
-            while newnode in precs or newnode.obsolete():
-                newnode = newnode.parents()[0]
-
-        if newnode.node() != wdp.node():
-            if opts.get('keep', False):
-                # This is largely the same as the implementation in
-                # strip.stripcmd(). We might want to refactor this somewhere
-                # common at some point.
-
-                # only reset the dirstate for files that would actually change
-                # between the working context and uctx
-                descendantrevs = repo.revs("%d::." % newnode.rev())
-                changedfiles = []
-                for rev in descendantrevs:
-                    # blindly reset the files, regardless of what actually
-                    # changed
-                    changedfiles.extend(repo[rev].files())
-
-                # reset files that only changed in the dirstate too
-                dirstate = repo.dirstate
-                dirchanges = [f for f in dirstate if dirstate[f] != 'n']
-                changedfiles.extend(dirchanges)
-                repo.dirstate.rebuild(newnode.node(), newnode.manifest(),
-                                      changedfiles)
-                dirstate.write(tr)
-            else:
-                bookactive = repo._activebookmark
-                # Active bookmark that we don't want to delete (with -B option)
-                # we deactivate and move it before the update and reactivate it
-                # after
-                movebookmark = bookactive and not bookmarks
-                if movebookmark:
-                    bookmarksmod.deactivate(repo)
-                    repo._bookmarks[bookactive] = newnode.node()
-                    repo._bookmarks.recordchange(tr)
-                commands.update(ui, repo, newnode.rev())
-                ui.status(_('working directory now at %s\n')
-                          % ui.label(str(newnode), 'evolve.node'))
-                if movebookmark:
-                    bookmarksmod.activate(repo, bookactive)
-
-        # update bookmarks
-        if bookmarks:
-            _deletebookmark(repo, repomarks, bookmarks)
-
-        # create markers
-        obsolete.createmarkers(repo, relations, metadata=metadata)
-
-        # informs that changeset have been pruned
-        ui.status(_('%i changesets pruned\n') % len(precs))
-
-        for ctx in repo.unfiltered().set('bookmark() and %ld', precs):
-            # used to be:
-            #
-            #   ldest = list(repo.set('max((::%d) - obsolete())', ctx))
-            #   if ldest:
-            #      c = ldest[0]
-            #
-            # but then revset took a lazy arrow in the knee and became much
-            # slower. The new forms makes as much sense and a much faster.
-            for dest in ctx.ancestors():
-                if not dest.obsolete():
-                    updatebookmarks = _bookmarksupdater(repo, ctx.node(), tr)
-                    updatebookmarks(dest.node())
-                    break
-
-        tr.close()
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-@eh.command(
-    'amend|refresh',
-    [('A', 'addremove', None,
-      _('mark new/missing files as added/removed before committing')),
-     ('e', 'edit', False, _('invoke editor on commit messages')),
-     ('', 'close-branch', None,
-      _('mark a branch as closed, hiding it from the branch list')),
-     ('s', 'secret', None, _('use the secret phase for committing')),
-    ] + walkopts + commitopts + commitopts2 + commitopts3 + interactiveopt,
-    _('[OPTION]... [FILE]...'))
-def amend(ui, repo, *pats, **opts):
-    """combine a changeset with updates and replace it with a new one
-
-    Commits a new changeset incorporating both the changes to the given files
-    and all the changes from the current parent changeset into the repository.
-
-    See :hg:`commit` for details about committing changes.
-
-    If you don't specify -m, the parent's message will be reused.
-
-    Behind the scenes, Mercurial first commits the update as a regular child
-    of the current parent. Then it creates a new commit on the parent's parents
-    with the updated contents. Then it changes the working copy parent to this
-    new combined changeset. Finally, the old changeset and its update are hidden
-    from :hg:`log` (unless you use --hidden with log).
+    succmarkers = repo.obsstore.successors
 
-    Returns 0 on success, 1 if nothing changed.
-    """
-    opts = opts.copy()
-    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)
-    return commitcmd[0](ui, repo, *pats, **opts)
-
-
-def _touchedbetween(repo, source, dest, match=None):
-    touched = set()
-    for files in repo.status(source, dest, match=match)[:3]:
-        touched.update(files)
-    return touched
-
-def _commitfiltered(repo, ctx, match, target=None):
-    """Recommit ctx with changed files not in match. Return the new
-    node identifier, or None if nothing changed.
-    """
-    base = ctx.p1()
-    if target is None:
-        target = base
-    # ctx
-    initialfiles = _touchedbetween(repo, base, ctx)
-    if base == target:
-        affected = set(f for f in initialfiles if match(f))
-        newcontent = set()
-    else:
-        affected = _touchedbetween(repo, target, ctx, match=match)
-        newcontent = _touchedbetween(repo, target, base, match=match)
-    # The commit touchs all existing files
-    # + all file that needs a new content
-    # - the file affected bny uncommit with the same content than base.
-    files = (initialfiles - affected) | newcontent
-    if not newcontent and files == initialfiles:
-        return None
-
-    # Filter copies
-    copied = copies.pathcopies(target, ctx)
-    copied = dict((dst, src) for dst, src in copied.iteritems()
-                  if dst in files)
-
-    def filectxfn(repo, memctx, path, contentctx=ctx, redirect=newcontent):
-        if path in redirect:
-            return filectxfn(repo, memctx, path, contentctx=target, redirect=())
-        if path not in contentctx:
-            return None
-        fctx = contentctx[path]
-        flags = fctx.flags()
-        mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
-                                  islink='l' in flags,
-                                  isexec='x' in flags,
-                                  copied=copied.get(path))
-        return mctx
-
-    new = context.memctx(repo,
-                         parents=[base.node(), node.nullid],
-                         text=ctx.description(),
-                         files=files,
-                         filectxfn=filectxfn,
-                         user=ctx.user(),
-                         date=ctx.date(),
-                         extra=ctx.extra())
-    # commitctx always create a new revision, no need to check
-    newid = repo.commitctx(new)
-    return newid
-
-def _uncommitdirstate(repo, oldctx, match):
-    """Fix the dirstate after switching the working directory from
-    oldctx to a copy of oldctx not containing changed files matched by
-    match.
-    """
-    ctx = repo['.']
-    ds = repo.dirstate
-    copies = dict(ds.copies())
-    m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
-    for f in m:
-        if ds[f] == 'r':
-            # modified + removed -> removed
-            continue
-        ds.normallookup(f)
-
-    for f in a:
-        if ds[f] == 'r':
-            # added + removed -> unknown
-            ds.drop(f)
-        elif ds[f] != 'a':
-            ds.add(f)
-
-    for f in r:
-        if ds[f] == 'a':
-            # removed + added -> normal
-            ds.normallookup(f)
-        elif ds[f] != 'r':
-            ds.remove(f)
-
-    # Merge old parent and old working dir copies
-    oldcopies = {}
-    for f in (m + a):
-        src = oldctx[f].renamed()
-        if src:
-            oldcopies[f] = src[0]
-    oldcopies.update(copies)
-    copies = dict((dst, oldcopies.get(src, src))
-                  for dst, src in oldcopies.iteritems())
-    # Adjust the dirstate copies
-    for dst, src in copies.iteritems():
-        if (src not in ctx or dst in ctx or ds[dst] != 'a'):
-            src = None
-        ds.copy(src, dst)
-
-@eh.command(
-    '^uncommit',
-    [('a', 'all', None, _('uncommit all changes when no arguments given')),
-     ('r', 'rev', '', _('revert commit content to REV instead')),
-     ] + commands.walkopts,
-    _('[OPTION]... [NAME]'))
-def uncommit(ui, repo, *pats, **opts):
-    """move changes from parent revision to working directory
-
-    Changes to selected files in the checked out revision appear again as
-    uncommitted changed in the working directory. A new revision
-    without the selected changes is created, becomes the checked out
-    revision, and obsoletes the previous one.
-
-    The --include option specifies patterns to uncommit.
-    The --exclude option specifies patterns to keep in the commit.
-
-    The --rev argument let you change the commit file to a content of another
-    revision. It still does not change the content of your file in the working
-    directory.
-
-    Return 0 if changed files are uncommitted.
-    """
-
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        wctx = repo[None]
-        if len(wctx.parents()) <= 0:
-            raise error.Abort(_("cannot uncommit null changeset"))
-        if len(wctx.parents()) > 1:
-            raise error.Abort(_("cannot uncommit while merging"))
-        old = repo['.']
-        if old.phase() == phases.public:
-            raise error.Abort(_("cannot rewrite immutable changeset"))
-        if len(old.parents()) > 1:
-            raise error.Abort(_("cannot uncommit merge changeset"))
-        oldphase = old.phase()
-
-        rev = None
-        if opts.get('rev'):
-            rev = scmutil.revsingle(repo, opts.get('rev'))
-            ctx = repo[None]
-            if ctx.p1() == rev or ctx.p2() == rev:
-                raise error.Abort(_("cannot uncommit to parent changeset"))
-
-        onahead = old.rev() in repo.changelog.headrevs()
-        disallowunstable = not obsolete.isenabled(repo,
-                                                  obsolete.allowunstableopt)
-        if disallowunstable and not onahead:
-            raise error.Abort(_("cannot uncommit in the middle of a stack"))
-
-        # Recommit the filtered changeset
-        tr = repo.transaction('uncommit')
-        updatebookmarks = _bookmarksupdater(repo, old.node(), tr)
-        newid = None
-        includeorexclude = opts.get('include') or opts.get('exclude')
-        if (pats or includeorexclude or opts.get('all')):
-            match = scmutil.match(old, pats, opts)
-            newid = _commitfiltered(repo, old, match, target=rev)
-        if newid is None:
-            raise error.Abort(_('nothing to uncommit'),
-                              hint=_("use --all to uncommit all files"))
-        # Move local changes on filtered changeset
-        obsolete.createmarkers(repo, [(old, (repo[newid],))])
-        phases.retractboundary(repo, tr, oldphase, [newid])
-        with repo.dirstate.parentchange():
-            repo.dirstate.setparents(newid, node.nullid)
-            _uncommitdirstate(repo, old, match)
-        updatebookmarks(newid)
-        if not repo[newid].files():
-            ui.warn(_("new changeset is empty\n"))
-            ui.status(_("(use 'hg prune .' to remove it)\n"))
-        tr.close()
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-@eh.wrapcommand('commit')
-def commitwrapper(orig, ui, repo, *arg, **kwargs):
-    tr = None
-    if kwargs.get('amend', False):
-        wlock = lock = None
-    else:
-        wlock = repo.wlock()
-        lock = repo.lock()
-    try:
-        obsoleted = kwargs.get('obsolete', [])
-        if obsoleted:
-            obsoleted = repo.set('%lr', obsoleted)
-        result = orig(ui, repo, *arg, **kwargs)
-        if not result: # commit succeeded
-            new = repo['-1']
-            oldbookmarks = []
-            markers = []
-            for old in obsoleted:
-                oldbookmarks.extend(repo.nodebookmarks(old.node()))
-                markers.append((old, (new,)))
-            if markers:
-                obsolete.createmarkers(repo, markers)
-            for book in oldbookmarks:
-                repo._bookmarks[book] = new.node()
-            if oldbookmarks:
-                if not wlock:
-                    wlock = repo.wlock()
-                if not lock:
-                    lock = repo.lock()
-                tr = repo.transaction('commit')
-                repo._bookmarks.recordchange(tr)
-                tr.close()
-        return result
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-@eh.command(
-    '^split',
-    [('r', 'rev', [], _("revision to split")),
-    ] + commitopts + commitopts2,
-    _('hg split [OPTION]... [-r] REV'))
-def cmdsplit(ui, repo, *revs, **opts):
-    """split a changeset into smaller changesets
-
-    By default, split the current revision by prompting for all its hunks to be
-    redistributed into new changesets.
-
-    Use --rev to split a given changeset instead.
-    """
-    tr = wlock = lock = None
-    newcommits = []
-
-    revarg = (list(revs) + opts.get('rev')) or ['.']
-    if len(revarg) != 1:
-        msg = _("more than one revset is given")
-        hnt = _("use either `hg split <rs>` or `hg split --rev <rs>`, not both")
-        raise error.Abort(msg, hint=hnt)
-
-    rev = scmutil.revsingle(repo, revarg[0])
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        cmdutil.bailifchanged(repo)
-        tr = repo.transaction('split')
-        ctx = repo[rev]
-        r = ctx.rev()
-        disallowunstable = not obsolete.isenabled(repo,
-                                                  obsolete.allowunstableopt)
-        if disallowunstable:
-            # XXX We should check head revs
-            if repo.revs("(%d::) - %d", rev, rev):
-                raise error.Abort(_("cannot split commit: %s not a head") % ctx)
-
-        if len(ctx.parents()) > 1:
-            raise error.Abort(_("cannot split merge commits"))
-        prev = ctx.p1()
-        bmupdate = _bookmarksupdater(repo, ctx.node(), tr)
-        bookactive = repo._activebookmark
-        if bookactive is not None:
-            repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark)
-        bookmarksmod.deactivate(repo)
-        hg.update(repo, prev)
-
-        commands.revert(ui, repo, rev=r, all=True)
-
-        def haschanges():
-            modified, added, removed, deleted = repo.status()[:4]
-            return modified or added or removed or deleted
-        msg = ("HG: This is the original pre-split commit message. "
-               "Edit it as appropriate.\n\n")
-        msg += ctx.description()
-        opts['message'] = msg
-        opts['edit'] = True
-        while haschanges():
-            pats = ()
-            cmdutil.dorecord(ui, repo, commands.commit, 'commit', False,
-                             cmdutil.recordfilter, *pats, **opts)
-            # TODO: Does no seem like the best way to do this
-            # We should make dorecord return the newly created commit
-            newcommits.append(repo['.'])
-            if haschanges():
-                if ui.prompt('Done splitting? [yN]', default='n') == 'y':
-                    commands.commit(ui, repo, **opts)
-                    newcommits.append(repo['.'])
-                    break
+    # Stack of nodes we search successors sets for
+    toproceed = [initialnode]
+    # set version of above list for fast loop detection
+    # element added to "toproceed" must be added here
+    stackedset = set(toproceed)
+    if cache is None:
+        cache = {}
+    while toproceed:
+        current = toproceed[-1]
+        if current in cache:
+            stackedset.remove(toproceed.pop())
+        elif current != initialnode and current in repo:
+            # We have a valid direct successors.
+            cache[current] = [(current,)]
+        elif current not in succmarkers:
+            if current in repo:
+                # We have a valid last successors.
+                cache[current] = [(current,)]
             else:
-                ui.status(_("no more change to split\n"))
-
-        if newcommits:
-            tip = repo[newcommits[-1]]
-            bmupdate(tip.node())
-            if bookactive is not None:
-                bookmarksmod.activate(repo, bookactive)
-            obsolete.createmarkers(repo, [(repo[r], newcommits)])
-        tr.close()
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-
-@eh.wrapcommand('strip', extension='strip', opts=[
-    ('', 'bundle', None, _("delete the commit entirely and move it to a "
-                           "backup bundle")),
-    ])
-def stripwrapper(orig, ui, repo, *revs, **kwargs):
-    if (not ui.configbool('experimental', 'prunestrip') or
-        kwargs.get('bundle', False)):
-        return orig(ui, repo, *revs, **kwargs)
-
-    if kwargs.get('force'):
-        ui.warn(_("warning: --force has no effect during strip with evolve "
-                  "enabled\n"))
-    if kwargs.get('no_backup', False):
-        ui.warn(_("warning: --no-backup has no effect during strips with "
-                  "evolve enabled\n"))
-
-    revs = list(revs) + kwargs.pop('rev', [])
-    revs = set(scmutil.revrange(repo, revs))
-    revs = repo.revs("(%ld)::", revs)
-    kwargs['rev'] = []
-    kwargs['new'] = []
-    kwargs['succ'] = []
-    kwargs['biject'] = False
-    return cmdprune(ui, repo, *revs, **kwargs)
-
-@eh.command(
-    '^touch',
-    [('r', 'rev', [], 'revision to update'),
-     ('D', 'duplicate', False,
-      'do not mark the new revision as successor of the old one'),
-     ('A', 'allowdivergence', False,
-      'mark the new revision as successor of the old one potentially creating '
-      'divergence')],
-    # allow to choose the seed ?
-    _('[-r] revs'))
-def touch(ui, repo, *revs, **opts):
-    """create successors that are identical to their predecessors except
-    for the changeset ID
-
-    This is used to "resurrect" changesets
-    """
-    duplicate = opts['duplicate']
-    allowdivergence = opts['allowdivergence']
-    revs = list(revs)
-    revs.extend(opts['rev'])
-    if not revs:
-        revs = ['.']
-    revs = scmutil.revrange(repo, revs)
-    if not revs:
-        ui.write_err('no revision to touch\n')
-        return 1
-    if not duplicate and repo.revs('public() and %ld', revs):
-        raise error.Abort("can't touch public revision")
-    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction('touch')
-        revs.sort() # ensure parent are run first
-        newmapping = {}
-        for r in revs:
-            ctx = repo[r]
-            extra = ctx.extra().copy()
-            extra['__touch-noise__'] = random.randint(0, 0xffffffff)
-            # search for touched parent
-            p1 = ctx.p1().node()
-            p2 = ctx.p2().node()
-            p1 = newmapping.get(p1, p1)
-            p2 = newmapping.get(p2, p2)
-
-            if not (duplicate or allowdivergence):
-                # The user hasn't yet decided what to do with the revived
-                # cset, let's ask
-                sset = obsolete.successorssets(repo, ctx.node())
-                nodivergencerisk = (len(sset) == 0 or
-                                    (len(sset) == 1 and
-                                     len(sset[0]) == 1 and
-                                     repo[sset[0][0]].rev() == ctx.rev()
-                                    ))
-                if nodivergencerisk:
-                    duplicate = False
+                # Final obsolete version is unknown locally.
+                # Do not count that as a valid successors
+                cache[current] = []
+        else:
+            for mark in sorted(succmarkers[current]):
+                for suc in mark[1]:
+                    if suc not in cache:
+                        if suc in stackedset:
+                            # cycle breaking
+                            cache[suc] = []
+                        else:
+                            # case (3) If we have not computed successors sets
+                            # of one of those successors we add it to the
+                            # `toproceed` stack and stop all work for this
+                            # iteration.
+                            toproceed.append(suc)
+                            stackedset.add(suc)
+                            break
                 else:
-                    displayer.show(ctx)
-                    index = ui.promptchoice(
-                        _("reviving this changeset will create divergence"
-                          " unless you make a duplicate.\n(a)llow divergence or"
-                          " (d)uplicate the changeset? $$ &Allowdivergence $$ "
-                          "&Duplicate"), 0)
-                    choice = ['allowdivergence', 'duplicate'][index]
-                    if choice == 'allowdivergence':
-                        duplicate = False
+                    continue
+                break
+            else:
+                succssets = []
+                for mark in sorted(succmarkers[current]):
+                    # successors sets contributed by this marker
+                    markss = [[]]
+                    for suc in mark[1]:
+                        # cardinal product with previous successors
+                        productresult = []
+                        for prefix in markss:
+                            for suffix in cache[suc]:
+                                newss = list(prefix)
+                                for part in suffix:
+                                    # do not duplicated entry in successors set
+                                    # first entry wins.
+                                    if part not in newss:
+                                        newss.append(part)
+                                productresult.append(newss)
+                        markss = productresult
+                    succssets.extend(markss)
+                # remove duplicated and subset
+                seen = []
+                final = []
+                candidate = sorted(((set(s), s) for s in succssets if s),
+                                   key=lambda x: len(x[1]), reverse=True)
+                for setversion, listversion in candidate:
+                    for seenset in seen:
+                        if setversion.issubset(seenset):
+                            break
                     else:
-                        duplicate = True
-
-            new, unusedvariable = rewrite(repo, ctx, [], ctx,
-                                          [p1, p2],
-                                          commitopts={'extra': extra})
-            # store touched version to help potential children
-            newmapping[ctx.node()] = new
-
-            if not duplicate:
-                obsolete.createmarkers(repo, [(ctx, (repo[new],))])
-            phases.retractboundary(repo, tr, ctx.phase(), [new])
-            if ctx in repo[None].parents():
-                with repo.dirstate.parentchange():
-                    repo.dirstate.setparents(new, node.nullid)
-        tr.close()
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-@eh.command(
-    '^fold|squash',
-    [('r', 'rev', [], _("revision to fold")),
-     ('', 'exact', None, _("only fold specified revisions")),
-     ('', 'from', None, _("fold revisions linearly to working copy parent"))
-    ] + commitopts + commitopts2,
-    _('hg fold [OPTION]... [-r] REV'))
-def fold(ui, repo, *revs, **opts):
-    """fold multiple revisions into a single one
-
-    With --from, folds all the revisions linearly between the given revisions
-    and the parent of the working directory.
-
-    With --exact, folds only the specified revisions while ignoring the
-    parent of the working directory. In this case, the given revisions must
-    form a linear unbroken chain.
-
-    .. container:: verbose
-
-     Some examples:
-
-     - Fold the current revision with its parent::
-
-         hg fold --from .^
-
-     - Fold all draft revisions with working directory parent::
-
-         hg fold --from 'draft()'
-
-       See :hg:`help phases` for more about draft revisions and
-       :hg:`help revsets` for more about the `draft()` keyword
-
-     - Fold revisions between 3 and 6 with the working directory parent::
-
-         hg fold --from 3::6
-
-     - Fold revisions 3 and 4:
-
-        hg fold "3 + 4" --exact
-
-     - Only fold revisions linearly between foo and @::
-
-         hg fold foo::@ --exact
-    """
-    revs = list(revs)
-    revs.extend(opts['rev'])
-    if not revs:
-        raise error.Abort(_('no revisions specified'))
-
-    revs = scmutil.revrange(repo, revs)
-
-    if opts['from'] and opts['exact']:
-        raise error.Abort(_('cannot use both --from and --exact'))
-    elif opts['from']:
-        # Try to extend given revision starting from the working directory
-        extrevs = repo.revs('(%ld::.) or (.::%ld)', revs, revs)
-        discardedrevs = [r for r in revs if r not in extrevs]
-        if discardedrevs:
-            msg = _("cannot fold non-linear revisions")
-            hint = _("given revisions are unrelated to parent of working"
-                     " directory")
-            raise error.Abort(msg, hint=hint)
-        revs = extrevs
-    elif opts['exact']:
-        # Nothing to do; "revs" is already set correctly
-        pass
-    else:
-        raise error.Abort(_('must specify either --from or --exact'))
-
-    if not revs:
-        raise error.Abort(_('specified revisions evaluate to an empty set'),
-                          hint=_('use different revision arguments'))
-    elif len(revs) == 1:
-        ui.write_err(_('single revision specified, nothing to fold\n'))
-        return 1
-
-    wlock = lock = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-
-        root, head = _foldcheck(repo, revs)
-
-        tr = repo.transaction('fold')
-        try:
-            commitopts = opts.copy()
-            allctx = [repo[r] for r in revs]
-            targetphase = max(c.phase() for c in allctx)
-
-            if commitopts.get('message') or commitopts.get('logfile'):
-                commitopts['edit'] = False
-            else:
-                msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
-                msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
-                         (c.rev(), c.description()) for c in allctx]
-                commitopts['message'] = "\n".join(msgs)
-                commitopts['edit'] = True
-
-            newid, unusedvariable = rewrite(repo, root, allctx, head,
-                                            [root.p1().node(),
-                                             root.p2().node()],
-                                            commitopts=commitopts)
-            phases.retractboundary(repo, tr, targetphase, [newid])
-            obsolete.createmarkers(repo, [(ctx, (repo[newid],))
-                                   for ctx in allctx])
-            tr.close()
-        finally:
-            tr.release()
-        ui.status('%i changesets folded\n' % len(revs))
-        if repo['.'].rev() in revs:
-            hg.update(repo, newid)
-    finally:
-        lockmod.release(lock, wlock)
-
-@eh.command(
-    '^metaedit',
-    [('r', 'rev', [], _("revision to edit")),
-     ('', 'fold', None, _("also fold specified revisions into one")),
-    ] + commitopts + commitopts2,
-    _('hg metaedit [OPTION]... [-r] [REV]'))
-def metaedit(ui, repo, *revs, **opts):
-    """edit commit information
-
-    Edits the commit information for the specified revisions. By default, edits
-    commit information for the working directory parent.
-
-    With --fold, also folds multiple revisions into one if necessary. In this
-    case, the given revisions must form a linear unbroken chain.
-
-    .. container:: verbose
-
-     Some examples:
-
-     - Edit the commit message for the working directory parent::
-
-         hg metaedit
-
-     - Change the username for the working directory parent::
-
-         hg metaedit --user 'New User <new-email@example.com>'
-
-     - Combine all draft revisions that are ancestors of foo but not of @ into
-       one::
-
-         hg metaedit --fold 'draft() and only(foo,@)'
-
-       See :hg:`help phases` for more about draft revisions, and
-       :hg:`help revsets` for more about the `draft()` and `only()` keywords.
-    """
-    revs = list(revs)
-    revs.extend(opts['rev'])
-    if not revs:
-        if opts['fold']:
-            raise error.Abort(_('revisions must be specified with --fold'))
-        revs = ['.']
-
-    wlock = lock = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-
-        revs = scmutil.revrange(repo, revs)
-        if not opts['fold'] and len(revs) > 1:
-            # TODO: handle multiple revisions. This is somewhat tricky because
-            # if we want to edit a series of commits:
-            #
-            #   a ---- b ---- c
-            #
-            # we need to rewrite a first, then directly rewrite b on top of the
-            # new a, then rewrite c on top of the new b. So we need to handle
-            # revisions in topological order.
-            raise error.Abort(_('editing multiple revisions without --fold is '
-                                'not currently supported'))
-
-        if opts['fold']:
-            root, head = _foldcheck(repo, revs)
-        else:
-            if repo.revs("%ld and public()", revs):
-                raise error.Abort(_('cannot edit commit information for public '
-                                    'revisions'))
-            newunstable = _disallowednewunstable(repo, revs)
-            if newunstable:
-                msg = _('cannot edit commit information in the middle'
-                        ' of a stack')
-                hint = _('%s will become unstable and new unstable changes'
-                         ' are not allowed')
-                hint %= repo[newunstable.first()]
-                raise error.Abort(msg, hint=hint)
-            root = head = repo[revs.first()]
-
-        wctx = repo[None]
-        p1 = wctx.p1()
-        tr = repo.transaction('metaedit')
-        newp1 = None
-        try:
-            commitopts = opts.copy()
-            allctx = [repo[r] for r in revs]
-            targetphase = max(c.phase() for c in allctx)
-
-            if commitopts.get('message') or commitopts.get('logfile'):
-                commitopts['edit'] = False
-            else:
-                if opts['fold']:
-                    msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
-                    msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
-                             (c.rev(), c.description()) for c in allctx]
-                else:
-                    msgs = [head.description()]
-                commitopts['message'] = "\n".join(msgs)
-                commitopts['edit'] = True
-
-            # TODO: if the author and message are the same, don't create a new
-            # hash. Right now we create a new hash because the date can be
-            # different.
-            newid, created = rewrite(repo, root, allctx, head,
-                                     [root.p1().node(), root.p2().node()],
-                                     commitopts=commitopts)
-            if created:
-                if p1.rev() in revs:
-                    newp1 = newid
-                phases.retractboundary(repo, tr, targetphase, [newid])
-                obsolete.createmarkers(repo, [(ctx, (repo[newid],))
-                                              for ctx in allctx])
-            else:
-                ui.status(_("nothing changed\n"))
-            tr.close()
-        finally:
-            tr.release()
-
-        if opts['fold']:
-            ui.status('%i changesets folded\n' % len(revs))
-        if newp1 is not None:
-            hg.update(repo, newp1)
-    finally:
-        lockmod.release(lock, wlock)
-
-def _foldcheck(repo, revs):
-    roots = repo.revs('roots(%ld)', revs)
-    if len(roots) > 1:
-        raise error.Abort(_("cannot fold non-linear revisions "
-                            "(multiple roots given)"))
-    root = repo[roots.first()]
-    if root.phase() <= phases.public:
-        raise error.Abort(_("cannot fold public revisions"))
-    heads = repo.revs('heads(%ld)', revs)
-    if len(heads) > 1:
-        raise error.Abort(_("cannot fold non-linear revisions "
-                            "(multiple heads given)"))
-    head = repo[heads.first()]
-    if _disallowednewunstable(repo, revs):
-        msg = _("cannot fold chain not ending with a head or with branching")
-        hint = _("new unstable changesets are not allowed")
-        raise error.Abort(msg, hint=hint)
-    return root, head
-
-def _disallowednewunstable(repo, revs):
-    allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
-    if allowunstable:
-        return revset.baseset()
-    return repo.revs("(%ld::) - %ld", revs, revs)
-
-@eh.wrapcommand('graft')
-def graftwrapper(orig, ui, repo, *revs, **kwargs):
-    kwargs = dict(kwargs)
-    revs = list(revs) + kwargs.get('rev', [])
-    kwargs['rev'] = []
-    obsoleted = kwargs.setdefault('obsolete', [])
-
-    wlock = lock = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        if kwargs.get('old_obsolete'):
-            if kwargs.get('continue'):
-                obsoleted.extend(repo.vfs.read('graftstate').splitlines())
-            else:
-                obsoleted.extend(revs)
-        # convert obsolete target into revs to avoid alias joke
-        obsoleted[:] = [str(i) for i in repo.revs('%lr', obsoleted)]
-        if obsoleted and len(revs) > 1:
-
-            raise error.Abort(_('cannot graft multiple revisions while '
-                                'obsoleting (for now).'))
-
-        return commitwrapper(orig, ui, repo, *revs, **kwargs)
-    finally:
-        lockmod.release(lock, wlock)
-
-@eh.extsetup
-def oldevolveextsetup(ui):
-    for cmd in ['prune', 'uncommit', 'touch', 'fold']:
-        try:
-            entry = extensions.wrapcommand(cmdtable, cmd,
-                                           warnobserrors)
-        except error.UnknownCommand:
-            # Commands may be disabled
-            continue
-
-    entry = cmdutil.findcmd('commit', commands.table)[1]
-    entry[1].append(('o', 'obsolete', [],
-                     _("make commit obsolete this revision (DEPRECATED)")))
-    entry = cmdutil.findcmd('graft', commands.table)[1]
-    entry[1].append(('o', 'obsolete', [],
-                     _("make graft obsoletes this revision (DEPRECATED)")))
-    entry[1].append(('O', 'old-obsolete', False,
-                     _("make graft obsoletes its source (DEPRECATED)")))
-
-@eh.wrapfunction(obsolete, '_checkinvalidmarkers')
-def _checkinvalidmarkers(orig, markers):
-    """search for marker with invalid data and raise error if needed
-
-    Exist as a separated function to allow the evolve extension for a more
-    subtle handling.
-    """
-    if 'debugobsconvert' in sys.argv:
-        return
-    for mark in markers:
-        if node.nullid in mark[1]:
-            msg = _('bad obsolescence marker detected: invalid successors nullid')
-            hint = _('You should run `hg debugobsconvert`')
-            raise error.Abort(msg, hint=hint)
-
-@eh.command(
-    'debugobsconvert',
-    [('', 'new-format', obsexchange._bestformat, _('Destination format for markers.'))],
-    '')
-def debugobsconvert(ui, repo, new_format):
-    origmarkers = repo.obsstore._all  # settle version
-    if new_format == repo.obsstore._version:
-        msg = _('New format is the same as the old format, not upgrading!')
-        raise error.Abort(msg)
-    f = repo.svfs('obsstore', 'wb', atomictemp=True)
-    known = set()
-    markers = []
-    for m in origmarkers:
-        # filter out invalid markers
-        if nullid in m[1]:
-            m = list(m)
-            m[1] = tuple(s for s in m[1] if s != nullid)
-            m = tuple(m)
-        if m in known:
-            continue
-        known.add(m)
-        markers.append(m)
-    ui.write(_('Old store is version %d, will rewrite in version %d\n') % (
-        repo.obsstore._version, new_format))
-    map(f.write, obsolete.encodemarkers(markers, True, new_format))
-    f.close()
-    ui.write(_('Done!\n'))
-
-
-def _helploader(ui):
-    return help.gettext(evolutionhelptext)
-
-@eh.uisetup
-def _setuphelp(ui):
-    for entry in help.helptable:
-        if entry[0] == "evolution":
-            break
-    else:
-        help.helptable.append((["evolution"], _("Safely Rewriting History"),
-                              _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)
-    if nodenew is not None:
-        phases.retractboundary(repo, tr, destphase, [nodenew])
-        obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))])
-        for book in oldbookmarks:
-            repo._bookmarks[book] = nodenew
-    else:
-        obsolete.createmarkers(repo, [(repo[nodesrc], ())])
-        # Behave like rebase, move bookmarks to dest
-        for book in oldbookmarks:
-            repo._bookmarks[book] = dest.node()
-    for book in destbookmarks: # restore bookmark that rebase move
-        repo._bookmarks[book] = dest.node()
-    if oldbookmarks or destbookmarks:
-        repo._bookmarks.recordchange(tr)
-
-evolvestateversion = 0
-
-@eh.uisetup
-def setupevolveunfinished(ui):
-    data = ('evolvestate', True, False, _('evolve in progress'),
-            _("use 'hg evolve --continue' or 'hg update -C .' to abort"))
-    cmdutil.unfinishedstates.append(data)
-
-@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 _evolvestatewrite(repo, state):
-    # [version]
-    # [type][length][content]
-    #
-    # `version` is a 4 bytes integer (handled at higher level)
-    # `type` is a single character, `length` is a 4 byte integer, and
-    # `content` is an arbitrary byte sequence of length `length`.
-    f = repo.vfs('evolvestate', 'w')
-    try:
-        f.write(_pack('>I', evolvestateversion))
-        current = state['current']
-        key = 'C' # as in 'current'
-        format = '>sI%is' % len(current)
-        f.write(_pack(format, key, len(current), current))
-    finally:
-        f.close()
-
-def _evolvestateread(repo):
-    try:
-        f = repo.vfs('evolvestate')
-    except IOError as err:
-        if err.errno != errno.ENOENT:
-            raise
-        return None
-    try:
-        versionblob = f.read(4)
-        if len(versionblob) < 4:
-            repo.ui.debug('ignoring corrupted evolvestte (file contains %i bits)'
-                          % len(versionblob))
-            return None
-        version = _unpack('>I', versionblob)[0]
-        if version != evolvestateversion:
-            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 = _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()
-
-def _evolvestatedelete(repo):
-    util.unlinkpath(repo.vfs.join('evolvestate'), ignoremissing=True)
-
-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, ['local', 'graft'], True)
+                        final.append(listversion)
+                        seen.append(setversion)
+                final.reverse() # put small successors set first
+                cache[current] = final
+    return cache[initialnode]
--- a/tests/test-discovery-obshashrange.t
+++ b/tests/test-discovery-obshashrange.t
@@ -534,17 +534,17 @@
   $ hg debugobshashrange --subranges --rev 'heads(all())'
            rev         node        index         size        depth      obshash
              7 f69452c5b1af            0            7            7 000000000000
-             5 45f8b879de92            0            6            6 b8a4206b0fc6
+             5 45f8b879de92            0            6            6 7c49a958a9ac
              3 2dc09a01254d            0            4            4 8932bf980bb4
              7 f69452c5b1af            4            3            7 000000000000
              3 2dc09a01254d            2            2            4 ce1937ca1278
-             5 45f8b879de92            4            2            6 31fc49d36a59
+             5 45f8b879de92            4            2            6 c6795525c540
              1 66f7d451a68b            0            2            2 327c7dd73d29
              6 c8d03c1b5e94            4            2            6 89755fd39e6d
              2 01241442b3c2            2            1            3 1ed3c61fb39a
              0 1ea73414a91b            0            1            1 000000000000
              3 2dc09a01254d            3            1            4 26f996446ecb
-             5 45f8b879de92            5            1            6 1a0c08180b65
+             5 45f8b879de92            5            1            6 796507769034
              1 66f7d451a68b            1            1            2 327c7dd73d29
              4 bebd167eb94d            4            1            5 b21465ecb790
              6 c8d03c1b5e94            5            1            6 446c2dc3bce5
@@ -586,17 +586,17 @@
   $ hg debugobshashrange --subranges --rev 'heads(all())'
            rev         node        index         size        depth      obshash
              7 f69452c5b1af            0            7            7 000000000000
-             5 45f8b879de92            0            6            6 b8a4206b0fc6
+             5 45f8b879de92            0            6            6 7c49a958a9ac
              3 2dc09a01254d            0            4            4 8932bf980bb4
              7 f69452c5b1af            4            3            7 000000000000
              3 2dc09a01254d            2            2            4 ce1937ca1278
-             5 45f8b879de92            4            2            6 31fc49d36a59
+             5 45f8b879de92            4            2            6 c6795525c540
              1 66f7d451a68b            0            2            2 327c7dd73d29
              6 c8d03c1b5e94            4            2            6 89755fd39e6d
              2 01241442b3c2            2            1            3 1ed3c61fb39a
              0 1ea73414a91b            0            1            1 000000000000
              3 2dc09a01254d            3            1            4 26f996446ecb
-             5 45f8b879de92            5            1            6 1a0c08180b65
+             5 45f8b879de92            5            1            6 796507769034
              1 66f7d451a68b            1            1            2 327c7dd73d29
              4 bebd167eb94d            4            1            5 b21465ecb790
              6 c8d03c1b5e94            5            1            6 446c2dc3bce5
@@ -631,18 +631,18 @@
   $ hg debugobshashrange --subranges --rev 'heads(all())'
            rev         node        index         size        depth      obshash
              8 4de32a90b66c            0            8            8 c7f1f7e9925b
-             5 45f8b879de92            0            6            6 b8a4206b0fc6
+             5 45f8b879de92            0            6            6 7c49a958a9ac
              3 2dc09a01254d            0            4            4 8932bf980bb4
              8 4de32a90b66c            4            4            8 c681c3e58c27
              3 2dc09a01254d            2            2            4 ce1937ca1278
-             5 45f8b879de92            4            2            6 31fc49d36a59
+             5 45f8b879de92            4            2            6 c6795525c540
              8 4de32a90b66c            6            2            8 033544c939f0
              1 66f7d451a68b            0            2            2 327c7dd73d29
              6 c8d03c1b5e94            4            2            6 89755fd39e6d
              2 01241442b3c2            2            1            3 1ed3c61fb39a
              0 1ea73414a91b            0            1            1 000000000000
              3 2dc09a01254d            3            1            4 26f996446ecb
-             5 45f8b879de92            5            1            6 1a0c08180b65
+             5 45f8b879de92            5            1            6 796507769034
              8 4de32a90b66c            7            1            8 033544c939f0
              1 66f7d451a68b            1            1            2 327c7dd73d29
              4 bebd167eb94d            4            1            5 b21465ecb790
@@ -760,18 +760,18 @@
   $ hg debugobshashrange --subranges --rev 'heads(all())'
            rev         node        index         size        depth      obshash
              7 4de32a90b66c            0            8            8 c7f1f7e9925b
-             8 45f8b879de92            0            6            6 b8a4206b0fc6
+             8 45f8b879de92            0            6            6 7c49a958a9ac
              3 2dc09a01254d            0            4            4 8932bf980bb4
              7 4de32a90b66c            4            4            8 c681c3e58c27
              3 2dc09a01254d            2            2            4 ce1937ca1278
-             8 45f8b879de92            4            2            6 31fc49d36a59
+             8 45f8b879de92            4            2            6 c6795525c540
              7 4de32a90b66c            6            2            8 033544c939f0
              1 66f7d451a68b            0            2            2 327c7dd73d29
              5 c8d03c1b5e94            4            2            6 89755fd39e6d
              2 01241442b3c2            2            1            3 1ed3c61fb39a
              0 1ea73414a91b            0            1            1 000000000000
              3 2dc09a01254d            3            1            4 26f996446ecb
-             8 45f8b879de92            5            1            6 1a0c08180b65
+             8 45f8b879de92            5            1            6 796507769034
              7 4de32a90b66c            7            1            8 033544c939f0
              1 66f7d451a68b            1            1            2 327c7dd73d29
              4 bebd167eb94d            4            1            5 b21465ecb790
new file mode 100644
--- /dev/null
+++ b/tests/test-evolve-cycles.t
@@ -0,0 +1,397 @@
+Test that evolve related algorithms don't crash on obs markers cycles
+
+Global setup
+============
+
+  $ . $TESTDIR/testlib/common.sh
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > interactive = true
+  > [phases]
+  > publish=False
+  > [extensions]
+  > evolve =
+  > EOF
+
+Test with cycle
+===============
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/cycle
+  $ cd $TESTTMP/cycle
+  $ mkcommit ROOT
+  $ mkcommit A
+  $ mkcommit B
+  $ mkcommit C
+  $ hg log -G
+  @  changeset:   3:a8df460dbbfe
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C
+  |
+  o  changeset:   2:c473644ee0e9
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B
+  |
+  o  changeset:   1:2a34000d3544
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Create a cycle
+  $ hg prune -s "desc(B)" "desc(A)"
+  1 changesets pruned
+  2 new unstable changesets
+  $ hg prune -s "desc(C)" "desc(B)"
+  1 changesets pruned
+  $ hg prune -s "desc(A)" "desc(C)"
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  working directory now at 2a34000d3544
+  1 changesets pruned
+  $ hg log --hidden -G
+  x  changeset:   3:a8df460dbbfe
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C
+  |
+  x  changeset:   2:c473644ee0e9
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B
+  |
+  @  changeset:   1:2a34000d3544
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Actual test
+-----------
+
+Check that debugobshistory never crash on a cycle
+
+  $ hg obslog "desc(A)" --hidden
+  @  2a34000d3544 (1) A
+  |    rewritten by test (*) as c473644ee0e9 (glob)
+  |
+  x  a8df460dbbfe (3) C
+  |    rewritten by test (*) as 2a34000d3544 (glob)
+  |
+  x  c473644ee0e9 (2) B
+  |    rewritten by test (*) as a8df460dbbfe (glob)
+  |
+
+  $ hg obslog "desc(B)" --hidden
+  @  2a34000d3544 (1) A
+  |    rewritten by test (*) as c473644ee0e9 (glob)
+  |
+  x  a8df460dbbfe (3) C
+  |    rewritten by test (*) as 2a34000d3544 (glob)
+  |
+  x  c473644ee0e9 (2) B
+  |    rewritten by test (*) as a8df460dbbfe (glob)
+  |
+
+  $ hg obslog "desc(C)" --hidden
+  @  2a34000d3544 (1) A
+  |    rewritten by test (*) as c473644ee0e9 (glob)
+  |
+  x  a8df460dbbfe (3) C
+  |    rewritten by test (*) as 2a34000d3544 (glob)
+  |
+  x  c473644ee0e9 (2) B
+  |    rewritten by test (*) as a8df460dbbfe (glob)
+  |
+
+Check that all option don't crash on a cycle either
+
+  $ hg obslog "desc(C)" --hidden --all
+  @  2a34000d3544 (1) A
+  |    rewritten by test (*) as c473644ee0e9 (glob)
+  |
+  x  a8df460dbbfe (3) C
+  |    rewritten by test (*) as 2a34000d3544 (glob)
+  |
+  x  c473644ee0e9 (2) B
+  |    rewritten by test (*) as a8df460dbbfe (glob)
+  |
+
+Test with multiple cyles
+========================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/multiple-cycle
+  $ cd $TESTTMP/multiple-cycle
+  $ mkcommit ROOT
+  $ mkcommit A
+  $ mkcommit B
+  $ mkcommit C
+  $ mkcommit D
+  $ mkcommit E
+  $ mkcommit F
+  $ hg log -G
+  @  changeset:   6:d9f908fde1a1
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     F
+  |
+  o  changeset:   5:0da815c333f6
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     E
+  |
+  o  changeset:   4:868d2e0eb19c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     D
+  |
+  o  changeset:   3:a8df460dbbfe
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C
+  |
+  o  changeset:   2:c473644ee0e9
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B
+  |
+  o  changeset:   1:2a34000d3544
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Create a first cycle
+  $ hg prune -s "desc(B)" "desc(A)"
+  1 changesets pruned
+  5 new unstable changesets
+  $ hg prune -s "desc(C)" "desc(B)"
+  1 changesets pruned
+  $ hg prune --split -s "desc(A)" -s "desc(D)" "desc(C)"
+  1 changesets pruned
+And create a second one
+  $ hg prune -s "desc(E)" "desc(D)"
+  1 changesets pruned
+  $ hg prune -s "desc(F)" "desc(E)"
+  1 changesets pruned
+  $ hg prune -s "desc(D)" "desc(F)"
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  working directory now at 868d2e0eb19c
+  1 changesets pruned
+  $ hg log --hidden -G
+  x  changeset:   6:d9f908fde1a1
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     F
+  |
+  x  changeset:   5:0da815c333f6
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     E
+  |
+  @  changeset:   4:868d2e0eb19c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     D
+  |
+  x  changeset:   3:a8df460dbbfe
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C
+  |
+  x  changeset:   2:c473644ee0e9
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B
+  |
+  x  changeset:   1:2a34000d3544
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Actual test
+-----------
+
+Check that debugobshistory never crash on a cycle
+
+  $ hg obslog "desc(D)" --hidden
+  x  0da815c333f6 (5) E
+  |    rewritten by test (*) as d9f908fde1a1 (glob)
+  |
+  @    868d2e0eb19c (4) D
+  |\     rewritten by test (*) as 0da815c333f6 (glob)
+  | |
+  | x  d9f908fde1a1 (6) F
+  | |    rewritten by test (*) as 868d2e0eb19c (glob)
+  | |
+  +---x  2a34000d3544 (1) A
+  | |      rewritten by test (*) as c473644ee0e9 (glob)
+  | |
+  x |  a8df460dbbfe (3) C
+  | |    rewritten by test (*) as 2a34000d3544, 868d2e0eb19c (glob)
+  | |
+  x |  c473644ee0e9 (2) B
+  | |    rewritten by test (*) as a8df460dbbfe (glob)
+  | |
+Check that all option don't crash either on a cycle
+  $ hg obslog --all --hidden "desc(F)"
+  x  0da815c333f6 (5) E
+  |    rewritten by test (*) as d9f908fde1a1 (glob)
+  |
+  @    868d2e0eb19c (4) D
+  |\     rewritten by test (*) as 0da815c333f6 (glob)
+  | |
+  | x  d9f908fde1a1 (6) F
+  | |    rewritten by test (*) as 868d2e0eb19c (glob)
+  | |
+  +---x  2a34000d3544 (1) A
+  | |      rewritten by test (*) as c473644ee0e9 (glob)
+  | |
+  x |  a8df460dbbfe (3) C
+  | |    rewritten by test (*) as 2a34000d3544, 868d2e0eb19c (glob)
+  | |
+  x |  c473644ee0e9 (2) B
+  | |    rewritten by test (*) as a8df460dbbfe (glob)
+  | |
+Check the json output is valid in this case
+
+  $ hg obslog "desc(D)" --hidden --no-graph -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "0da815c333f6"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "868d2e0eb19c",
+          "debugobshistory.rev": 4,
+          "debugobshistory.shortdescription": "D"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "868d2e0eb19c"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "d9f908fde1a1",
+          "debugobshistory.rev": 6,
+          "debugobshistory.shortdescription": "F"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "d9f908fde1a1"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "0da815c333f6",
+          "debugobshistory.rev": 5,
+          "debugobshistory.shortdescription": "E"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "2a34000d3544",
+                      "868d2e0eb19c"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "a8df460dbbfe",
+          "debugobshistory.rev": 3,
+          "debugobshistory.shortdescription": "C"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "a8df460dbbfe"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "c473644ee0e9",
+          "debugobshistory.rev": 2,
+          "debugobshistory.shortdescription": "B"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "c473644ee0e9"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "2a34000d3544",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A"
+      }
+  ]
+
new file mode 100644
--- /dev/null
+++ b/tests/test-evolve-effectflags.t
@@ -0,0 +1,218 @@
+Test the 'effect-flags' feature
+
+Global setup
+============
+
+  $ . $TESTDIR/testlib/common.sh
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > interactive = true
+  > [phases]
+  > publish=False
+  > [extensions]
+  > evolve =
+  > rebase =
+  > [experimental]
+  > evolution.effect-flags = 1
+  > EOF
+
+  $ hg init $TESTTMP/effect-flags
+  $ cd $TESTTMP/effect-flags
+  $ mkcommit ROOT
+
+amend touching the description only
+-----------------------------------
+
+  $ mkcommit A0
+  $ hg amend -m "A1"
+
+check result
+
+  $ hg debugobsolete --rev .
+  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (*) {'ef1': '1', 'user': 'test'} (glob)
+  $ hg obslog .
+  @  fdf9bde5129a (2) A1
+  |
+  x  471f378eab4c (1) A0
+       rewritten(description) by test (*) as fdf9bde5129a (glob)
+  
+
+amend touching the user only
+----------------------------
+
+  $ mkcommit B0
+  $ hg amend -u "bob <bob@bob.com>"
+
+check result
+
+  $ hg debugobsolete --rev .
+  ef4a313b1e0ade55718395d80e6b88c5ccd875eb 5485c92d34330dac9d7a63dc07e1e3373835b964 0 (*) {'ef1': '16', 'user': 'test'} (glob)
+  $ hg obslog .
+  @  5485c92d3433 (4) B0
+  |
+  x  ef4a313b1e0a (3) B0
+       rewritten(user) by test (*) as 5485c92d3433 (glob)
+  
+
+amend touching the date only
+----------------------------
+
+  $ mkcommit B1
+  $ hg amend -d "42 0"
+
+check result
+
+  $ hg debugobsolete --rev .
+  2ef0680ff45038ac28c9f1ff3644341f54487280 4dd84345082e9e5291c2e6b3f335bbf8bf389378 0 (*) {'ef1': '32', 'user': 'test'} (glob)
+  $ hg obslog .
+  @  4dd84345082e (6) B1
+  |
+  x  2ef0680ff450 (5) B1
+       rewritten(date) by test (*) as 4dd84345082e (glob)
+  
+
+amend touching the branch only
+----------------------------
+
+  $ mkcommit B2
+  $ hg branch my-branch
+  marked working directory as branch my-branch
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg amend
+
+check result
+
+  $ hg debugobsolete --rev .
+  4d1430a201c1ffbd8465dec75edd4a691a2d97ec 0 {bd3db8264ceebf1966319f5df3be7aac6acd1a8e} (*) {'ef1': '0', 'user': 'test'} (glob)
+  bd3db8264ceebf1966319f5df3be7aac6acd1a8e 14a01456e0574f0e0a0b15b2345486a6364a8d79 0 (*) {'ef1': '64', 'user': 'test'} (glob)
+  $ hg obslog .
+  @  14a01456e057 (9) B2
+  |
+  x  bd3db8264cee (7) B2
+       rewritten(branch) by test (*) as 14a01456e057 (glob)
+  
+
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+rebase (parents change)
+-----------------------
+
+  $ mkcommit C0
+  $ mkcommit D0
+  $ hg rebase -r . -d 'desc(B0)'
+  rebasing 11:c85eff83a034 "D0" (tip)
+
+check result
+
+  $ hg debugobsolete --rev .
+  c85eff83a0340efd9da52b806a94c350222f3371 da86aa2f19a30d6686b15cae15c7b6c908ec9699 0 (*) {'ef1': '4', 'user': 'test'} (glob)
+  $ hg obslog .
+  @  da86aa2f19a3 (12) D0
+  |
+  x  c85eff83a034 (11) D0
+       rewritten(parent) by test (*) as da86aa2f19a3 (glob)
+  
+
+amend touching the diff
+-----------------------
+
+  $ mkcommit E0
+  $ echo 42 >> E0
+  $ hg amend
+
+check result
+
+  $ hg debugobsolete --rev .
+  d6f4d8b8d3c8cde990f13915bced7f92ce1cc54f 0 {ebfe0333e0d96f68a917afd97c0a0af87f1c3b5f} (*) {'ef1': '0', 'user': 'test'} (glob)
+  ebfe0333e0d96f68a917afd97c0a0af87f1c3b5f 75781fdbdbf58a987516b00c980bccda1e9ae588 0 (*) {'ef1': '8', 'user': 'test'} (glob)
+  $ hg obslog .
+  @  75781fdbdbf5 (15) E0
+  |
+  x  ebfe0333e0d9 (13) E0
+       rewritten(content) by test (*) as 75781fdbdbf5 (glob)
+  
+
+amend with multiple effect (desc and meta)
+-------------------------------------------
+
+  $ mkcommit F0
+  $ hg branch my-other-branch
+  marked working directory as branch my-other-branch
+  $ hg amend -m F1 -u "bob <bob@bob.com>" -d "42 0"
+
+check result
+
+  $ hg debugobsolete --rev .
+  3b12912003b4e7aa6df6cded86255006c3c29d27 0 {fad47e5bd78e6aa4db1b5a0a1751bc12563655ff} (*) {'ef1': '0', 'user': 'test'} (glob)
+  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (*) {'ef1': '113', 'user': 'test'} (glob)
+  $ hg obslog .
+  @  a94e0fd5f1c8 (18) F1
+  |
+  x  fad47e5bd78e (16) F0
+       rewritten(description, user, date, branch) by test (*) as a94e0fd5f1c8 (glob)
+  
+rebase not touching the diff
+----------------------------
+
+  $ cat << EOF > H0
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > EOF
+  $ hg add H0
+  $ hg commit -m 'H0'
+  $ echo "H1" >> H0
+  $ hg commit -m "H1"
+  $ hg up -r "desc(H0)"
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF > H0
+  > H2
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > EOF
+  $ hg commit -m "H2"
+  created new head
+  $ hg rebase -s "desc(H1)" -d "desc(H2)" -t :merge3
+  rebasing 20:b57fed8d8322 "H1"
+  merging H0
+  $ hg obslog tip
+  o  e509e2eb3df5 (22) H1
+  |
+  x  b57fed8d8322 (20) H1
+       rewritten(parent) by test (*) as e509e2eb3df5 (glob)
+  
+amend closing the branch should be detected as meta change
+----------------------------------------------------------
+
+  $ hg branch closedbranch
+  marked working directory as branch closedbranch
+  $ mkcommit G0
+  $ mkcommit I0
+  $ hg commit --amend --close-branch
+
+check result
+
+  $ hg obslog .
+  @  12c6238b5e37 (26) I0
+  |
+  x  2f599e54c1c6 (24) I0
+       rewritten(meta) by test (*) as 12c6238b5e37 (glob)
+  
new file mode 100644
--- /dev/null
+++ b/tests/test-evolve-obshistory-complex.t
@@ -0,0 +1,428 @@
+Global setup
+============
+
+  $ . $TESTDIR/testlib/common.sh
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > interactive = true
+  > [phases]
+  > publish=False
+  > [extensions]
+  > evolve =
+  > EOF
+
+Test obslog with split + fold + split
+=====================================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/splitfoldsplit
+  $ cd $TESTTMP/splitfoldsplit
+  $ mkcommit ROOT
+  $ mkcommit A
+  $ mkcommit B
+  $ mkcommit C
+  $ mkcommit D
+  $ mkcommit E
+  $ mkcommit F
+  $ hg log -G
+  @  changeset:   6:d9f908fde1a1
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     F
+  |
+  o  changeset:   5:0da815c333f6
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     E
+  |
+  o  changeset:   4:868d2e0eb19c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     D
+  |
+  o  changeset:   3:a8df460dbbfe
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C
+  |
+  o  changeset:   2:c473644ee0e9
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B
+  |
+  o  changeset:   1:2a34000d3544
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Split commits two by two
+------------------------
+
+  $ hg fold --exact -r 1 -r 2 --date "0 0" -m "fold0"
+  2 changesets folded
+  4 new unstable changesets
+  $ hg fold --exact -r 3 -r 4 --date "0 0" -m "fold1"
+  2 changesets folded
+  $ hg fold --exact -r 5 -r 6 --date "0 0" -m "fold2"
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log -G 
+  @  changeset:   9:100cc25b765f
+  |  tag:         tip
+  |  parent:      4:868d2e0eb19c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  trouble:     unstable
+  |  summary:     fold2
+  |
+  | o  changeset:   8:d15d0ffc75f6
+  | |  parent:      2:c473644ee0e9
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  trouble:     unstable
+  | |  summary:     fold1
+  | |
+  | | o  changeset:   7:b868bc49b0a4
+  | | |  parent:      0:ea207398892e
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     fold0
+  | | |
+  x | |  changeset:   4:868d2e0eb19c
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     D
+  | | |
+  x | |  changeset:   3:a8df460dbbfe
+  |/ /   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     C
+  | |
+  x |  changeset:   2:c473644ee0e9
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     B
+  | |
+  x |  changeset:   1:2a34000d3544
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
+Then split
+----------
+
+  $ hg split "desc(fold0)" -d "0 0" << EOF
+  > Y
+  > Y
+  > N
+  > N
+  > Y
+  > Y
+  > EOF
+  0 files updated, 0 files merged, 6 files removed, 0 files unresolved
+  adding A
+  adding B
+  diff --git a/A b/A
+  new file mode 100644
+  examine changes to 'A'? [Ynesfdaq?] Y
+  
+  @@ -0,0 +1,1 @@
+  +A
+  record change 1/2 to 'A'? [Ynesfdaq?] Y
+  
+  diff --git a/B b/B
+  new file mode 100644
+  examine changes to 'B'? [Ynesfdaq?] N
+  
+  created new head
+  Done splitting? [yN] N
+  diff --git a/B b/B
+  new file mode 100644
+  examine changes to 'B'? [Ynesfdaq?] Y
+  
+  @@ -0,0 +1,1 @@
+  +B
+  record this change to 'B'? [Ynesfdaq?] Y
+  
+  no more change to split
+  $ hg split "desc(fold1)" -d "0 0" << EOF
+  > Y
+  > Y
+  > N
+  > N
+  > Y
+  > Y
+  > EOF
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  adding C
+  adding D
+  diff --git a/C b/C
+  new file mode 100644
+  examine changes to 'C'? [Ynesfdaq?] Y
+  
+  @@ -0,0 +1,1 @@
+  +C
+  record change 1/2 to 'C'? [Ynesfdaq?] Y
+  
+  diff --git a/D b/D
+  new file mode 100644
+  examine changes to 'D'? [Ynesfdaq?] N
+  
+  created new head
+  Done splitting? [yN] N
+  diff --git a/D b/D
+  new file mode 100644
+  examine changes to 'D'? [Ynesfdaq?] Y
+  
+  @@ -0,0 +1,1 @@
+  +D
+  record this change to 'D'? [Ynesfdaq?] Y
+  
+  no more change to split
+  $ hg split "desc(fold2)" -d "0 0" << EOF
+  > Y
+  > Y
+  > N
+  > N
+  > Y
+  > Y
+  > EOF
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  adding E
+  adding F
+  diff --git a/E b/E
+  new file mode 100644
+  examine changes to 'E'? [Ynesfdaq?] Y
+  
+  @@ -0,0 +1,1 @@
+  +E
+  record change 1/2 to 'E'? [Ynesfdaq?] Y
+  
+  diff --git a/F b/F
+  new file mode 100644
+  examine changes to 'F'? [Ynesfdaq?] N
+  
+  created new head
+  Done splitting? [yN] N
+  diff --git a/F b/F
+  new file mode 100644
+  examine changes to 'F'? [Ynesfdaq?] Y
+  
+  @@ -0,0 +1,1 @@
+  +F
+  record this change to 'F'? [Ynesfdaq?] Y
+  
+  no more change to split
+  $ hg log -G
+  @  changeset:   15:d4a000f63ee9
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  trouble:     unstable
+  |  summary:     fold2
+  |
+  o  changeset:   14:ec31316faa9d
+  |  parent:      4:868d2e0eb19c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  trouble:     unstable
+  |  summary:     fold2
+  |
+  | o  changeset:   13:d0f33db50670
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  trouble:     unstable
+  | |  summary:     fold1
+  | |
+  | o  changeset:   12:7b3290f6e0a0
+  | |  parent:      2:c473644ee0e9
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  trouble:     unstable
+  | |  summary:     fold1
+  | |
+  | | o  changeset:   11:e036916b63ea
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     fold0
+  | | |
+  | | o  changeset:   10:19e14c8397fc
+  | | |  parent:      0:ea207398892e
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     fold0
+  | | |
+  x | |  changeset:   4:868d2e0eb19c
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     D
+  | | |
+  x | |  changeset:   3:a8df460dbbfe
+  |/ /   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     C
+  | |
+  x |  changeset:   2:c473644ee0e9
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     B
+  | |
+  x |  changeset:   1:2a34000d3544
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
+Connect them all
+----------------
+
+  $ hg prune -s 12 -r 11
+  1 changesets pruned
+  $ hg prune -s 14 -r 13
+  1 changesets pruned
+  $ hg log -G
+  @  changeset:   15:d4a000f63ee9
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  trouble:     unstable
+  |  summary:     fold2
+  |
+  o  changeset:   14:ec31316faa9d
+  |  parent:      4:868d2e0eb19c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  trouble:     unstable
+  |  summary:     fold2
+  |
+  | o  changeset:   12:7b3290f6e0a0
+  | |  parent:      2:c473644ee0e9
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  trouble:     unstable
+  | |  summary:     fold1
+  | |
+  | | o  changeset:   10:19e14c8397fc
+  | | |  parent:      0:ea207398892e
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     fold0
+  | | |
+  x | |  changeset:   4:868d2e0eb19c
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     D
+  | | |
+  x | |  changeset:   3:a8df460dbbfe
+  |/ /   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     C
+  | |
+  x |  changeset:   2:c473644ee0e9
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     B
+  | |
+  x |  changeset:   1:2a34000d3544
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Actual Test
+===========
+
+Obslog should show a subset of the obs history, this test check that the
+walking algorithm works no matter the level of successors + precursors
+
+  $ hg obslog 12
+  o    7b3290f6e0a0 (12) fold1
+  |\
+  x |    d15d0ffc75f6 (8) fold1
+  |\ \     rewritten by test (*) as 7b3290f6e0a0, d0f33db50670 (glob)
+  | | |
+  | | x  e036916b63ea (11) fold0
+  | | |    rewritten by test (*) as 7b3290f6e0a0 (glob)
+  | | |
+  x | |  868d2e0eb19c (4) D
+   / /     rewritten by test (*) as d15d0ffc75f6 (glob)
+  | |
+  x |  a8df460dbbfe (3) C
+   /     rewritten by test (*) as d15d0ffc75f6 (glob)
+  |
+  x    b868bc49b0a4 (7) fold0
+  |\     rewritten by test (*) as 19e14c8397fc, e036916b63ea (glob)
+  | |
+  x |  2a34000d3544 (1) A
+   /     rewritten by test (*) as b868bc49b0a4 (glob)
+  |
+  x  c473644ee0e9 (2) B
+       rewritten by test (*) as b868bc49b0a4 (glob)
+  
+While with all option, we should see 15 changesets
+
+  $ hg obslog --all 15
+  o  19e14c8397fc (10) fold0
+  |
+  | o    7b3290f6e0a0 (12) fold1
+  | |\
+  | | | @  d4a000f63ee9 (15) fold2
+  | | | |
+  | | | | o  ec31316faa9d (14) fold2
+  | | | |/|
+  | | | x |    100cc25b765f (9) fold2
+  | | | |\ \     rewritten by test (*) as d4a000f63ee9, ec31316faa9d (glob)
+  | | | | | |
+  | +-------x  d0f33db50670 (13) fold1
+  | | | | |      rewritten by test (*) as ec31316faa9d (glob)
+  | | | | |
+  +---x | |  e036916b63ea (11) fold0
+  | |  / /     rewritten by test (*) as 7b3290f6e0a0 (glob)
+  | | | |
+  | | x |  0da815c333f6 (5) E
+  | |  /     rewritten by test (*) as 100cc25b765f (glob)
+  | | |
+  x | |    b868bc49b0a4 (7) fold0
+  |\ \ \     rewritten by test (*) as 19e14c8397fc, e036916b63ea (glob)
+  | | | |
+  | | x |    d15d0ffc75f6 (8) fold1
+  | | |\ \     rewritten by test (*) as 7b3290f6e0a0, d0f33db50670 (glob)
+  | | | | |
+  | | | | x  d9f908fde1a1 (6) F
+  | | | |      rewritten by test (*) as 100cc25b765f (glob)
+  | | | |
+  x | | |  2a34000d3544 (1) A
+   / / /     rewritten by test (*) as b868bc49b0a4 (glob)
+  | | |
+  | x |  868d2e0eb19c (4) D
+  |  /     rewritten by test (*) as d15d0ffc75f6 (glob)
+  | |
+  | x  a8df460dbbfe (3) C
+  |      rewritten by test (*) as d15d0ffc75f6 (glob)
+  |
+  x  c473644ee0e9 (2) B
+       rewritten by test (*) as b868bc49b0a4 (glob)
+  
--- a/tests/test-evolve-obshistory.t
+++ b/tests/test-evolve-obshistory.t
@@ -53,13 +53,13 @@
   
 Actual test
 -----------
-  $ hg olog 4ae3a4151de9
+  $ hg obslog 4ae3a4151de9
   @  4ae3a4151de9 (3) A1
   |
   x  471f378eab4c (1) A0
        rewritten by test (*) as 4ae3a4151de9 (glob)
   
-  $ hg olog 4ae3a4151de9 --no-graph -Tjson | python -m json.tool
+  $ hg obslog 4ae3a4151de9 --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [],
@@ -86,11 +86,11 @@
           "debugobshistory.shortdescription": "A0"
       }
   ]
-  $ hg olog --hidden 471f378eab4c
+  $ hg obslog --hidden 471f378eab4c
   x  471f378eab4c (1) A0
        rewritten by test (*) as 4ae3a4151de9 (glob)
   
-  $ hg olog --hidden 471f378eab4c --no-graph -Tjson | python -m json.tool
+  $ hg obslog --hidden 471f378eab4c --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [
@@ -173,11 +173,11 @@
 Actual test
 -----------
 
-  $ hg olog 'desc(B0)' --hidden
+  $ hg obslog 'desc(B0)' --hidden
   x  0dec01379d3b (2) B0
        pruned by test (*) (glob)
   
-  $ hg olog 'desc(B0)' --hidden --no-graph -Tjson | python -m json.tool
+  $ hg obslog 'desc(B0)' --hidden --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [
@@ -195,10 +195,10 @@
           "debugobshistory.shortdescription": "B0"
       }
   ]
-  $ hg olog 'desc(A0)'
+  $ hg obslog 'desc(A0)'
   @  471f378eab4c (1) A0
   
-  $ hg olog 'desc(A0)' --no-graph -Tjson | python -m json.tool
+  $ hg obslog 'desc(A0)' --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [],
@@ -306,11 +306,11 @@
 -----------
 
 Check that debugobshistory on splitted commit show both targets
-  $ hg olog 471597cad322 --hidden
+  $ hg obslog 471597cad322 --hidden
   x  471597cad322 (1) A0
        rewritten by test (*) as 337fec4d2edc, f257fde29c7a (glob)
   
-  $ hg olog 471597cad322 --hidden --no-graph -Tjson | python -m json.tool
+  $ hg obslog 471597cad322 --hidden --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [
@@ -334,23 +334,50 @@
   ]
 Check that debugobshistory on the first successor after split show
 the revision plus the splitted one
-  $ hg olog 337fec4d2edc
+  $ hg obslog 337fec4d2edc
   o  337fec4d2edc (2) A0
   |
   x  471597cad322 (1) A0
        rewritten by test (*) as 337fec4d2edc, f257fde29c7a (glob)
   
+With the all option, it should show the three changesets
+  $ hg obslog --all 337fec4d2edc
+  o  337fec4d2edc (2) A0
+  |
+  | @  f257fde29c7a (3) A0
+  |/
+  x  471597cad322 (1) A0
+       rewritten by test (*) as 337fec4d2edc, f257fde29c7a (glob)
+  
 Check that debugobshistory on the second successor after split show
 the revision plus the splitted one
-  $ hg olog f257fde29c7a
+  $ hg obslog f257fde29c7a
   @  f257fde29c7a (3) A0
   |
   x  471597cad322 (1) A0
        rewritten by test (*) as 337fec4d2edc, f257fde29c7a (glob)
   
+With the all option, it should show the three changesets
+  $ hg obslog f257fde29c7a --all
+  o  337fec4d2edc (2) A0
+  |
+  | @  f257fde29c7a (3) A0
+  |/
+  x  471597cad322 (1) A0
+       rewritten by test (*) as 337fec4d2edc, f257fde29c7a (glob)
+  
+Obslog with all option all should also works on the splitted commit
+  $ hg obslog -a 471597cad322 --hidden
+  o  337fec4d2edc (2) A0
+  |
+  | @  f257fde29c7a (3) A0
+  |/
+  x  471597cad322 (1) A0
+       rewritten by test (*) as 337fec4d2edc, f257fde29c7a (glob)
+  
 Check that debugobshistory on both successors after split show
 a coherent graph
-  $ hg olog 'f257fde29c7a+337fec4d2edc'
+  $ hg obslog 'f257fde29c7a+337fec4d2edc'
   o  337fec4d2edc (2) A0
   |
   | @  f257fde29c7a (3) A0
@@ -520,11 +547,23 @@
 Actual test
 -----------
 
-  $ hg olog de7290d8b885 --hidden
+  $ hg obslog de7290d8b885 --hidden
   x  de7290d8b885 (1) A0
        rewritten by test (*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
   
-  $ hg olog de7290d8b885 --hidden --no-graph -Tjson | python -m json.tool
+  $ hg obslog de7290d8b885 --hidden --all
+  o  1ae8bc733a14 (4) A0
+  |
+  | o  337fec4d2edc (2) A0
+  |/
+  | @  c7f044602e9b (5) A0
+  |/
+  | o  f257fde29c7a (3) A0
+  |/
+  x  de7290d8b885 (1) A0
+       rewritten by test (*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
+  
+  $ hg obslog de7290d8b885 --hidden --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [
@@ -548,13 +587,13 @@
           "debugobshistory.shortdescription": "A0"
       }
   ]
-  $ hg olog c7f044602e9b
+  $ hg obslog c7f044602e9b
   @  c7f044602e9b (5) A0
   |
   x  de7290d8b885 (1) A0
        rewritten by test (*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
   
-  $ hg olog c7f044602e9b --no-graph -Tjson | python -m json.tool
+  $ hg obslog c7f044602e9b --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [],
@@ -585,7 +624,19 @@
       }
   ]
 Check that debugobshistory on all heads show a coherent graph
-  $ hg olog 2::5
+  $ hg obslog 2::5
+  o  1ae8bc733a14 (4) A0
+  |
+  | o  337fec4d2edc (2) A0
+  |/
+  | @  c7f044602e9b (5) A0
+  |/
+  | o  f257fde29c7a (3) A0
+  |/
+  x  de7290d8b885 (1) A0
+       rewritten by test (*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
+  
+  $ hg obslog 5 --all
   o  1ae8bc733a14 (4) A0
   |
   | o  337fec4d2edc (2) A0
@@ -665,19 +716,12 @@
 
 Check that debugobshistory on the first folded revision show only
 the revision with the target
-  $ hg olog --hidden 471f378eab4c
+  $ hg obslog --hidden 471f378eab4c
   x  471f378eab4c (1) A0
        rewritten by test (*) as eb5a0daa2192 (glob)
   
-Check that debugobshistory on the second folded revision show only
-the revision with the target
-  $ hg olog --hidden 0dec01379d3b
-  x  0dec01379d3b (2) B0
-       rewritten by test (*) as eb5a0daa2192 (glob)
-  
-Check that debugobshistory on the successor revision show a coherent
-graph
-  $ hg olog eb5a0daa2192
+Check that with all option, all changesets are shown
+  $ hg obslog --hidden --all 471f378eab4c
   @    eb5a0daa2192 (3) C0
   |\
   x |  0dec01379d3b (2) B0
@@ -686,7 +730,34 @@
   x  471f378eab4c (1) A0
        rewritten by test (*) as eb5a0daa2192 (glob)
   
-  $ hg olog eb5a0daa2192 --no-graph -Tjson | python -m json.tool
+Check that debugobshistory on the second folded revision show only
+the revision with the target
+  $ hg obslog --hidden 0dec01379d3b
+  x  0dec01379d3b (2) B0
+       rewritten by test (*) as eb5a0daa2192 (glob)
+  
+Check that with all option, all changesets are shown
+  $ hg obslog --hidden --all 0dec01379d3b
+  @    eb5a0daa2192 (3) C0
+  |\
+  x |  0dec01379d3b (2) B0
+   /     rewritten by test (*) as eb5a0daa2192 (glob)
+  |
+  x  471f378eab4c (1) A0
+       rewritten by test (*) as eb5a0daa2192 (glob)
+  
+Check that debugobshistory on the successor revision show a coherent
+graph
+  $ hg obslog eb5a0daa2192
+  @    eb5a0daa2192 (3) C0
+  |\
+  x |  0dec01379d3b (2) B0
+   /     rewritten by test (*) as eb5a0daa2192 (glob)
+  |
+  x  471f378eab4c (1) A0
+       rewritten by test (*) as eb5a0daa2192 (glob)
+  
+  $ hg obslog eb5a0daa2192 --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [],
@@ -815,12 +886,23 @@
 -----------
 
 Check that debugobshistory on the divergent revision show both destinations
-  $ hg olog --hidden 471f378eab4c
+  $ hg obslog --hidden 471f378eab4c
   x  471f378eab4c (1) A0
        rewritten by test (*) as 65b757b745b9 (glob)
        rewritten by test (*) as fdf9bde5129a (glob)
   
-  $ hg olog --hidden 471f378eab4c --no-graph -Tjson | python -m json.tool
+
+Check that with all option, every changeset is shown
+  $ hg obslog --hidden --all 471f378eab4c
+  @  65b757b745b9 (3) A2
+  |
+  | o  fdf9bde5129a (2) A1
+  |/
+  x  471f378eab4c (1) A0
+       rewritten by test (*) as 65b757b745b9 (glob)
+       rewritten by test (*) as fdf9bde5129a (glob)
+  
+  $ hg obslog --hidden 471f378eab4c --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [
@@ -854,25 +936,16 @@
   ]
 Check that debugobshistory on the first diverged revision show the revision
 and the diverent one
-  $ hg olog fdf9bde5129a
+  $ hg obslog fdf9bde5129a
   o  fdf9bde5129a (2) A1
   |
   x  471f378eab4c (1) A0
        rewritten by test (*) as 65b757b745b9 (glob)
        rewritten by test (*) as fdf9bde5129a (glob)
   
-Check that debugobshistory on the second diverged revision show the revision
-and the diverent one
-  $ hg olog 65b757b745b9
-  @  65b757b745b9 (3) A2
-  |
-  x  471f378eab4c (1) A0
-       rewritten by test (*) as 65b757b745b9 (glob)
-       rewritten by test (*) as fdf9bde5129a (glob)
-  
-Check that debugobshistory on the both diverged revision show a coherent
-graph
-  $ hg olog '65b757b745b9+fdf9bde5129a'
+
+Check that all option show all of them
+  $ hg obslog fdf9bde5129a -a
   @  65b757b745b9 (3) A2
   |
   | o  fdf9bde5129a (2) A1
@@ -881,7 +954,37 @@
        rewritten by test (*) as 65b757b745b9 (glob)
        rewritten by test (*) as fdf9bde5129a (glob)
   
-  $ hg olog '65b757b745b9+fdf9bde5129a' --no-graph -Tjson | python -m json.tool
+Check that debugobshistory on the second diverged revision show the revision
+and the diverent one
+  $ hg obslog 65b757b745b9
+  @  65b757b745b9 (3) A2
+  |
+  x  471f378eab4c (1) A0
+       rewritten by test (*) as 65b757b745b9 (glob)
+       rewritten by test (*) as fdf9bde5129a (glob)
+  
+Check that all option show all of them
+  $ hg obslog 65b757b745b9 -a
+  @  65b757b745b9 (3) A2
+  |
+  | o  fdf9bde5129a (2) A1
+  |/
+  x  471f378eab4c (1) A0
+       rewritten by test (*) as 65b757b745b9 (glob)
+       rewritten by test (*) as fdf9bde5129a (glob)
+  
+Check that debugobshistory on the both diverged revision show a coherent
+graph
+  $ hg obslog '65b757b745b9+fdf9bde5129a'
+  @  65b757b745b9 (3) A2
+  |
+  | o  fdf9bde5129a (2) A1
+  |/
+  x  471f378eab4c (1) A0
+       rewritten by test (*) as 65b757b745b9 (glob)
+       rewritten by test (*) as fdf9bde5129a (glob)
+  
+  $ hg obslog '65b757b745b9+fdf9bde5129a' --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [],
@@ -1005,7 +1108,7 @@
  -----------
 
 Check that debugobshistory on head show a coherent graph
-  $ hg olog eb5a0daa2192
+  $ hg obslog eb5a0daa2192
   @    eb5a0daa2192 (4) C0
   |\
   x |  471f378eab4c (1) A0
@@ -1017,7 +1120,20 @@
   x  0dec01379d3b (2) B0
        rewritten by test (*) as b7ea6d14e664 (glob)
   
-  $ hg olog eb5a0daa2192 --no-graph -Tjson | python -m json.tool
+Check that obslog on ROOT with all option show everything
+  $ hg obslog 1 --hidden --all
+  @    eb5a0daa2192 (4) C0
+  |\
+  x |  471f378eab4c (1) A0
+   /     rewritten by test (*) as eb5a0daa2192 (glob)
+  |
+  x  b7ea6d14e664 (3) B1
+  |    rewritten by test (*) as eb5a0daa2192 (glob)
+  |
+  x  0dec01379d3b (2) B0
+       rewritten by test (*) as b7ea6d14e664 (glob)
+  
+  $ hg obslog eb5a0daa2192 --no-graph -Tjson | python -m json.tool
   [
       {
           "debugobshistory.markers": [],
@@ -1169,7 +1285,7 @@
  Actual test
  -----------
 
-  $ hg olog 7a230b46bf61
+  $ hg obslog 7a230b46bf61
   @  7a230b46bf61 (3) A2
   |
   x  fdf9bde5129a (2) A1
@@ -1192,7 +1308,7 @@
   (use 'hg evolve' to update to its successor: 7a230b46bf61)
 Check that debugobshistory works with markers pointing to missing local
 changectx
-  $ hg olog 7a230b46bf61
+  $ hg obslog 7a230b46bf61
   o  7a230b46bf61 (2) A2
   |
   x  fdf9bde5129a
@@ -1201,7 +1317,7 @@
   @  471f378eab4c (1) A0
        rewritten by test (*) as fdf9bde5129a (glob)
   
-  $ hg olog 7a230b46bf61 --color=debug
+  $ hg obslog 7a230b46bf61 --color=debug
   o  [evolve.node|7a230b46bf61] [evolve.rev|(2)] [evolve.short_description|A2]
   |
   x  [evolve.node evolve.missing_change_ctx|fdf9bde5129a]
@@ -1210,353 +1326,3 @@
   @  [evolve.node|471f378eab4c] [evolve.rev|(1)] [evolve.short_description|A0]
        [evolve.verb|rewritten] by [evolve.user|test] [evolve.date|(*)] as [evolve.node|fdf9bde5129a] (glob)
   
-
-Test with cycle
-===============
-
-Test setup
-----------
-
-  $ hg init $TESTTMP/cycle
-  $ cd $TESTTMP/cycle
-  $ mkcommit ROOT
-  $ mkcommit A
-  $ mkcommit B
-  $ mkcommit C
-  $ hg log -G
-  @  changeset:   3:a8df460dbbfe
-  |  tag:         tip
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     C
-  |
-  o  changeset:   2:c473644ee0e9
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     B
-  |
-  o  changeset:   1:2a34000d3544
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     A
-  |
-  o  changeset:   0:ea207398892e
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     ROOT
-  
-Create a cycle
-  $ hg prune -s "desc(B)" "desc(A)"
-  1 changesets pruned
-  2 new unstable changesets
-  $ hg prune -s "desc(C)" "desc(B)"
-  1 changesets pruned
-  $ hg prune -s "desc(A)" "desc(C)"
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  working directory now at 2a34000d3544
-  1 changesets pruned
-  $ hg log --hidden -G
-  x  changeset:   3:a8df460dbbfe
-  |  tag:         tip
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     C
-  |
-  x  changeset:   2:c473644ee0e9
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     B
-  |
-  @  changeset:   1:2a34000d3544
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     A
-  |
-  o  changeset:   0:ea207398892e
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     ROOT
-  
-Actual test
------------
-
-Check that debugobshistory never crash on a cycle
-
-  $ hg olog "desc(A)" --hidden
-  @  2a34000d3544 (1) A
-  |    rewritten by test (*) as c473644ee0e9 (glob)
-  |
-  x  a8df460dbbfe (3) C
-  |    rewritten by test (*) as 2a34000d3544 (glob)
-  |
-  x  c473644ee0e9 (2) B
-  |    rewritten by test (*) as a8df460dbbfe (glob)
-  |
-
-  $ hg olog "desc(B)" --hidden
-  @  2a34000d3544 (1) A
-  |    rewritten by test (*) as c473644ee0e9 (glob)
-  |
-  x  a8df460dbbfe (3) C
-  |    rewritten by test (*) as 2a34000d3544 (glob)
-  |
-  x  c473644ee0e9 (2) B
-  |    rewritten by test (*) as a8df460dbbfe (glob)
-  |
-
-  $ hg olog "desc(C)" --hidden
-  @  2a34000d3544 (1) A
-  |    rewritten by test (*) as c473644ee0e9 (glob)
-  |
-  x  a8df460dbbfe (3) C
-  |    rewritten by test (*) as 2a34000d3544 (glob)
-  |
-  x  c473644ee0e9 (2) B
-  |    rewritten by test (*) as a8df460dbbfe (glob)
-  |
-
-Test with multiple cyles
-========================
-
-Test setup
-----------
-
-  $ hg init $TESTTMP/multiple-cycle
-  $ cd $TESTTMP/multiple-cycle
-  $ mkcommit ROOT
-  $ mkcommit A
-  $ mkcommit B
-  $ mkcommit C
-  $ mkcommit D
-  $ mkcommit E
-  $ mkcommit F
-  $ hg log -G
-  @  changeset:   6:d9f908fde1a1
-  |  tag:         tip
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     F
-  |
-  o  changeset:   5:0da815c333f6
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     E
-  |
-  o  changeset:   4:868d2e0eb19c
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     D
-  |
-  o  changeset:   3:a8df460dbbfe
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     C
-  |
-  o  changeset:   2:c473644ee0e9
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     B
-  |
-  o  changeset:   1:2a34000d3544
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     A
-  |
-  o  changeset:   0:ea207398892e
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     ROOT
-  
-Create a first cycle
-  $ hg prune -s "desc(B)" "desc(A)"
-  1 changesets pruned
-  5 new unstable changesets
-  $ hg prune -s "desc(C)" "desc(B)"
-  1 changesets pruned
-  $ hg prune --split -s "desc(A)" -s "desc(D)" "desc(C)"
-  1 changesets pruned
-And create a second one
-  $ hg prune -s "desc(E)" "desc(D)"
-  1 changesets pruned
-  $ hg prune -s "desc(F)" "desc(E)"
-  1 changesets pruned
-  $ hg prune -s "desc(D)" "desc(F)"
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  working directory now at 868d2e0eb19c
-  1 changesets pruned
-  $ hg log --hidden -G
-  x  changeset:   6:d9f908fde1a1
-  |  tag:         tip
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     F
-  |
-  x  changeset:   5:0da815c333f6
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     E
-  |
-  @  changeset:   4:868d2e0eb19c
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     D
-  |
-  x  changeset:   3:a8df460dbbfe
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     C
-  |
-  x  changeset:   2:c473644ee0e9
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     B
-  |
-  x  changeset:   1:2a34000d3544
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     A
-  |
-  o  changeset:   0:ea207398892e
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     ROOT
-  
-Actual test
------------
-
-Check that debugobshistory never crash on a cycle
-
-  $ hg olog "desc(D)" --hidden
-  x  0da815c333f6 (5) E
-  |    rewritten by test (*) as d9f908fde1a1 (glob)
-  |
-  @    868d2e0eb19c (4) D
-  |\     rewritten by test (*) as 0da815c333f6 (glob)
-  | |
-  | x  d9f908fde1a1 (6) F
-  | |    rewritten by test (*) as 868d2e0eb19c (glob)
-  | |
-  +---x  2a34000d3544 (1) A
-  | |      rewritten by test (*) as c473644ee0e9 (glob)
-  | |
-  x |  a8df460dbbfe (3) C
-  | |    rewritten by test (*) as 2a34000d3544, 868d2e0eb19c (glob)
-  | |
-  x |  c473644ee0e9 (2) B
-  | |    rewritten by test (*) as a8df460dbbfe (glob)
-  | |
-
-Check the json output is valid in this case
-
-  $ hg olog "desc(D)" --hidden --no-graph -Tjson | python -m json.tool
-  [
-      {
-          "debugobshistory.markers": [
-              {
-                  "debugobshistory.marker_date": [
-                      *, (glob)
-                      0
-                  ],
-                  "debugobshistory.marker_user": "test",
-                  "debugobshistory.succnodes": [
-                      "0da815c333f6"
-                  ],
-                  "debugobshistory.verb": "rewritten"
-              }
-          ],
-          "debugobshistory.node": "868d2e0eb19c",
-          "debugobshistory.rev": 4,
-          "debugobshistory.shortdescription": "D"
-      },
-      {
-          "debugobshistory.markers": [
-              {
-                  "debugobshistory.marker_date": [
-                      *, (glob)
-                      0
-                  ],
-                  "debugobshistory.marker_user": "test",
-                  "debugobshistory.succnodes": [
-                      "868d2e0eb19c"
-                  ],
-                  "debugobshistory.verb": "rewritten"
-              }
-          ],
-          "debugobshistory.node": "d9f908fde1a1",
-          "debugobshistory.rev": 6,
-          "debugobshistory.shortdescription": "F"
-      },
-      {
-          "debugobshistory.markers": [
-              {
-                  "debugobshistory.marker_date": [
-                      *, (glob)
-                      0
-                  ],
-                  "debugobshistory.marker_user": "test",
-                  "debugobshistory.succnodes": [
-                      "d9f908fde1a1"
-                  ],
-                  "debugobshistory.verb": "rewritten"
-              }
-          ],
-          "debugobshistory.node": "0da815c333f6",
-          "debugobshistory.rev": 5,
-          "debugobshistory.shortdescription": "E"
-      },
-      {
-          "debugobshistory.markers": [
-              {
-                  "debugobshistory.marker_date": [
-                      *, (glob)
-                      0
-                  ],
-                  "debugobshistory.marker_user": "test",
-                  "debugobshistory.succnodes": [
-                      "2a34000d3544",
-                      "868d2e0eb19c"
-                  ],
-                  "debugobshistory.verb": "rewritten"
-              }
-          ],
-          "debugobshistory.node": "a8df460dbbfe",
-          "debugobshistory.rev": 3,
-          "debugobshistory.shortdescription": "C"
-      },
-      {
-          "debugobshistory.markers": [
-              {
-                  "debugobshistory.marker_date": [
-                      *, (glob)
-                      0
-                  ],
-                  "debugobshistory.marker_user": "test",
-                  "debugobshistory.succnodes": [
-                      "a8df460dbbfe"
-                  ],
-                  "debugobshistory.verb": "rewritten"
-              }
-          ],
-          "debugobshistory.node": "c473644ee0e9",
-          "debugobshistory.rev": 2,
-          "debugobshistory.shortdescription": "B"
-      },
-      {
-          "debugobshistory.markers": [
-              {
-                  "debugobshistory.marker_date": [
-                      *, (glob)
-                      0
-                  ],
-                  "debugobshistory.marker_user": "test",
-                  "debugobshistory.succnodes": [
-                      "c473644ee0e9"
-                  ],
-                  "debugobshistory.verb": "rewritten"
-              }
-          ],
-          "debugobshistory.node": "2a34000d3544",
-          "debugobshistory.rev": 1,
-          "debugobshistory.shortdescription": "A"
-      }
-  ]
new file mode 100644
--- /dev/null
+++ b/tests/test-evolve-templates.t
@@ -0,0 +1,775 @@
+This test file test the various templates for precursors and successors.
+
+Global setup
+============
+
+  $ . $TESTDIR/testlib/common.sh
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > interactive = true
+  > [phases]
+  > publish=False
+  > [extensions]
+  > evolve =
+  > [alias]
+  > tlog = log -G -T '{node|short}\
+  >     {if(precursors, "\n  Precursors: {precursors}")}\
+  >     {if(precursors, "\n  semi-colon: {join(precursors, "; ")}")}\
+  >     {if(successors, "\n  Successors: {successors}")}\
+  >     {if(successors, "\n  semi-colon: {join(successors, "; ")}")}\
+  >     {if(successors, "\n  Fate: {obsfate_quiet}")}\n'
+  > EOF
+
+Test templates on amended commit
+================================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/templates-local-amend
+  $ cd $TESTTMP/templates-local-amend
+  $ mkcommit ROOT
+  $ mkcommit A0
+  $ echo 42 >> A0
+  $ hg amend -m "A1"
+  $ hg amend -m "A2"
+  $ hg log --hidden -G
+  @  changeset:   4:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | x  changeset:   3:a468dc9b3633
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A1
+  |
+  | x  changeset:   2:f137d23bb3e1
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     temporary amend commit for 471f378eab4c
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Check templates
+---------------
+  $ hg up 'desc(A0)' --hidden
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (471f378eab4c)
+  (use 'hg evolve' to update to its successor: d004c8f274b9)
+
+Precursors template should show current revision as it is the working copy
+  $ hg tlog
+  o  d004c8f274b9
+  |    Precursors: 471f378eab4c
+  |    semi-colon: 471f378eab4c
+  | @  471f378eab4c
+  |/     Successors: [d004c8f274b9]
+  |      semi-colon: [d004c8f274b9]
+  |      Fate: superseed as d004c8f274b9
+  o  ea207398892e
+  
+  $ hg up 'desc(A1)' --hidden
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (a468dc9b3633)
+  (use 'hg evolve' to update to its successor: d004c8f274b9)
+
+Precursors template should show current revision as it is the working copy
+  $ hg tlog
+  o  d004c8f274b9
+  |    Precursors: a468dc9b3633
+  |    semi-colon: a468dc9b3633
+  | @  a468dc9b3633
+  |/     Successors: [d004c8f274b9]
+  |      semi-colon: [d004c8f274b9]
+  |      Fate: superseed as d004c8f274b9
+  o  ea207398892e
+  
+Precursors template should show the precursor as we force its display with
+--hidden  
+  $ hg tlog --hidden
+  o  d004c8f274b9
+  |    Precursors: a468dc9b3633
+  |    semi-colon: a468dc9b3633
+  | @  a468dc9b3633
+  |/     Precursors: 471f378eab4c
+  |      semi-colon: 471f378eab4c
+  |      Successors: [d004c8f274b9]
+  |      semi-colon: [d004c8f274b9]
+  |      Fate: superseed as d004c8f274b9
+  | x  f137d23bb3e1
+  | |
+  | x  471f378eab4c
+  |/     Successors: [a468dc9b3633]
+  |      semi-colon: [a468dc9b3633]
+  |      Fate: superseed as a468dc9b3633
+  o  ea207398892e
+  
+
+  $ hg up 'desc(A2)'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg tlog
+  @  d004c8f274b9
+  |
+  o  ea207398892e
+  
+  $ hg tlog --hidden
+  @  d004c8f274b9
+  |    Precursors: a468dc9b3633
+  |    semi-colon: a468dc9b3633
+  | x  a468dc9b3633
+  |/     Precursors: 471f378eab4c
+  |      semi-colon: 471f378eab4c
+  |      Successors: [d004c8f274b9]
+  |      semi-colon: [d004c8f274b9]
+  |      Fate: superseed as d004c8f274b9
+  | x  f137d23bb3e1
+  | |
+  | x  471f378eab4c
+  |/     Successors: [a468dc9b3633]
+  |      semi-colon: [a468dc9b3633]
+  |      Fate: superseed as a468dc9b3633
+  o  ea207398892e
+  
+
+Test templates with splitted commit
+===================================
+
+  $ hg init $TESTTMP/templates-local-split
+  $ cd $TESTTMP/templates-local-split
+  $ mkcommit ROOT
+  $ echo 42 >> a
+  $ echo 43 >> b
+  $ hg commit -A -m "A0"
+  adding a
+  adding b
+  $ hg log --hidden -G
+  @  changeset:   1:471597cad322
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ hg split -r 'desc(A0)' -d "0 0" << EOF
+  > y
+  > y
+  > n
+  > n
+  > y
+  > y
+  > EOF
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  adding a
+  adding b
+  diff --git a/a b/a
+  new file mode 100644
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +42
+  record change 1/2 to 'a'? [Ynesfdaq?] y
+  
+  diff --git a/b b/b
+  new file mode 100644
+  examine changes to 'b'? [Ynesfdaq?] n
+  
+  created new head
+  Done splitting? [yN] n
+  diff --git a/b b/b
+  new file mode 100644
+  examine changes to 'b'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +43
+  record this change to 'b'? [Ynesfdaq?] y
+  
+  no more change to split
+
+  $ hg log --hidden -G
+  @  changeset:   3:f257fde29c7a
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   2:337fec4d2edc
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  | x  changeset:   1:471597cad322
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
+Check templates
+---------------
+
+  $ hg up 'obsolete()' --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (471597cad322)
+  (use 'hg evolve' to update to its tipmost successor: 337fec4d2edc, f257fde29c7a)
+
+Precursors template should show current revision as it is the working copy
+  $ hg tlog
+  o  f257fde29c7a
+  |    Precursors: 471597cad322
+  |    semi-colon: 471597cad322
+  o  337fec4d2edc
+  |    Precursors: 471597cad322
+  |    semi-colon: 471597cad322
+  | @  471597cad322
+  |/     Successors: [337fec4d2edc, f257fde29c7a]
+  |      semi-colon: [337fec4d2edc, f257fde29c7a]
+  |      Fate: superseed as 337fec4d2edc,f257fde29c7a
+  o  ea207398892e
+  
+  $ hg up f257fde29c7a
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Precursors template should not show a precursor as it's not displayed in the
+log
+  $ hg tlog
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  o  ea207398892e
+  
+Precursors template should show the precursor as we force its display with
+--hidden
+  $ hg tlog --hidden
+  @  f257fde29c7a
+  |    Precursors: 471597cad322
+  |    semi-colon: 471597cad322
+  o  337fec4d2edc
+  |    Precursors: 471597cad322
+  |    semi-colon: 471597cad322
+  | x  471597cad322
+  |/     Successors: [337fec4d2edc, f257fde29c7a]
+  |      semi-colon: [337fec4d2edc, f257fde29c7a]
+  |      Fate: superseed as 337fec4d2edc,f257fde29c7a
+  o  ea207398892e
+  
+Test templates with folded commit
+==============================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/templates-local-fold
+  $ cd $TESTTMP/templates-local-fold
+  $ mkcommit ROOT
+  $ mkcommit A0
+  $ mkcommit B0
+  $ hg log --hidden -G
+  @  changeset:   2:0dec01379d3b
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B0
+  |
+  o  changeset:   1:471f378eab4c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ hg fold --exact -r 'desc(A0) + desc(B0)' --date "0 0" -m "C0"
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log --hidden -G
+  @  changeset:   3:eb5a0daa2192
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   2:0dec01379d3b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Check templates
+---------------
+
+  $ hg up 'desc(A0)' --hidden
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory parent is obsolete! (471f378eab4c)
+  (use 'hg evolve' to update to its successor: eb5a0daa2192)
+
+Precursors template should show current revision as it is the working copy
+  $ hg tlog
+  o  eb5a0daa2192
+  |    Precursors: 471f378eab4c
+  |    semi-colon: 471f378eab4c
+  | @  471f378eab4c
+  |/     Successors: [eb5a0daa2192]
+  |      semi-colon: [eb5a0daa2192]
+  |      Fate: superseed as eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg up 'desc(B0)' --hidden
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (0dec01379d3b)
+  (use 'hg evolve' to update to its successor: eb5a0daa2192)
+
+Precursors template should show both precursors as they should be both
+displayed
+  $ hg tlog
+  o  eb5a0daa2192
+  |    Precursors: 0dec01379d3b 471f378eab4c
+  |    semi-colon: 0dec01379d3b; 471f378eab4c
+  | @  0dec01379d3b
+  | |    Successors: [eb5a0daa2192]
+  | |    semi-colon: [eb5a0daa2192]
+  | |    Fate: superseed as eb5a0daa2192
+  | x  471f378eab4c
+  |/     Successors: [eb5a0daa2192]
+  |      semi-colon: [eb5a0daa2192]
+  |      Fate: superseed as eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg up 'desc(C0)'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Precursors template should not show precursors as it's not displayed in the
+log
+  $ hg tlog
+  @  eb5a0daa2192
+  |
+  o  ea207398892e
+  
+Precursors template should show both precursors as we force its display with
+--hidden
+  $ hg tlog --hidden
+  @  eb5a0daa2192
+  |    Precursors: 0dec01379d3b 471f378eab4c
+  |    semi-colon: 0dec01379d3b; 471f378eab4c
+  | x  0dec01379d3b
+  | |    Successors: [eb5a0daa2192]
+  | |    semi-colon: [eb5a0daa2192]
+  | |    Fate: superseed as eb5a0daa2192
+  | x  471f378eab4c
+  |/     Successors: [eb5a0daa2192]
+  |      semi-colon: [eb5a0daa2192]
+  |      Fate: superseed as eb5a0daa2192
+  o  ea207398892e
+  
+
+Test templates with divergence
+==============================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/templates-local-divergence
+  $ cd $TESTTMP/templates-local-divergence
+  $ mkcommit ROOT
+  $ mkcommit A0
+  $ hg amend -m "A1"
+  $ hg log --hidden -G
+  @  changeset:   2:fdf9bde5129a
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A1
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ hg update --hidden 'desc(A0)'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (471f378eab4c)
+  (use 'hg evolve' to update to its successor: fdf9bde5129a)
+  $ hg amend -m "A2"
+  2 new divergent changesets
+  $ hg log --hidden -G
+  @  changeset:   3:65b757b745b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  trouble:     divergent
+  |  summary:     A2
+  |
+  | o  changeset:   2:fdf9bde5129a
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    trouble:     divergent
+  |    summary:     A1
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ hg amend -m 'A3'
+
+Check templates
+---------------
+
+  $ hg up 'desc(A0)' --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (471f378eab4c)
+  (471f378eab4c has diverged, use 'hg evolve -list --divergent' to resolve the issue)
+
+Precursors template should show current revision as it is the working copy
+  $ hg tlog
+  o  019fadeab383
+  |    Precursors: 471f378eab4c
+  |    semi-colon: 471f378eab4c
+  | o  fdf9bde5129a
+  |/     Precursors: 471f378eab4c
+  |      semi-colon: 471f378eab4c
+  | @  471f378eab4c
+  |/     Successors: [fdf9bde5129a], [019fadeab383]
+  |      semi-colon: [fdf9bde5129a]; [019fadeab383]
+  |      Fate: superseed as fdf9bde5129a + superseed as 019fadeab383
+  o  ea207398892e
+  
+  $ hg up 'desc(A1)'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+Precursors template should not show precursors as it's not displayed in the
+log
+  $ hg tlog
+  o  019fadeab383
+  |
+  | @  fdf9bde5129a
+  |/
+  o  ea207398892e
+  
+Precursors template should a precursor as we force its display with --hidden
+  $ hg tlog --hidden
+  o  019fadeab383
+  |    Precursors: 65b757b745b9
+  |    semi-colon: 65b757b745b9
+  | x  65b757b745b9
+  |/     Precursors: 471f378eab4c
+  |      semi-colon: 471f378eab4c
+  |      Successors: [019fadeab383]
+  |      semi-colon: [019fadeab383]
+  |      Fate: superseed as 019fadeab383
+  | @  fdf9bde5129a
+  |/     Precursors: 471f378eab4c
+  |      semi-colon: 471f378eab4c
+  | x  471f378eab4c
+  |/     Successors: [fdf9bde5129a], [65b757b745b9]
+  |      semi-colon: [fdf9bde5129a]; [65b757b745b9]
+  |      Fate: superseed as fdf9bde5129a + superseed as 65b757b745b9
+  o  ea207398892e
+  
+Test templates with amended + folded commit
+===========================================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/templates-local-amend-fold
+  $ cd $TESTTMP/templates-local-amend-fold
+  $ mkcommit ROOT
+  $ mkcommit A0
+  $ mkcommit B0
+  $ hg amend -m "B1"
+  $ hg log --hidden -G
+  @  changeset:   3:b7ea6d14e664
+  |  tag:         tip
+  |  parent:      1:471f378eab4c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B1
+  |
+  | x  changeset:   2:0dec01379d3b
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     B0
+  |
+  o  changeset:   1:471f378eab4c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ hg fold --exact -r 'desc(A0) + desc(B1)' --date "0 0" -m "C0"
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log --hidden -G
+  @  changeset:   4:eb5a0daa2192
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   3:b7ea6d14e664
+  | |  parent:      1:471f378eab4c
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     B1
+  | |
+  | | x  changeset:   2:0dec01379d3b
+  | |/   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Check templates
+---------------
+
+  $ hg up 'desc(A0)' --hidden
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory parent is obsolete! (471f378eab4c)
+  (use 'hg evolve' to update to its successor: eb5a0daa2192)
+  $ hg tlog
+  o  eb5a0daa2192
+  |    Precursors: 471f378eab4c
+  |    semi-colon: 471f378eab4c
+  | @  471f378eab4c
+  |/     Successors: [eb5a0daa2192]
+  |      semi-colon: [eb5a0daa2192]
+  |      Fate: superseed as eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg up 'desc(B0)' --hidden
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (0dec01379d3b)
+  (use 'hg evolve' to update to its successor: eb5a0daa2192)
+  $ hg tlog
+  o  eb5a0daa2192
+  |    Precursors: 0dec01379d3b 471f378eab4c
+  |    semi-colon: 0dec01379d3b; 471f378eab4c
+  | @  0dec01379d3b
+  | |    Successors: [eb5a0daa2192]
+  | |    semi-colon: [eb5a0daa2192]
+  | |    Fate: superseed as eb5a0daa2192
+  | x  471f378eab4c
+  |/     Successors: [eb5a0daa2192]
+  |      semi-colon: [eb5a0daa2192]
+  |      Fate: superseed as eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg up 'desc(B1)' --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (b7ea6d14e664)
+  (use 'hg evolve' to update to its successor: eb5a0daa2192)
+  $ hg tlog
+  o  eb5a0daa2192
+  |    Precursors: 471f378eab4c b7ea6d14e664
+  |    semi-colon: 471f378eab4c; b7ea6d14e664
+  | @  b7ea6d14e664
+  | |    Successors: [eb5a0daa2192]
+  | |    semi-colon: [eb5a0daa2192]
+  | |    Fate: superseed as eb5a0daa2192
+  | x  471f378eab4c
+  |/     Successors: [eb5a0daa2192]
+  |      semi-colon: [eb5a0daa2192]
+  |      Fate: superseed as eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg up 'desc(C0)'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg tlog
+  @  eb5a0daa2192
+  |
+  o  ea207398892e
+  
+  $ hg tlog --hidden
+  @  eb5a0daa2192
+  |    Precursors: 471f378eab4c b7ea6d14e664
+  |    semi-colon: 471f378eab4c; b7ea6d14e664
+  | x  b7ea6d14e664
+  | |    Precursors: 0dec01379d3b
+  | |    semi-colon: 0dec01379d3b
+  | |    Successors: [eb5a0daa2192]
+  | |    semi-colon: [eb5a0daa2192]
+  | |    Fate: superseed as eb5a0daa2192
+  | | x  0dec01379d3b
+  | |/     Successors: [b7ea6d14e664]
+  | |      semi-colon: [b7ea6d14e664]
+  | |      Fate: superseed as b7ea6d14e664
+  | x  471f378eab4c
+  |/     Successors: [eb5a0daa2192]
+  |      semi-colon: [eb5a0daa2192]
+  |      Fate: superseed as eb5a0daa2192
+  o  ea207398892e
+  
+
+Test template with pushed and pulled obs markers
+==============================================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/templates-local-remote-markers-1
+  $ cd $TESTTMP/templates-local-remote-markers-1
+  $ mkcommit ROOT
+  $ mkcommit A0  
+  $ hg clone $TESTTMP/templates-local-remote-markers-1 $TESTTMP/templates-local-remote-markers-2
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd $TESTTMP/templates-local-remote-markers-2
+  $ hg log --hidden -G
+  @  changeset:   1:471f378eab4c
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ cd $TESTTMP/templates-local-remote-markers-1
+  $ hg amend -m "A1"
+  $ hg amend -m "A2"
+  $ hg log --hidden -G
+  @  changeset:   3:7a230b46bf61
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | x  changeset:   2:fdf9bde5129a
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A1
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ cd $TESTTMP/templates-local-remote-markers-2
+  $ hg pull
+  pulling from $TESTTMP/templates-local-remote-markers-1
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 1 files (+1 heads)
+  2 new obsolescence markers
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  working directory parent is obsolete! (471f378eab4c)
+  (use 'hg evolve' to update to its successor: 7a230b46bf61)
+  $ hg log --hidden -G
+  o  changeset:   2:7a230b46bf61
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | @  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
+Check templates
+---------------
+
+  $ hg tlog
+  o  7a230b46bf61
+  |    Precursors: 471f378eab4c
+  |    semi-colon: 471f378eab4c
+  | @  471f378eab4c
+  |/     Successors: [7a230b46bf61]
+  |      semi-colon: [7a230b46bf61]
+  |      Fate: superseed as 7a230b46bf61
+  o  ea207398892e
+  
+  $ hg up 'desc(A2)'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg tlog
+  @  7a230b46bf61
+  |
+  o  ea207398892e
+  
+  $ hg tlog --hidden
+  @  7a230b46bf61
+  |    Precursors: 471f378eab4c
+  |    semi-colon: 471f378eab4c
+  | x  471f378eab4c
+  |/     Successors: [7a230b46bf61]
+  |      semi-colon: [7a230b46bf61]
+  |      Fate: superseed as 7a230b46bf61
+  o  ea207398892e
+  
--- a/tests/test-evolve.t
+++ b/tests/test-evolve.t
@@ -793,6 +793,17 @@
       more than 2 successors:         0
       available  keys:
                  user:               10
+  marker size:
+      format v1:
+          smallest length:           69
+          longer length:             69
+          median length:             69
+          mean length:               69
+      format v0:
+          smallest length:           * (glob)
+          longer length:             * (glob)
+          median length:             * (glob)
+          mean length:               * (glob)
   disconnected clusters:              1
           any known node:             1
           smallest length:           10
@@ -1491,124 +1502,3 @@
 
   $ hg status newlyadded
   A newlyadded
-
-hg metaedit
------------
-
-  $ hg update --clean .
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm newlyadded
-  $ hg metaedit -r 0
-  abort: cannot edit commit information for public revisions
-  [255]
-  $ hg metaedit --fold
-  abort: revisions must be specified with --fold
-  [255]
-  $ hg metaedit -r 0 --fold
-  abort: cannot fold public revisions
-  [255]
-  $ hg metaedit '36 + 42' --fold
-  abort: cannot fold non-linear revisions (multiple roots given)
-  [255]
-  $ hg metaedit '36::39 + 41' --fold
-  abort: cannot fold non-linear revisions (multiple heads given)
-  [255]
-check that metaedit respects allowunstable
-  $ hg metaedit '.^' --config 'experimental.evolution=createmarkers, allnewcommands'
-  abort: cannot edit commit information in the middle of a stack
-  (c904da5245b0 will become unstable and new unstable changes are not allowed)
-  [255]
-  $ hg metaedit '18::20' --fold --config 'experimental.evolution=createmarkers, allnewcommands'
-  abort: cannot fold chain not ending with a head or with branching
-  (new unstable changesets are not allowed)
-  [255]
-  $ hg metaedit --user foobar
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
-  42: test
-  43: foobar
-  $ hg log --template '{rev}: {author}\n' -r .
-  43: foobar
-
-TODO: support this
-  $ hg metaedit '.^::.'
-  abort: editing multiple revisions without --fold is not currently supported
-  [255]
-
-  $ HGEDITOR=cat hg metaedit '.^::.' --fold
-  HG: This is a fold of 2 changesets.
-  HG: Commit message of changeset 41.
-  
-  amended
-  
-  HG: Commit message of changeset 43.
-  
-  will be evolved safely
-  
-  
-  
-  HG: Enter commit message.  Lines beginning with 'HG:' are removed.
-  HG: Leave message empty to abort commit.
-  HG: --
-  HG: user: test
-  HG: branch 'default'
-  HG: changed a
-  HG: changed newfile
-  2 changesets folded
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-
-  $ glog -r .
-  @  44:41bf1183869c@default(draft) amended
-  |
-  ~
-
-no new commit is created here because the date is the same
-  $ HGEDITOR=cat hg metaedit
-  amended
-  
-  
-  will be evolved safely
-  
-  
-  HG: Enter commit message.  Lines beginning with 'HG:' are removed.
-  HG: Leave message empty to abort commit.
-  HG: --
-  HG: user: test
-  HG: branch 'default'
-  HG: changed a
-  HG: changed newfile
-  nothing changed
-
-  $ glog -r '.^::.'
-  @  44:41bf1183869c@default(draft) amended
-  |
-  o  36:43c3f5ef149f@default(draft) add uu
-  |
-  ~
-
-TODO: don't create a new commit in this case, we should take the date of the
-old commit (we add a default date with a value to show that metaedit is taking
-the current date to generate the hash, this way we still have a stable hash
-but highlight the bug)
-  $ hg metaedit --config defaults.metaedit= --config devel.default-date="42 0"
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n'
-  36: add uu
-  45: amended
-
-  $ hg up .^
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg metaedit --user foobar2 45
-  $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
-  42: test
-  43: foobar
-  44: test
-  45: test
-  46: foobar2
-  $ hg diff -r 45 -r 46 --hidden
-
-'fold' one commit
-  $ hg metaedit 39 --fold --user foobar3
-  1 changesets folded
-  $ hg log -r 47 --template '{rev}: {author}\n'
-  47: foobar3
--- a/tests/test-exchange-obsmarkers-case-B1.t
+++ b/tests/test-exchange-obsmarkers-case-B1.t
@@ -75,7 +75,7 @@
   ===========
   a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 0000000000000000000000000000000000000000
   f5bc6836db60e308a17ba08bf050154ba9c4fad7 926d9d84b97b3483891ae983990ad87c1f7827e9
-  f6fbb35d8ac958bbe70035e4c789c18471cdc0af e041f7ff1c7bd5501c7ab602baa35f0873128021
+  f6fbb35d8ac958bbe70035e4c789c18471cdc0af c2398c6305068b6b377f36402c507b713a7c586f
   obshashrange
   ============
            rev         node        index         size        depth      obshash
--- a/tests/test-exchange-obsmarkers-case-B2.t
+++ b/tests/test-exchange-obsmarkers-case-B2.t
@@ -65,7 +65,7 @@
   obshashtree
   ===========
   a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 52a5380bc04783a9ad43bb2ab2f47a02ef02adcc
-  f5bc6836db60e308a17ba08bf050154ba9c4fad7 c5a567339e205e8cc4c494e4fb82944daaec449c
+  f5bc6836db60e308a17ba08bf050154ba9c4fad7 6892277170c13fec6712303d639965a454b5cabf
   obshashrange
   ============
            rev         node        index         size        depth      obshash
--- a/tests/test-exchange-obsmarkers-case-B3.t
+++ b/tests/test-exchange-obsmarkers-case-B3.t
@@ -78,7 +78,7 @@
   a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 0000000000000000000000000000000000000000
   f5bc6836db60e308a17ba08bf050154ba9c4fad7 0000000000000000000000000000000000000000
   35b1839966785d5703a01607229eea932db42f87 631ab4cd02ffa1d144dc8f32a18be574076031e3
-  e56289ab6378dc752fd7965f8bf66b58bda740bd 47c9d2d8db5d4b1eddd0266329ad260ccc84772c
+  e56289ab6378dc752fd7965f8bf66b58bda740bd a37fa02ad96f2e8ecce7080e73cfe22af8e1b14f
   obshashrange
   ============
            rev         node        index         size        depth      obshash
--- a/tests/test-exchange-obsmarkers-case-B4.t
+++ b/tests/test-exchange-obsmarkers-case-B4.t
@@ -93,7 +93,7 @@
   a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 1900882e85db10a1dc5bc7748f436a8a834356c6
   f5bc6836db60e308a17ba08bf050154ba9c4fad7 c27e764c783f451ef3aa40daf2a3795e6674cd06
   f6fbb35d8ac958bbe70035e4c789c18471cdc0af 907beff79fdff2b82b5d3bed7989107a6d744508
-  7f7f229b13a629a5b20581c6cb723f4e2ca54bed c27e764c783f451ef3aa40daf2a3795e6674cd06
+  7f7f229b13a629a5b20581c6cb723f4e2ca54bed 86e6b5a5ad828d83b3205ea4ca11154972da19a1
   obshashrange
   ============
            rev         node        index         size        depth      obshash
--- a/tests/test-exchange-obsmarkers-case-B5.t
+++ b/tests/test-exchange-obsmarkers-case-B5.t
@@ -100,7 +100,7 @@
   6e72f0a95b5e01a7504743aa941f69cb1fbef8b0 13bd00d88332fcd3fe634ed42f9d35c9cfc06398
   1d0f3cd253006f014c7687a78abbc9287db4101d 01d985a82467333a4de7a5b4e8a0de3286f8bda8
   e5ea8f9c73143125d36658e90ef70c6d2027a5b7 d2b1159bcf3694aabc3674785b1859544c35357d
-  069b05c3876d56f62895e853a501ea58ea85f68d d00e3201fcf83a1bf42e70757f07b45bdd77a220
+  069b05c3876d56f62895e853a501ea58ea85f68d a07163ee7e7cda4065f7593715b7b12f833f7065
   obshashrange
   ============
            rev         node        index         size        depth      obshash
--- a/tests/test-exchange-obsmarkers-case-B6.t
+++ b/tests/test-exchange-obsmarkers-case-B6.t
@@ -77,7 +77,7 @@
   a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 86e41541149f4b6cccc5fd131d744d8e83a681e5
   f5bc6836db60e308a17ba08bf050154ba9c4fad7 f2e05412d3f1d5bc1ae647cf9efc43e0399c26ca
   962ecf6b1afc94e15c7e48fdfb76ef8abd11372b 974507d1c466d0aa86d288836194339ed3b98736
-  f6298a8ac3a4b78bbeae5f1d3dc5bc3c3812f0f3 04e03a8959d8a39984e6a8f4a16fba975b364747
+  f6298a8ac3a4b78bbeae5f1d3dc5bc3c3812f0f3 1e0b6dce792c229c865a543c6f8964b4bba5fb5a
   obshashrange
   ============
            rev         node        index         size        depth      obshash
--- a/tests/test-exchange-obsmarkers-case-B7.t
+++ b/tests/test-exchange-obsmarkers-case-B7.t
@@ -71,7 +71,7 @@
   ===========
   a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 0000000000000000000000000000000000000000
   f5bc6836db60e308a17ba08bf050154ba9c4fad7 926d9d84b97b3483891ae983990ad87c1f7827e9
-  f6fbb35d8ac958bbe70035e4c789c18471cdc0af e041f7ff1c7bd5501c7ab602baa35f0873128021
+  f6fbb35d8ac958bbe70035e4c789c18471cdc0af c2398c6305068b6b377f36402c507b713a7c586f
   obshashrange
   ============
            rev         node        index         size        depth      obshash
--- a/tests/test-exchange-obsmarkers-case-C1.t
+++ b/tests/test-exchange-obsmarkers-case-C1.t
@@ -75,8 +75,8 @@
   obshashtree
   ===========
   a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 1ce18e5a71f78d443a80c819f2f7197c4706af70
-  f5bc6836db60e308a17ba08bf050154ba9c4fad7 92af733686ce7e0469d8b2b87b4612a4c2d33468
-  f6fbb35d8ac958bbe70035e4c789c18471cdc0af 3800aeba3728457abb9c508c94f6abc59e698c55
+  f5bc6836db60e308a17ba08bf050154ba9c4fad7 5c3c935df6f4007c633c3386d6f58b22c274809e
+  f6fbb35d8ac958bbe70035e4c789c18471cdc0af bccb3d243420ef26782fa11f209830db360c34b2
   obshashrange
   ============
            rev         node        index         size        depth      obshash
--- a/tests/test-exchange-obsmarkers-case-C2.t
+++ b/tests/test-exchange-obsmarkers-case-C2.t
@@ -80,7 +80,7 @@
   ===========
   a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 0000000000000000000000000000000000000000
   28b51eb45704506b5c603decd6bf7ac5e0f6a52f 72f95b7b9fa12243aeb90433d211f2c38263da31
-  06055a7959d4128e6e3bccfd01482e83a2db8a3a 58ecf9a107b10986d88da605eb0d03b7f24ae486
+  06055a7959d4128e6e3bccfd01482e83a2db8a3a 9d758cd0bf02f27e9b7de5665b10ceb1dc8bb1f1
   e5ea8f9c73143125d36658e90ef70c6d2027a5b7 289cb0d058c81c763eca8bb438657dba9a7ba646
   obshashrange
   ============
--- a/tests/test-exchange-obsmarkers-case-C3.t
+++ b/tests/test-exchange-obsmarkers-case-C3.t
@@ -84,8 +84,8 @@
   ===========
   a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 40be80b35671116f2c61ef25797806536a6eb5a0
   28b51eb45704506b5c603decd6bf7ac5e0f6a52f beac7228bbe708bc7c9181c3c27f8a17f21dbd9f
-  06055a7959d4128e6e3bccfd01482e83a2db8a3a 8b648bd67281e9e525919285ac7b3bb2836c2f02
-  e5ea8f9c73143125d36658e90ef70c6d2027a5b7 dcd2b566ad0983333be704afdc205066e1a6b742
+  06055a7959d4128e6e3bccfd01482e83a2db8a3a 04b20150f38991d297ecfe0bf0f77deb816aed9d
+  e5ea8f9c73143125d36658e90ef70c6d2027a5b7 a59f7d5ea437389a33d2e98b9b85ba1d568a743e
   obshashrange
   ============
            rev         node        index         size        depth      obshash
--- a/tests/test-exchange-obsmarkers-case-C4.t
+++ b/tests/test-exchange-obsmarkers-case-C4.t
@@ -87,7 +87,7 @@
   a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 a9c02d134f5b98acc74d1dc4eb28fd59f958a2bd
   f5bc6836db60e308a17ba08bf050154ba9c4fad7 619b4d13bd9878f04d7208dcfcf1e89da826f6be
   35b1839966785d5703a01607229eea932db42f87 ddeb7b7a87378f59cecb36d5146df0092b6b3327
-  7f7f229b13a629a5b20581c6cb723f4e2ca54bed 58ef2e726c5bd89bceffb6243294b38eadbf3d60
+  7f7f229b13a629a5b20581c6cb723f4e2ca54bed 0ef78fef48a60c677f55ba6efa4126dab2d6caf2
   obshashrange
   ============
            rev         node        index         size        depth      obshash
copy from tests/test-evolve.t
copy to tests/test-metaedit.t
--- a/tests/test-evolve.t
+++ b/tests/test-metaedit.t
@@ -20,7 +20,7 @@
   $ mkcommit() {
   >    echo "$1" > "$1"
   >    hg add "$1"
-  >    hg ci -m "add $1"
+  >    hg ci -m "$1"
   > }
 
   $ mkstack() {
@@ -42,143 +42,6 @@
   >   for i in $@; do mkcommit $i ; done
   > }
 
-Test the evolution test topic is installed
-
-  $ hg help evolution
-  Safely Rewriting History
-  """"""""""""""""""""""""
-  
-      Obsolescence markers make it possible to mark changesets that have been
-      deleted or superset in a new version of the changeset.
-  
-      Unlike the previous way of handling such changes, by stripping the old
-      changesets from the repository, obsolescence markers can be propagated
-      between repositories. This allows for a safe and simple way of exchanging
-      mutable history and altering it after the fact. Changeset phases are
-      respected, such that only draft and secret changesets can be altered (see
-      'hg help phases' for details).
-  
-      Obsolescence is tracked using "obsolete markers", a piece of metadata
-      tracking which changesets have been made obsolete, potential successors
-      for a given changeset, the moment the changeset was marked as obsolete,
-      and the user who performed the rewriting operation. The markers are stored
-      separately from standard changeset data can be exchanged without any of
-      the precursor changesets, preventing unnecessary exchange of obsolescence
-      data.
-  
-      The complete set of obsolescence markers describes a history of changeset
-      modifications that is orthogonal to the repository history of file
-      modifications. This changeset history allows for detection and automatic
-      resolution of edge cases arising from multiple users rewriting the same
-      part of history concurrently.
-  
-      Current feature status
-      ======================
-  
-      This feature is still in development.  If you see this help, you have
-      enabled an extension that turned this feature on.
-  
-      Obsolescence markers will be exchanged between repositories that
-      explicitly assert support for the obsolescence feature (this can currently
-      only be done via an extension).
-
-various init
-
-  $ hg init local
-  $ cd local
-  $ mkcommit a
-  $ mkcommit b
-  $ cat >> .hg/hgrc << EOF
-  > [phases]
-  > publish = True
-  > EOF
-  $ hg pull -q . # make 1 public
-  $ rm .hg/hgrc
-  $ mkcommit c
-  $ mkcommit d
-  $ hg up 1
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ mkcommit e -q
-  created new head
-  $ mkcommit f
-  $ hg qlog
-  5 - e44648563c73 add f (draft)
-  4 - fbb94e3a0ecf add e (draft)
-  3 - 47d2a3944de8 add d (draft)
-  2 - 4538525df7e2 add c (draft)
-  1 - 7c3bad9141dc add b (public)
-  0 - 1f0dee641bb7 add a (public)
-
-test kill and immutable changeset
-
-  $ hg log -r 1 --template '{rev} {phase} {obsolete}\n'
-  1 public 
-  $ hg prune 1
-  abort: cannot prune immutable changeset: 7c3bad9141dc
-  (see 'hg help phases' for details)
-  [255]
-  $ hg log -r 1 --template '{rev} {phase} {obsolete}\n'
-  1 public 
-
-test simple kill
-
-  $ hg id -n
-  5
-  $ hg prune .
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  working directory now at fbb94e3a0ecf
-  1 changesets pruned
-  $ hg qlog
-  4 - fbb94e3a0ecf add e (draft)
-  3 - 47d2a3944de8 add d (draft)
-  2 - 4538525df7e2 add c (draft)
-  1 - 7c3bad9141dc add b (public)
-  0 - 1f0dee641bb7 add a (public)
-
-test multiple kill
-
-  $ hg prune 4 -r 3
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  working directory now at 7c3bad9141dc
-  2 changesets pruned
-  $ hg qlog
-  2 - 4538525df7e2 add c (draft)
-  1 - 7c3bad9141dc add b (public)
-  0 - 1f0dee641bb7 add a (public)
-
-test kill with dirty changes
-
-  $ hg up 2
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ echo 4 > g
-  $ hg add g
-  $ hg prune .
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  working directory now at 7c3bad9141dc
-  1 changesets pruned
-  $ hg st
-  A g
-
-Smoketest debugobsrelsethashtree:
-
-  $ hg debugobsrelsethashtree
-  1f0dee641bb7258c56bd60e93edfa2405381c41e 0000000000000000000000000000000000000000
-  7c3bad9141dcb46ff89abf5f61856facd56e476c * (glob)
-  4538525df7e2b9f09423636c61ef63a4cb872a2d * (glob)
-  47d2a3944de8b013de3be9578e8e344ea2e6c097 * (glob)
-  fbb94e3a0ecf6d20c2cc31152ef162ce45af982f * (glob)
-  e44648563c73f75950076031c6fdf06629de95f1 * (glob)
-
-Smoketest stablerange.obshash:
-
-  $ hg debugobshashrange --subranges --rev 'head()'
-           rev         node        index         size        depth      obshash
-             1 7c3bad9141dc            0            2            2 * (glob)
-             0 1f0dee641bb7            0            1            1 000000000000
-             1 7c3bad9141dc            1            1            2 * (glob)
-
-  $ cd ..
-
 ##########################
 importing Parren test
 ##########################
@@ -188,7 +51,7 @@
   > logtemplate = "{rev}\t{bookmarks}: {desc|firstline} - {author|user}\n"
   > EOF
 
-Creating And Updating Changeset
+HG METAEDIT
 ===============================
 
 Setup the Base Repo
@@ -196,1308 +59,44 @@
 
 We start with a plain base repo::
 
-  $ hg init main; cd main
-  $ cat >main-file-1 <<-EOF
-  > One
-  > 
-  > Two
-  > 
-  > Three
-  > EOF
-  $ echo Two >main-file-2
-  $ hg add
-  adding main-file-1
-  adding main-file-2
-  $ hg commit --message base
-  $ cd ..
-
-and clone this into a new repo where we do our work::
-
-  $ hg clone main work
-  updating to branch default
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cd work
-
-
-Create First Patch
-------------------
-
-To begin with, we just do the changes that will be the initial version of the changeset::
-
-  $ echo One >file-from-A
-  $ sed -i'' -e s/One/Eins/ main-file-1
-  $ hg add file-from-A
-
-So this is what we would like our changeset to be::
-
-  $ hg diff
-  diff --git a/file-from-A b/file-from-A
-  new file mode 100644
-  --- /dev/null
-  +++ b/file-from-A
-  @@ -0,0 +1,1 @@
-  +One
-  diff --git a/main-file-1 b/main-file-1
-  --- a/main-file-1
-  +++ b/main-file-1
-  @@ -1,1 +1,1 @@
-  -One
-  +Eins
-
-To commit it we just - commit it::
-
-  $ hg commit --message "a nifty feature"
-
-and place a bookmark so we can easily refer to it again (which we could have done before the commit)::
-
-  $ hg book feature-A
-
-
-Create Second Patch
--------------------
-
-Let's do this again for the second changeset::
-
-  $ echo Two >file-from-B
-  $ sed -i'' -e s/Two/Zwie/ main-file-1
-  $ hg add file-from-B
-
-Before committing, however, we need to switch to a new bookmark for the second
-changeset. Otherwise we would inadvertently move the bookmark for our first changeset.
-It is therefore advisable to always set the bookmark before committing::
-
-  $ hg book feature-B
-  $ hg commit --message "another feature (child of $(hg log -r . -T '{node|short}'))"
-
-So here we are::
-
-  $ hg book
-     feature-A                 1:568a468b60fc
-   * feature-B                 2:73296a82292a
-
-
-Fix The Second Patch
---------------------
-
-There's a typo in feature-B. We spelled *Zwie* instead of *Zwei*::
-
-  $ hg diff --change tip | grep -F Zwie
-  +Zwie
-
-Fixing this is very easy. Just change::
-
-  $ sed -i'' -e s/Zwie/Zwei/ main-file-1
-
-and **amend**::
-
-  $ hg amend
-
-This results in a new single changeset for our amended changeset, and the old
-changeset plus the updating changeset are hidden from view by default::
-
-  $ hg log
-  4	feature-B: another feature (child of 568a468b60fc) - test
-  1	feature-A: a nifty feature - test
-  0	: base - test
-
-  $ hg up feature-A -q
-  $ hg bookmark -i feature-A
-  $ sed -i'' -e s/Eins/Un/ main-file-1
-
-(amend of public changeset denied)
-
-  $ hg phase --public 0 -v
-  phase changed for 1 changesets
-
-
-(amend of on ancestors)
-
-  $ hg amend
-  1 new unstable changesets
-  $ hg log
-  6	feature-A: a nifty feature - test
-  4	feature-B: another feature (child of 568a468b60fc) - test
-  1	: a nifty feature - test
-  0	: base - test
-  $ hg up -q 0
-  $ glog --hidden
-  o  6:ba0ec09b1bab@default(draft) a nifty feature
-  |
-  | x  5:c296b79833d1@default(draft) temporary amend commit for 568a468b60fc
-  | |
-  | | o  4:6992c59c6b06@default(draft) another feature (child of 568a468b60fc)
-  | |/
-  | | x  3:c97947cdc7a2@default(draft) temporary amend commit for 73296a82292a
-  | | |
-  | | x  2:73296a82292a@default(draft) another feature (child of 568a468b60fc)
-  | |/
-  | x  1:568a468b60fc@default(draft) a nifty feature
-  |/
-  @  0:e55e0562ee93@default(public) base
-  
-  $ hg debugobsolete
-  73296a82292a76fb8a7061969d2489ec0d84cd5e 6992c59c6b06a1b4a92e24ff884829ae026d018b 0 (*) {'user': 'test'} (glob)
-  c97947cdc7a2a11cf78419f5c2c3dd3944ec79e8 0 {73296a82292a76fb8a7061969d2489ec0d84cd5e} (*) {'user': 'test'} (glob)
-  568a468b60fc99a42d5d4ddbe181caff1eef308d ba0ec09b1babf3489b567853807f452edd46704f 0 (*) {'user': 'test'} (glob)
-  c296b79833d1d497f33144786174bf35e04e44a3 0 {568a468b60fc99a42d5d4ddbe181caff1eef308d} (*) {'user': 'test'} (glob)
-  $ hg evolve
-  move:[4] another feature (child of 568a468b60fc)
-  atop:[6] a nifty feature
-  merging main-file-1
-  working directory is now at 99833d22b0c6
-  $ hg log
-  7	feature-B: another feature (child of ba0ec09b1bab) - test
-  6	feature-A: a nifty feature - test
-  0	: base - test
-
-Test commit -o options
-
-  $ hg up 6
-  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg revert -r 7 --all
-  adding file-from-B
-  reverting main-file-1
-  $ sed -i'' -e s/Zwei/deux/ main-file-1
-  $ hg commit -m 'another feature that rox' -o 7
+  $ hg init $TESTTMP/metaedit; cd $TESTTMP/metaedit
+  $ mkcommit "ROOT"
+  $ hg phase --public "desc(ROOT)"
+  $ mkcommit "A"
+  $ mkcommit "B"
+  $ hg up "desc(A)"
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit "C"
   created new head
-  $ hg log
-  8	feature-B: another feature that rox - test
-  6	feature-A: a nifty feature - test
-  0	: base - test
-
-phase change turning obsolete changeset public issue a bumped warning
-
-  $ hg phase --hidden --public 7
-  1 new bumped changesets
-
-all solving bumped troubled
-
-  $ hg glog
-  @  8	feature-B: another feature that rox - test
-  |
-  | o  7	: another feature (child of ba0ec09b1bab) - test
-  |/
-  o  6	feature-A: a nifty feature - test
-  |
-  o  0	: base - test
-  
-  $ hg evolve --any --traceback --bumped
-  recreate:[8] another feature that rox
-  atop:[7] another feature (child of ba0ec09b1bab)
-  computing new diff
-  committed as 6707c5e1c49d
-  working directory is now at 6707c5e1c49d
-  $ hg glog
-  @  9	feature-B: bumped update to 99833d22b0c6: - test
-  |
-  o  7	: another feature (child of ba0ec09b1bab) - test
-  |
-  o  6	feature-A: a nifty feature - test
-  |
-  o  0	: base - test
-  
-  $ hg diff --hidden -r 9 -r 8
-  $ hg diff -r 9^ -r 9
-  diff --git a/main-file-1 b/main-file-1
-  --- a/main-file-1
-  +++ b/main-file-1
-  @@ -3,1 +3,1 @@
-  -Zwei
-  +deux
-  $ hg log -r 'bumped()' # no more bumped
-
-test evolve --all
-  $ sed -i'' -e s/deux/to/ main-file-1
-  $ hg commit -m 'dansk 2!'
-  $ sed -i'' -e s/Three/tre/ main-file-1
-  $ hg commit -m 'dansk 3!'
-  $ hg update 9
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ sed -i'' -e s/Un/Én/ main-file-1
-  $ hg commit --amend -m 'dansk!'
-  2 new unstable changesets
-
-(ninja test for the {trouble} template:
-
-  $ hg log -G --template '{rev} {troubles}\n'
-  @  13
-  |
-  | o  11 unstable
-  | |
-  | o  10 unstable
-  | |
-  | x  9
-  |/
-  o  7
-  |
-  o  6
-  |
-  o  0
-  
-
-
-(/ninja)
-
-  $ hg evolve --all --traceback
-  move:[10] dansk 2!
-  atop:[13] dansk!
-  merging main-file-1
-  move:[11] dansk 3!
-  atop:[14] dansk 2!
-  merging main-file-1
-  working directory is now at 68557e4f0048
-  $ hg glog
-  @  15	: dansk 3! - test
-  |
-  o  14	: dansk 2! - test
-  |
-  o  13	feature-B: dansk! - test
-  |
-  o  7	: another feature (child of ba0ec09b1bab) - test
-  |
-  o  6	feature-A: a nifty feature - test
-  |
-  o  0	: base - test
-  
-
-  $ cd ..
-
-enable general delta
-
-  $ cat << EOF >> $HGRCPATH
-  > [format]
-  > generaldelta=1
-  > EOF
-
-
-
-  $ hg init alpha
-  $ cd alpha
-  $ echo 'base' > firstfile
-  $ hg add firstfile
-  $ hg ci -m 'base'
-
-  $ cd ..
-  $ hg clone -Ur 0 alpha beta
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 1 changes to 1 files
-  $ cd alpha
+  $ mkcommit "D"
+  $ echo "D'" > D
+  $ hg amend -m "D2"
+  $ hg up "desc(C)"
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit "E"
+  created new head
+  $ mkcommit "F"
 
-  $ cat << EOF > A
-  > We
-  > need
-  > some
-  > kind
-  > of 
-  > file
-  > big
-  > enough
-  > to
-  > prevent
-  > snapshot
-  > .
-  > yes
-  > new
-  > lines
-  > are
-  > useless
-  > .
-  > EOF
-  $ hg add A
-  $ hg commit -m 'adding A'
-  $ hg mv A B
-  $ echo '.' >> B
-  $ hg amend -m 'add B'
-  $ hg verify
-  checking changesets
-  checking manifests
-  crosschecking files in changesets and manifests
-  checking files
-  3 files, 4 changesets, 4 total revisions
-  $ hg --config extensions.hgext.mq= strip 'extinct()'
-  abort: empty revision set
-  [255]
-  $ hg --config extensions.hgext.mq= strip --hidden 'extinct()'
-  saved backup bundle to $TESTTMP/alpha/.hg/strip-backup/e87767087a57-d7bd82e9-backup.hg (glob)
-  $ hg verify
-  checking changesets
-  checking manifests
-  crosschecking files in changesets and manifests
-  checking files
-  2 files, 2 changesets, 2 total revisions
-  $ cd ..
-
-Clone just this branch
-
-  $ cd beta
-  $ hg pull -r tip ../alpha
-  pulling from ../alpha
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 1 changes to 1 files
-  2 new obsolescence markers
-  (run 'hg update' to get a working copy)
-  $ hg up
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-
-  $ cd ..
-
-Test graft --obsolete/--old-obsolete
-
-  $ hg init test-graft
-  $ cd test-graft
-  $ mkcommit 0
-  $ mkcommit 1
-  $ mkcommit 2
-  $ mkcommit 3
-  $ hg up -qC 0
-  $ mkcommit 4
-  created new head
-  $ glog --hidden
-  @  4:ce341209337f@default(draft) add 4
-  |
-  | o  3:0e84df4912da@default(draft) add 3
-  | |
-  | o  2:db038628b9e5@default(draft) add 2
-  | |
-  | o  1:73d38bb17fd7@default(draft) add 1
-  |/
-  o  0:8685c6d34325@default(draft) add 0
-  
-  $ hg graft -r3 -O
-  grafting 3:0e84df4912da "add 3"
-  $ hg graft -r1 -o 2
-  grafting 1:73d38bb17fd7 "add 1"
-  $ glog --hidden
-  @  6:acb28cd497b7@default(draft) add 1
-  |
-  o  5:0b9e50c35132@default(draft) add 3
-  |
-  o  4:ce341209337f@default(draft) add 4
-  |
-  | x  3:0e84df4912da@default(draft) add 3
-  | |
-  | x  2:db038628b9e5@default(draft) add 2
-  | |
-  | o  1:73d38bb17fd7@default(draft) add 1
-  |/
-  o  0:8685c6d34325@default(draft) add 0
-  
-  $ hg debugobsolete
-  0e84df4912da4c7cad22a3b4fcfd58ddfb7c8ae9 0b9e50c35132ff548ec0065caea6a87e1ebcef32 0 (*) {'user': 'test'} (glob)
-  db038628b9e56f51a454c0da0c508df247b41748 acb28cd497b7f8767e01ef70f68697a959573c2d 0 (*) {'user': 'test'} (glob)
-
-Test graft --continue
-
-  $ hg up -qC 0
-  $ echo 2 > 1
-  $ hg ci -Am conflict 1
-  created new head
-  $ hg up -qC 6
-  $ hg graft -O 7
-  grafting 7:a5bfd90a2f29 "conflict" (tip)
-  merging 1
-  warning: conflicts while merging 1! (edit, then use 'hg resolve --mark')
-  abort: unresolved conflicts, can't continue
-  (use 'hg resolve' and 'hg graft --continue')
-  [255]
-  $ hg log -r7 --template '{rev}:{node|short} {obsolete}\n'
-  7:a5bfd90a2f29 
-  $ echo 3 > 1
-  $ hg resolve -m 1
-  (no more unresolved files)
-  continue: hg graft --continue
-  $ hg graft --continue -O
-  grafting 7:a5bfd90a2f29 "conflict" (tip)
-  $ glog --hidden
-  @  8:920e58bb443b@default(draft) conflict
-  |
-  | x  7:a5bfd90a2f29@default(draft) conflict
-  | |
-  o |  6:acb28cd497b7@default(draft) add 1
-  | |
-  o |  5:0b9e50c35132@default(draft) add 3
-  | |
-  o |  4:ce341209337f@default(draft) add 4
-  |/
-  | x  3:0e84df4912da@default(draft) add 3
-  | |
-  | x  2:db038628b9e5@default(draft) add 2
-  | |
-  | o  1:73d38bb17fd7@default(draft) add 1
-  |/
-  o  0:8685c6d34325@default(draft) add 0
-  
-  $ hg debugobsolete
-  0e84df4912da4c7cad22a3b4fcfd58ddfb7c8ae9 0b9e50c35132ff548ec0065caea6a87e1ebcef32 0 (*) {'user': 'test'} (glob)
-  db038628b9e56f51a454c0da0c508df247b41748 acb28cd497b7f8767e01ef70f68697a959573c2d 0 (*) {'user': 'test'} (glob)
-  a5bfd90a2f29c7ccb8f917ff4e5013a9053d0a04 920e58bb443b73eea9d6d65570b4241051ea3229 0 (*) {'user': 'test'} (glob)
-
-Test touch
+Test
+----
 
-  $ glog
-  @  8:920e58bb443b@default(draft) conflict
-  |
-  o  6:acb28cd497b7@default(draft) add 1
-  |
-  o  5:0b9e50c35132@default(draft) add 3
-  |
-  o  4:ce341209337f@default(draft) add 4
-  |
-  | o  1:73d38bb17fd7@default(draft) add 1
-  |/
-  o  0:8685c6d34325@default(draft) add 0
-  
-  $ hg touch
-  $ glog
-  @  9:*@default(draft) conflict (glob)
-  |
-  o  6:acb28cd497b7@default(draft) add 1
-  |
-  o  5:0b9e50c35132@default(draft) add 3
-  |
-  o  4:ce341209337f@default(draft) add 4
-  |
-  | o  1:73d38bb17fd7@default(draft) add 1
-  |/
-  o  0:8685c6d34325@default(draft) add 0
-  
-  $ hg touch .
-  $ glog
-  @  10:*@default(draft) conflict (glob)
-  |
-  o  6:acb28cd497b7@default(draft) add 1
-  |
-  o  5:0b9e50c35132@default(draft) add 3
-  |
-  o  4:ce341209337f@default(draft) add 4
-  |
-  | o  1:73d38bb17fd7@default(draft) add 1
-  |/
-  o  0:8685c6d34325@default(draft) add 0
-  
-
-Test fold
-
-  $ rm *.orig
-  $ hg fold
-  abort: no revisions specified
-  [255]
-  $ hg fold --from
-  abort: no revisions specified
-  [255]
-  $ hg fold .
-  abort: must specify either --from or --exact
-  [255]
-  $ hg fold --from . --exact
-  abort: cannot use both --from and --exact
-  [255]
-  $ hg fold --from .
-  single revision specified, nothing to fold
-  [1]
-  $ hg fold 0::10 --rev 1 --exact
-  abort: cannot fold non-linear revisions (multiple heads given)
-  [255]
-  $ hg fold -r 4 -r 6 --exact
-  abort: cannot fold non-linear revisions (multiple roots given)
-  [255]
-  $ hg fold --from 10 1
-  abort: cannot fold non-linear revisions
-  (given revisions are unrelated to parent of working directory)
-  [255]
-  $ hg fold --exact -r "4 and not 4"
-  abort: specified revisions evaluate to an empty set
-  (use different revision arguments)
-  [255]
-  $ hg phase --public 0
-  $ hg fold --from -r 0
-  abort: cannot fold public revisions
-  [255]
-  $ hg fold --from -r 5
-  3 changesets folded
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg fold --from 6 # want to run hg fold 6
-  abort: hidden revision '6'!
-  (use --hidden to access hidden revisions; successor: af636757ce3b)
-  [255]
-  $ hg log -r 11 --template '{desc}\n'
-  add 3
-  
-  
-  add 1
-  
-  
-  conflict
-  $ hg debugrebuildstate
-  $ hg st
-
-Test fold with wc parent is not the head of the folded revision
-
-  $ hg up 4
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg fold --rev 4::11 --user victor --exact
-  2 changesets folded
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ glog
-  @  12:d26d339c513f@default(draft) add 4
-  |
-  | o  1:73d38bb17fd7@default(draft) add 1
-  |/
-  o  0:8685c6d34325@default(public) add 0
-  
-  $ hg log --template '{rev}: {author}\n'
-  12: victor
-  1: test
-  0: test
-  $ hg log -r 12 --template '{desc}\n'
-  add 4
-  
-  
-  add 3
-  
-  
-  add 1
-  
-  
-  conflict
-  $ hg debugrebuildstate
-  $ hg st
-
-Test olog
-
-  $ hg olog | head -n 10 # hg touch makes the output unstable (fix it with devel option for more stable touch)
-  @    d26d339c513f (12) add 4
-  |\
-  x |    af636757ce3b (11) add 3
-  |\ \     rewritten by test (*) as d26d339c513f (glob)
-  | | |
-  | \ \
-  | |\ \
-  | | | x  ce341209337f (4) add 4
-  | | |      rewritten by test (*) as d26d339c513f (glob)
-  | | |
-
-Test obsstore stat
-
-  $ hg debugobsstorestat
-  markers total:                     10
-      for known precursors:          10
-      with parents data:              0
-  markers with no successors:         0
-                1 successors:        10
-                2 successors:         0
-      more than 2 successors:         0
-      available  keys:
-                 user:               10
-  disconnected clusters:              1
-          any known node:             1
-          smallest length:           10
-          longer length:             10
-          median length:             10
-          mean length:               10
-      using parents data:             1
-          any known node:             1
-          smallest length:           10
-          longer length:             10
-          median length:             10
-          mean length:               10
-
-
-Test evolving renames
-
-  $ hg up null
-  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
-  $ echo a > a
-  $ hg ci -Am a
-  adding a
-  created new head
-  $ echo b > b
-  $ hg ci -Am b
-  adding b
-  $ hg mv a c
-  $ hg ci -m c
-  $ hg prune .^
-  1 changesets pruned
-  1 new unstable changesets
-  $ hg stab --any
-  move:[15] c
-  atop:[13] a
-  working directory is now at 3742bde73477
-  $ hg st -C --change=tip
-  A c
-    a
-  R a
-
-Test fold with commit messages
-
-  $ cd ../work
-  $ hg fold --from .^ --message "Folding with custom commit message"
-  2 changesets folded
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ glog
-  @  16:98cb758db56d@default(draft) Folding with custom commit message
-  |
-  o  13:0a2f9b959bb4@default(draft) dansk!
-  |
-  o  7:99833d22b0c6@default(public) another feature (child of ba0ec09b1bab)
-  |
-  o  6:ba0ec09b1bab@default(public) a nifty feature
+  $ hg log -G
+  @  8	: F - test
   |
-  o  0:e55e0562ee93@default(public) base
-  
-  $ cat > commit-message <<EOF
-  > A longer
-  >                   commit message
-  > EOF
-
-  $ hg fold --from .^ --logfile commit-message
-  2 changesets folded
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg qlog
-  17 - a00182c58888 A longer
-                    commit message (draft)
-  7 - 99833d22b0c6 another feature (child of ba0ec09b1bab) (public)
-  6 - ba0ec09b1bab a nifty feature (public)
-  0 - e55e0562ee93 base (public)
-
-  $ cd ..
-
-Test branch preservation:
-===========================
-
-  $ hg init evolving-branch
-  $ cd evolving-branch
-  $ touch a
-  $ hg add a
-  $ hg ci -m 'a0'
-  $ echo 1 > a
-  $ hg ci -m 'a1'
-  $ echo 2 > a
-  $ hg ci -m 'a2'
-  $ echo 3 > a
-  $ hg ci -m 'a3'
-
-  $ hg log -G --template '{rev} [{branch}] {desc|firstline}\n'
-  @  3 [default] a3
-  |
-  o  2 [default] a2
-  |
-  o  1 [default] a1
-  |
-  o  0 [default] a0
-  
-
-branch change propagated
-
-  $ hg up 'desc(a2)'
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg branch mybranch
-  marked working directory as branch mybranch
-  (branches are permanent and global, did you want a bookmark?)
-  $ hg amend
-  1 new unstable changesets
-
-  $ hg evolve
-  move:[3] a3
-  atop:[5] a2
-  working directory is now at 7c5649f73d11
-
-  $ hg log -G --template '{rev} [{branch}] {desc|firstline}\n'
-  @  6 [mybranch] a3
-  |
-  o  5 [mybranch] a2
-  |
-  o  1 [default] a1
+  o  7	: E - test
   |
-  o  0 [default] a0
-  
-
-branch change preserved
-
-  $ hg up 'desc(a1)'
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg amend -m 'a1_'
-  2 new unstable changesets
-  $ hg evolve
-  move:[5] a2
-  atop:[7] a1_
-  working directory is now at eb07e22a0e63
-  $ hg evolve
-  move:[6] a3
-  atop:[8] a2
-  working directory is now at 777c26ca5e78
-  $ hg log -G --template '{rev} [{branch}] {desc|firstline}\n'
-  @  9 [mybranch] a3
-  |
-  o  8 [mybranch] a2
-  |
-  o  7 [default] a1_
-  |
-  o  0 [default] a0
-  
-
-Evolve from the middle of a stack pick the right changesets.
-
-  $ hg up 7
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg ci --amend -m 'a1__'
-  2 new unstable changesets
-
-  $ hg up 8
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg log -G --template '{rev} [{branch}] {desc|firstline}\n'
-  o  10 [default] a1__
-  |
-  | o  9 [mybranch] a3
-  | |
-  | @  8 [mybranch] a2
-  | |
-  | x  7 [default] a1_
-  |/
-  o  0 [default] a0
-  
-  $ hg evolve
-  nothing to evolve on current working copy parent
-  (2 other unstable in the repository, do you want --any or --rev)
-  [2]
-
-
-Evolve disables active bookmarks.
-
-  $ hg up 10
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg bookmark testbookmark
-  $ ls .hg/bookmarks*
-  .hg/bookmarks
-  .hg/bookmarks.* (glob)
-  $ hg evolve
-  move:[8] a2
-  atop:[10] a1__
-  (leaving bookmark testbookmark)
-  working directory is now at d952e93add6f
-  $ ls .hg/bookmarks*
-  .hg/bookmarks
-
-Possibility to select what trouble to solve first, asking for bumped before
-divergent
-  $ hg up 10
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg revert -r 11 --all
-  reverting a
-  $ hg log -G --template '{rev} [{branch}] {desc|firstline}\n'
-  o  11 [mybranch] a2
-  |
-  @  10 [default] a1__
-  |
-  | o  9 [mybranch] a3
-  | |
-  | x  8 [mybranch] a2
-  | |
-  | x  7 [default] a1_
+  | o  6	: D2 - test
   |/
-  o  0 [default] a0
-  
-  $ echo "hello world" > newfile
-  $ hg add newfile
-  $ hg commit -m "add new file bumped" -o 11
-  $ hg phase --public --hidden 11
-  1 new bumped changesets
-  $ hg glog
-  @  12	: add new file bumped - test
-  |
-  | o  11	: a2 - test
-  |/
-  o  10	testbookmark: a1__ - test
-  |
-  | o  9	: a3 - test
-  | |
-  | x  8	: a2 - test
-  | |
-  | x  7	: a1_ - test
-  |/
-  o  0	: a0 - test
-  
-
-Now we have a bumped and an unstable changeset, we solve the bumped first
-normally the unstable changeset would be solve first
-
-  $ hg glog
-  @  12	: add new file bumped - test
-  |
-  | o  11	: a2 - test
-  |/
-  o  10	testbookmark: a1__ - test
-  |
-  | o  9	: a3 - test
-  | |
-  | x  8	: a2 - test
-  | |
-  | x  7	: a1_ - test
-  |/
-  o  0	: a0 - test
-  
-  $ hg evolve -r 12 --bumped
-  recreate:[12] add new file bumped
-  atop:[11] a2
-  computing new diff
-  committed as f15d32934071
-  working directory is now at f15d32934071
-  $ hg evolve --any
-  move:[9] a3
-  atop:[13] bumped update to d952e93add6f:
-  working directory is now at cce26b684bfe
-Check that we can resolve troubles in a revset with more than one commit
-  $ hg up 14 -C
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ mkcommit gg
-  $ hg up 14 
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ mkcommit gh
-  created new head
-  $ hg up 14 
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ printf "newline\nnewline\n" >> a
-  $ hg glog
-  o  16	: add gh - test
-  |
-  | o  15	: add gg - test
-  |/
-  @  14	: a3 - test
-  |
-  o  13	: bumped update to d952e93add6f: - test
-  |
-  o  11	: a2 - test
-  |
-  o  10	testbookmark: a1__ - test
-  |
-  o  0	: a0 - test
-  
-  $ hg amend
-  2 new unstable changesets
-  $ hg glog
-  @  18	: a3 - test
+  o  3	: C - test
   |
-  | o  16	: add gh - test
-  | |
-  | | o  15	: add gg - test
-  | |/
-  | x  14	: a3 - test
+  | o  2	: B - test
   |/
-  o  13	: bumped update to d952e93add6f: - test
-  |
-  o  11	: a2 - test
-  |
-  o  10	testbookmark: a1__ - test
-  |
-  o  0	: a0 - test
-  
-
-Evolving an empty revset should do nothing
-  $ hg evolve --rev "16 and 15"
-  set of specified revisions is empty
-  [1]
-
-  $ hg evolve --rev "14::" --bumped
-  no bumped changesets in specified revisions
-  (do you want to use --unstable)
-  [2]
-  $ hg evolve --rev "14::" --unstable
-  move:[15] add gg
-  atop:[18] a3
-  move:[16] add gh
-  atop:[18] a3
-  working directory is now at e02107f98737
-  $ hg glog
-  @  20	: add gh - test
-  |
-  | o  19	: add gg - test
-  |/
-  o  18	: a3 - test
-  |
-  o  13	: bumped update to d952e93add6f: - test
-  |
-  o  11	: a2 - test
+  o  1	: A - test
   |
-  o  10	testbookmark: a1__ - test
-  |
-  o  0	: a0 - test
-  
-Enabling commands selectively, no command enabled, next and fold and unknown
-  $ cat >> $HGRCPATH <<EOF
-  > [experimental]
-  > evolution=createmarkers
-  > EOF
-  $ hg next
-  hg: unknown command 'next'
-  Mercurial Distributed SCM
-  
-  basic commands:
-  
-   add           add the specified files on the next commit
-   annotate      show changeset information by line for each file
-   clone         make a copy of an existing repository
-   commit        commit the specified files or all outstanding changes
-   diff          diff repository (or selected files)
-   export        dump the header and diffs for one or more changesets
-   forget        forget the specified files on the next commit
-   init          create a new repository in the given directory
-   log           show revision history of entire repository or files
-   merge         merge another revision into working directory
-   pull          pull changes from the specified source
-   push          push changes to the specified destination
-   remove        remove the specified files on the next commit
-   serve         start stand-alone webserver
-   status        show changed files in the working directory
-   summary       summarize working directory state
-   update        update working directory (or switch revisions)
-  
-  (use 'hg help' for the full list of commands or 'hg -v' for details)
-  [255]
-  $ hg fold
-  hg: unknown command 'fold'
-  Mercurial Distributed SCM
-  
-  basic commands:
+  o  0	: ROOT - test
   
-   add           add the specified files on the next commit
-   annotate      show changeset information by line for each file
-   clone         make a copy of an existing repository
-   commit        commit the specified files or all outstanding changes
-   diff          diff repository (or selected files)
-   export        dump the header and diffs for one or more changesets
-   forget        forget the specified files on the next commit
-   init          create a new repository in the given directory
-   log           show revision history of entire repository or files
-   merge         merge another revision into working directory
-   pull          pull changes from the specified source
-   push          push changes to the specified destination
-   remove        remove the specified files on the next commit
-   serve         start stand-alone webserver
-   status        show changed files in the working directory
-   summary       summarize working directory state
-   update        update working directory (or switch revisions)
-  
-  (use 'hg help' for the full list of commands or 'hg -v' for details)
-  [255]
-Enabling commands selectively, only fold enabled, next is still unknown
-  $ cat >> $HGRCPATH <<EOF
-  > [experimental]
-  > evolution=createmarkers
-  > evolutioncommands=fold
-  > EOF
-  $ hg fold
-  abort: no revisions specified
-  [255]
-  $ hg next
-  hg: unknown command 'next'
-  Mercurial Distributed SCM
-  
-  basic commands:
-  
-   add           add the specified files on the next commit
-   annotate      show changeset information by line for each file
-   clone         make a copy of an existing repository
-   commit        commit the specified files or all outstanding changes
-   diff          diff repository (or selected files)
-   export        dump the header and diffs for one or more changesets
-   fold          fold multiple revisions into a single one
-   forget        forget the specified files on the next commit
-   init          create a new repository in the given directory
-   log           show revision history of entire repository or files
-   merge         merge another revision into working directory
-   pull          pull changes from the specified source
-   push          push changes to the specified destination
-   remove        remove the specified files on the next commit
-   serve         start stand-alone webserver
-   status        show changed files in the working directory
-   summary       summarize working directory state
-   update        update working directory (or switch revisions)
-  
-  (use 'hg help' for the full list of commands or 'hg -v' for details)
-  [255]
-
-Restore all of the evolution features
-
-  $ cat >> $HGRCPATH <<EOF
-  > [experimental]
-  > evolution=all
-  > EOF
-
-Check hg evolve --rev on singled out commit
-  $ hg up 19 -C
-  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ mkcommit j1
-  $ mkcommit j2
-  $ mkcommit j3
-  $ hg up .^^
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ echo "hello" > j4
-  $ hg add j4
-  $ hg amend
-  2 new unstable changesets
-  $ glog -r "18::"
-  @  25:8dc373be86d9@default(draft) add j1
-  |
-  | o  23:d7eadcf6eccd@default(draft) add j3
-  | |
-  | o  22:2223ea564144@default(draft) add j2
-  | |
-  | x  21:48490698b269@default(draft) add j1
-  |/
-  | o  20:e02107f98737@default(draft) add gh
-  | |
-  o |  19:24e63b319adf@default(draft) add gg
-  |/
-  o  18:edc3c9de504e@default(draft) a3
-  |
-  ~
-
-  $ hg evolve --rev 23 --any
-  abort: cannot specify both "--rev" and "--any"
-  [255]
-  $ hg evolve --rev 23
-  cannot solve instability of d7eadcf6eccd, skipping
-
-Check that uncommit respects the allowunstable option
-With only createmarkers we can only uncommit on a head
-  $ cat >> $HGRCPATH <<EOF
-  > [experimental]
-  > evolution=createmarkers, allnewcommands
-  > EOF
-  $ hg up 8dc373be86d9^
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg uncommit --all
-  abort: cannot uncommit in the middle of a stack
-  [255]
-  $ hg up 8dc373be86d9
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg uncommit --all
-  new changeset is empty
-  (use 'hg prune .' to remove it)
-  $ glog -r "18::"
-  @  26:044804d0c10d@default(draft) add j1
-  |
-  | o  23:d7eadcf6eccd@default(draft) add j3
-  | |
-  | o  22:2223ea564144@default(draft) add j2
-  | |
-  | x  21:48490698b269@default(draft) add j1
-  |/
-  | o  20:e02107f98737@default(draft) add gh
-  | |
-  o |  19:24e63b319adf@default(draft) add gg
-  |/
-  o  18:edc3c9de504e@default(draft) a3
-  |
-  ~
-
-Check that prune respects the allowunstable option
-  $ hg up -C .
+  $ hg update --clean .
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg up 20
-  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg evolve --all
-  nothing to evolve on current working copy parent
-  (2 other unstable in the repository, do you want --any or --rev)
-  [2]
-  $ hg evolve --all --any
-  move:[22] add j2
-  atop:[26] add j1
-  move:[23] add j3
-  atop:[27] add j2
-  working directory is now at c9a20e2d74aa
-  $ glog -r "18::"
-  @  28:c9a20e2d74aa@default(draft) add j3
-  |
-  o  27:b0e3066231e2@default(draft) add j2
-  |
-  o  26:044804d0c10d@default(draft) add j1
-  |
-  | o  20:e02107f98737@default(draft) add gh
-  | |
-  o |  19:24e63b319adf@default(draft) add gg
-  |/
-  o  18:edc3c9de504e@default(draft) a3
-  |
-  ~
-  $ hg up 19
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ mkcommit c5_
-  created new head
-  $ hg prune '26 + 27'
-  abort: cannot prune in the middle of a stack
-  (new unstable changesets are not allowed)
-  [255]
-  $ hg prune '19::28'
-  abort: cannot prune in the middle of a stack
-  (new unstable changesets are not allowed)
-  [255]
-  $ hg prune '26::'
-  3 changesets pruned
-  $ glog -r "18::"
-  @  29:2251801b6c91@default(draft) add c5_
-  |
-  | o  20:e02107f98737@default(draft) add gh
-  | |
-  o |  19:24e63b319adf@default(draft) add gg
-  |/
-  o  18:edc3c9de504e@default(draft) a3
-  |
-  ~
-
-Check that fold respects the allowunstable option
-  $ hg up edc3c9de504e
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ mkcommit unstableifparentisfolded
-  created new head
-  $ glog -r "18::"
-  @  30:68330ac625b8@default(draft) add unstableifparentisfolded
-  |
-  | o  29:2251801b6c91@default(draft) add c5_
-  | |
-  +---o  20:e02107f98737@default(draft) add gh
-  | |
-  | o  19:24e63b319adf@default(draft) add gg
-  |/
-  o  18:edc3c9de504e@default(draft) a3
-  |
-  ~
-
-  $ hg fold --exact "19 + 18"
-  abort: cannot fold chain not ending with a head or with branching
-  (new unstable changesets are not allowed)
-  [255]
-  $ hg fold --exact "18::29"
-  abort: cannot fold chain not ending with a head or with branching
-  (new unstable changesets are not allowed)
-  [255]
-  $ hg fold --exact "19::"
-  2 changesets folded
-
-Check that evolve shows error while handling split commits
---------------------------------------
-
-  $ cat >> $HGRCPATH <<EOF
-  > [experimental]
-  > evolution=all
-  > EOF
-
-  $ glog -r "18::"
-  o  31:580886d07058@default(draft) add gg
-  |
-  | @  30:68330ac625b8@default(draft) add unstableifparentisfolded
-  |/
-  | o  20:e02107f98737@default(draft) add gh
-  |/
-  o  18:edc3c9de504e@default(draft) a3
-  |
-  ~
-
-Create a split commit
-  $ printf "oo" > oo;
-  $ printf "pp" > pp;
-  $ hg add oo pp
-  $ hg commit -m "oo+pp"
-  $ mkcommit uu
-  $ hg up 30
-  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
-  $ printf "oo" > oo;
-  $ hg add oo
-  $ hg commit -m "_oo"
-  created new head
-  $ printf "pp" > pp;
-  $ hg add pp
-  $ hg commit -m "_pp"
-  $ hg prune --succ "desc(_oo) + desc(_pp)" -r "desc('oo+pp')" --split
-  1 changesets pruned
-  1 new unstable changesets
-  $ glog -r "18::"
-  @  35:7a555adf2b4a@default(draft) _pp
-  |
-  o  34:2be4d2d5bf34@default(draft) _oo
-  |
-  | o  33:53f0c003e03e@default(draft) add uu
-  | |
-  | x  32:1bf2152f4f82@default(draft) oo+pp
-  |/
-  | o  31:580886d07058@default(draft) add gg
-  | |
-  o |  30:68330ac625b8@default(draft) add unstableifparentisfolded
-  |/
-  | o  20:e02107f98737@default(draft) add gh
-  |/
-  o  18:edc3c9de504e@default(draft) a3
-  |
-  ~
-  $ hg evolve --rev "18::"
-  move:[33] add uu
-  atop:[35] _pp
-  working directory is now at 43c3f5ef149f
-
-
-Check that dirstate changes are kept at failure for conflicts (issue4966)
-----------------------------------------
-
-  $ echo "will be amended" > newfile
-  $ hg commit -m "will be amended"
-  $ hg parents
-  37	: will be amended - test
-
-  $ echo "will be evolved safely" >> a
-  $ hg commit -m "will be evolved safely"
-
-  $ echo "will cause conflict at evolve" > newfile
-  $ echo "newly added" > newlyadded
-  $ hg add newlyadded
-  $ hg commit -m "will cause conflict at evolve"
-
-  $ hg update -q 37
-  $ echo "amended" > newfile
-  $ hg amend -m "amended"
-  2 new unstable changesets
-
-  $ hg evolve --rev "37::"
-  move:[38] will be evolved safely
-  atop:[41] amended
-  move:[39] will cause conflict at evolve
-  atop:[42] will be evolved safely
-  merging newfile
-  warning: conflicts while merging newfile! (edit, then use 'hg resolve --mark')
-  evolve failed!
-  fix conflict and run 'hg evolve --continue' or use 'hg update -C .' to abort
-  abort: unresolved merge conflicts (see hg help resolve)
-  [255]
-
-  $ glog -r "36::" --hidden
-  @  42:c904da5245b0@default(draft) will be evolved safely
-  |
-  o  41:34ae045ec400@default(draft) amended
-  |
-  | x  40:e88bee38ffc2@default(draft) temporary amend commit for 36030b147271
-  | |
-  | | o  39:02e943732647@default(draft) will cause conflict at evolve
-  | | |
-  | | x  38:f8e30e9317aa@default(draft) will be evolved safely
-  | |/
-  | x  37:36030b147271@default(draft) will be amended
-  |/
-  o  36:43c3f5ef149f@default(draft) add uu
-  |
-  ~
-
-  $ hg status newlyadded
-  A newlyadded
-
-hg metaedit
------------
-
-  $ hg update --clean .
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ rm newlyadded
   $ hg metaedit -r 0
   abort: cannot edit commit information for public revisions
   [255]
@@ -1507,28 +106,31 @@
   $ hg metaedit -r 0 --fold
   abort: cannot fold public revisions
   [255]
-  $ hg metaedit '36 + 42' --fold
+  $ hg metaedit 'desc(C) + desc(F)' --fold
   abort: cannot fold non-linear revisions (multiple roots given)
   [255]
-  $ hg metaedit '36::39 + 41' --fold
+  $ hg metaedit "desc(C)::desc(D2) + desc(E)" --fold
   abort: cannot fold non-linear revisions (multiple heads given)
   [255]
 check that metaedit respects allowunstable
   $ hg metaedit '.^' --config 'experimental.evolution=createmarkers, allnewcommands'
   abort: cannot edit commit information in the middle of a stack
-  (c904da5245b0 will become unstable and new unstable changes are not allowed)
+  (587528abfffe will become unstable and new unstable changes are not allowed)
   [255]
-  $ hg metaedit '18::20' --fold --config 'experimental.evolution=createmarkers, allnewcommands'
+  $ hg metaedit 'desc(A)::desc(B)' --fold --config 'experimental.evolution=createmarkers, allnewcommands'
   abort: cannot fold chain not ending with a head or with branching
   (new unstable changesets are not allowed)
   [255]
   $ hg metaedit --user foobar
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
-  42: test
-  43: foobar
+  $ hg log --template '{rev}: {author}\n' -r 'desc(F):' --hidden
+  5: test
+  6: test
+  7: test
+  8: test
+  9: foobar
   $ hg log --template '{rev}: {author}\n' -r .
-  43: foobar
+  9: foobar
 
 TODO: support this
   $ hg metaedit '.^::.'
@@ -1537,13 +139,13 @@
 
   $ HGEDITOR=cat hg metaedit '.^::.' --fold
   HG: This is a fold of 2 changesets.
-  HG: Commit message of changeset 41.
+  HG: Commit message of changeset 7.
   
-  amended
+  E
   
-  HG: Commit message of changeset 43.
+  HG: Commit message of changeset 9.
   
-  will be evolved safely
+  F
   
   
   
@@ -1552,22 +154,22 @@
   HG: --
   HG: user: test
   HG: branch 'default'
-  HG: changed a
-  HG: changed newfile
+  HG: added E
+  HG: added F
   2 changesets folded
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ glog -r .
-  @  44:41bf1183869c@default(draft) amended
+  @  10:a08d35fd7d9d@default(draft) E
   |
   ~
 
 no new commit is created here because the date is the same
   $ HGEDITOR=cat hg metaedit
-  amended
+  E
   
   
-  will be evolved safely
+  F
   
   
   HG: Enter commit message.  Lines beginning with 'HG:' are removed.
@@ -1575,14 +177,14 @@
   HG: --
   HG: user: test
   HG: branch 'default'
-  HG: changed a
-  HG: changed newfile
+  HG: added E
+  HG: added F
   nothing changed
 
   $ glog -r '.^::.'
-  @  44:41bf1183869c@default(draft) amended
+  @  10:a08d35fd7d9d@default(draft) E
   |
-  o  36:43c3f5ef149f@default(draft) add uu
+  o  3:3260958f1169@default(draft) C
   |
   ~
 
@@ -1593,22 +195,21 @@
   $ hg metaedit --config defaults.metaedit= --config devel.default-date="42 0"
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n'
-  36: add uu
-  45: amended
+  3: C
+  11: E
 
   $ hg up .^
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg metaedit --user foobar2 45
-  $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
-  42: test
-  43: foobar
-  44: test
-  45: test
-  46: foobar2
-  $ hg diff -r 45 -r 46 --hidden
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg metaedit --user foobar2 tip
+  $ hg log --template '{rev}: {author}\n' -r "user(foobar):" --hidden
+  9: foobar
+  10: test
+  11: test
+  12: foobar2
+  $ hg diff -r "10" -r "11" --hidden
 
 'fold' one commit
-  $ hg metaedit 39 --fold --user foobar3
+  $ hg metaedit "desc(D2)" --fold --user foobar3
   1 changesets folded
-  $ hg log -r 47 --template '{rev}: {author}\n'
-  47: foobar3
+  $ hg log -r "tip" --template '{rev}: {author}\n'
+  13: foobar3
--- a/tests/test-obsolete-push.t
+++ b/tests/test-obsolete-push.t
@@ -56,7 +56,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  1 new obsolescence markers
+  2 new obsolescence markers
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd strict-publish-client
--- a/tests/test-prune.t
+++ b/tests/test-prune.t
@@ -354,6 +354,17 @@
       more than 2 successors:         0
       available  keys:
                  user:                7
+  marker size:
+      format v1:
+          smallest length:           69
+          longer length:             69
+          median length:             69
+          mean length:               69
+      format v0:
+          smallest length:          * (glob)
+          longer length:            * (glob)
+          median length:            * (glob)
+          mean length:              * (glob)
   disconnected clusters:              7
           any known node:             7
           smallest length:            1
copy from tests/test-stablesort.t
copy to tests/test-stablesort-criss-cross.t
--- a/tests/test-stablesort.t
+++ b/tests/test-stablesort-criss-cross.t
@@ -12,8 +12,6 @@
   > showsort = debugstablesort --template="{node|short}\n"
   > EOF
 
-
-
   $ checktopo () {
   >     seen='null';
   >     for node in `hg showsort --rev "$1"`; do
@@ -23,451 +21,6 @@
   >     done;
   > }
 
-Basic tests
-===========
-(no criss cross merge)
-
-Smoke tests
------------
-
-Starts with a "simple case"
-
-  $ hg init repo_A
-  $ cd repo_A
-  $ hg debugbuilddag '
-  > ..:g   # 2 nodes, tagged "g"
-  > <2.:h   # another node base one -2 -> 0, tagged "h"
-  > *1/2:m # merge -1 and -2 (1, 2), tagged "m"
-  > <2+2:i # 2 nodes based on -2, tag head as "i"
-  > .:c    # 1 node tagged "c"
-  > <m+3:a # 3 nodes base on the "m" tag
-  > <2.:b  # 1 node based on -2; tagged "b"
-  > <m+2:d # 2 nodes from "m" tagged "d"
-  > <2.:e  # 1 node based on -2, tagged "e"
-  > <m+1:f # 1 node based on "m" tagged "f"
-  > <i/f   # merge "i" and "f"
-  > '
-  $ hg log -G
-  o    15 1d8d22637c2d r15 tip
-  |\
-  | o  14 43227190fef8 r14 f
-  | |
-  | | o  13 b4594d867745 r13 e
-  | | |
-  | | | o  12 e46a4836065c r12 d
-  | | |/
-  | | o  11 bab5d5bf48bd r11
-  | |/
-  | | o  10 ff43616e5d0f r10 b
-  | | |
-  | | | o  9 dcbb326fdec2 r9 a
-  | | |/
-  | | o  8 d62d843c9a01 r8
-  | | |
-  | | o  7 e7d9710d9fc6 r7
-  | |/
-  +---o  6 2702dd0c91e7 r6 c
-  | |
-  o |  5 f0f3ef9a6cd5 r5 i
-  | |
-  o |  4 4c748ffd1a46 r4
-  | |
-  | o  3 2b6d669947cd r3 m
-  |/|
-  o |  2 fa942426a6fd r2 h
-  | |
-  | o  1 66f7d451a68b r1 g
-  |/
-  o  0 1ea73414a91b r0
-  
-  $ hg showsort --rev 'all()' --traceback
-  1ea73414a91b
-  66f7d451a68b
-  fa942426a6fd
-  2b6d669947cd
-  43227190fef8
-  bab5d5bf48bd
-  b4594d867745
-  e46a4836065c
-  e7d9710d9fc6
-  d62d843c9a01
-  dcbb326fdec2
-  ff43616e5d0f
-  4c748ffd1a46
-  f0f3ef9a6cd5
-  1d8d22637c2d
-  2702dd0c91e7
-
-Verify the topological order
-----------------------------
-
-Check we we did not issued a node before on ancestor
-
-output of log should be empty
-
-  $ checktopo 'all()'
-  === checking 1ea73414a91b ===
-  === checking 66f7d451a68b ===
-  === checking fa942426a6fd ===
-  === checking 2b6d669947cd ===
-  === checking 43227190fef8 ===
-  === checking bab5d5bf48bd ===
-  === checking b4594d867745 ===
-  === checking e46a4836065c ===
-  === checking e7d9710d9fc6 ===
-  === checking d62d843c9a01 ===
-  === checking dcbb326fdec2 ===
-  === checking ff43616e5d0f ===
-  === checking 4c748ffd1a46 ===
-  === checking f0f3ef9a6cd5 ===
-  === checking 1d8d22637c2d ===
-  === checking 2702dd0c91e7 ===
-
-Check stability
-===============
-
-have repo with changesets in orders
-
-  $ cd ..
-  $ hg -R repo_A log -G > A.log
-  $ hg clone repo_A repo_B --rev 5
-  adding changesets
-  adding manifests
-  adding file changes
-  added 4 changesets with 0 changes to 0 files
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg -R repo_B pull --rev 13
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 4 changesets with 0 changes to 0 files (+1 heads)
-  (run 'hg heads' to see heads, 'hg merge' to merge)
-  $ hg -R repo_B pull --rev 14
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 0 changes to 0 files (+1 heads)
-  (run 'hg heads .' to see heads, 'hg merge' to merge)
-  $ hg -R repo_B pull
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 7 changesets with 0 changes to 0 files (+3 heads)
-  (run 'hg heads .' to see heads, 'hg merge' to merge)
-  $ hg -R repo_B log -G
-  o    15 1d8d22637c2d r15 tip
-  |\
-  | | o  14 e46a4836065c r12
-  | | |
-  | | | o  13 ff43616e5d0f r10
-  | | | |
-  | | | | o  12 dcbb326fdec2 r9
-  | | | |/
-  | | | o  11 d62d843c9a01 r8
-  | | | |
-  | | | o  10 e7d9710d9fc6 r7
-  | | | |
-  +-------o  9 2702dd0c91e7 r6
-  | | | |
-  | o---+  8 43227190fef8 r14
-  |  / /
-  | +---o  7 b4594d867745 r13
-  | | |
-  | o |  6 bab5d5bf48bd r11
-  | |/
-  | o    5 2b6d669947cd r3
-  | |\
-  | | o  4 66f7d451a68b r1
-  | | |
-  @ | |  3 f0f3ef9a6cd5 r5
-  | | |
-  o | |  2 4c748ffd1a46 r4
-  |/ /
-  o /  1 fa942426a6fd r2
-  |/
-  o  0 1ea73414a91b r0
-  
-  $ hg -R repo_B log -G > B.log
-
-  $ hg clone repo_A repo_C --rev 10
-  adding changesets
-  adding manifests
-  adding file changes
-  added 7 changesets with 0 changes to 0 files
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg -R repo_C pull --rev 12
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 0 changes to 0 files (+1 heads)
-  (run 'hg heads' to see heads, 'hg merge' to merge)
-  $ hg -R repo_C pull --rev 15
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 4 changesets with 0 changes to 0 files (+1 heads)
-  (run 'hg heads .' to see heads, 'hg merge' to merge)
-  $ hg -R repo_C pull
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 3 changesets with 0 changes to 0 files (+3 heads)
-  (run 'hg heads .' to see heads, 'hg merge' to merge)
-  $ hg -R repo_C log -G
-  o  15 b4594d867745 r13 tip
-  |
-  | o  14 dcbb326fdec2 r9
-  | |
-  | | o  13 2702dd0c91e7 r6
-  | | |
-  | | | o  12 1d8d22637c2d r15
-  | | |/|
-  | | | o  11 43227190fef8 r14
-  | | | |
-  | | o |  10 f0f3ef9a6cd5 r5
-  | | | |
-  | | o |  9 4c748ffd1a46 r4
-  | | | |
-  +-------o  8 e46a4836065c r12
-  | | | |
-  o-----+  7 bab5d5bf48bd r11
-   / / /
-  +-----@  6 ff43616e5d0f r10
-  | | |
-  o | |  5 d62d843c9a01 r8
-  | | |
-  o---+  4 e7d9710d9fc6 r7
-   / /
-  | o  3 2b6d669947cd r3
-  |/|
-  o |  2 fa942426a6fd r2
-  | |
-  | o  1 66f7d451a68b r1
-  |/
-  o  0 1ea73414a91b r0
-  
-  $ hg -R repo_C log -G > C.log
-
-  $ hg clone repo_A repo_D --rev 2
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 0 changes to 0 files
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg -R repo_D pull --rev 10
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 5 changesets with 0 changes to 0 files
-  (run 'hg update' to get a working copy)
-  $ hg -R repo_D pull --rev 15
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 4 changesets with 0 changes to 0 files (+1 heads)
-  (run 'hg heads' to see heads, 'hg merge' to merge)
-  $ hg -R repo_D pull
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 5 changesets with 0 changes to 0 files (+4 heads)
-  (run 'hg heads .' to see heads, 'hg merge' to merge)
-  $ hg -R repo_D log -G
-  o  15 b4594d867745 r13 tip
-  |
-  | o  14 e46a4836065c r12
-  |/
-  o  13 bab5d5bf48bd r11
-  |
-  | o  12 dcbb326fdec2 r9
-  | |
-  | | o  11 2702dd0c91e7 r6
-  | | |
-  | | | o  10 1d8d22637c2d r15
-  | | |/|
-  +-----o  9 43227190fef8 r14
-  | | |
-  | | o  8 f0f3ef9a6cd5 r5
-  | | |
-  | | o  7 4c748ffd1a46 r4
-  | | |
-  | +---o  6 ff43616e5d0f r10
-  | | |
-  | o |  5 d62d843c9a01 r8
-  | | |
-  | o |  4 e7d9710d9fc6 r7
-  |/ /
-  o |  3 2b6d669947cd r3
-  |\|
-  o |  2 66f7d451a68b r1
-  | |
-  | @  1 fa942426a6fd r2
-  |/
-  o  0 1ea73414a91b r0
-  
-  $ hg -R repo_D log -G > D.log
-
-check the log output are different
-
-  $ python "$RUNTESTDIR/md5sum.py" *.log
-  55919ebc9c02f28070cf3255b1690f8c  A.log
-  c6244b76a60d0707767dc71780e544f3  B.log
-  4d8b08b8c50ecbdd2460a62e5852d84d  C.log
-  0f327003593b50b9591bea8ee28acb81  D.log
-
-bug stable ordering should be identical
----------------------------------------
-
-  $ repos="A B C D "
-
-for 'all()'
-
-  $ for x in $repos; do
-  >     echo $x
-  >     hg -R repo_$x showsort --rev 'all()' > ${x}.all.order;
-  > done
-  A
-  B
-  C
-  D
-
-  $ python "$RUNTESTDIR/md5sum.py" *.all.order
-  0c6b2e6f15249c0359b0f93e28c5bd1c  A.all.order
-  0c6b2e6f15249c0359b0f93e28c5bd1c  B.all.order
-  0c6b2e6f15249c0359b0f93e28c5bd1c  C.all.order
-  0c6b2e6f15249c0359b0f93e28c5bd1c  D.all.order
-
-one specific head
-
-  $ for x in $repos; do
-  >     hg -R repo_$x showsort --rev 'b4594d867745' > ${x}.b4594d867745.order;
-  > done
-
-  $ python "$RUNTESTDIR/md5sum.py" *.b4594d867745.order
-  5c40900a22008f24eab8dfe2f30ad79f  A.b4594d867745.order
-  5c40900a22008f24eab8dfe2f30ad79f  B.b4594d867745.order
-  5c40900a22008f24eab8dfe2f30ad79f  C.b4594d867745.order
-  5c40900a22008f24eab8dfe2f30ad79f  D.b4594d867745.order
-
-one secific heads, that is a merge
-
-  $ for x in $repos; do
-  >     hg -R repo_$x showsort --rev '1d8d22637c2d' > ${x}.1d8d22637c2d.order;
-  > done
-
-  $ python "$RUNTESTDIR/md5sum.py" *.1d8d22637c2d.order
-  77dc20a6f86db9103df8edaae9ad2754  A.1d8d22637c2d.order
-  77dc20a6f86db9103df8edaae9ad2754  B.1d8d22637c2d.order
-  77dc20a6f86db9103df8edaae9ad2754  C.1d8d22637c2d.order
-  77dc20a6f86db9103df8edaae9ad2754  D.1d8d22637c2d.order
-
-changeset that are not heads
-
-  $ for x in $repos; do
-  >     hg -R repo_$x showsort --rev 'e7d9710d9fc6+43227190fef8' > ${x}.non-heads.order;
-  > done
-
-  $ python "$RUNTESTDIR/md5sum.py" *.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  A.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  B.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  C.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  D.non-heads.order
-
-Check with different subset
-
-  $ hg clone repo_A repo_E --rev "43227190fef8"
-  adding changesets
-  adding manifests
-  adding file changes
-  added 5 changesets with 0 changes to 0 files
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg -R repo_E pull --rev e7d9710d9fc6
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 0 changes to 0 files (+1 heads)
-  (run 'hg heads' to see heads, 'hg merge' to merge)
-
-  $ hg clone repo_A repo_F --rev "1d8d22637c2d"
-  adding changesets
-  adding manifests
-  adding file changes
-  added 8 changesets with 0 changes to 0 files
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg -R repo_F pull --rev d62d843c9a01
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 0 changes to 0 files (+1 heads)
-  (run 'hg heads' to see heads, 'hg merge' to merge)
-
-  $ hg clone repo_A repo_G --rev "e7d9710d9fc6"
-  adding changesets
-  adding manifests
-  adding file changes
-  added 5 changesets with 0 changes to 0 files
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg -R repo_G pull --rev 43227190fef8
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 0 changes to 0 files (+1 heads)
-  (run 'hg heads' to see heads, 'hg merge' to merge)
-  $ hg -R repo_G pull --rev 2702dd0c91e7
-  pulling from $TESTTMP/repo_A (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 3 changesets with 0 changes to 0 files (+1 heads)
-  (run 'hg heads .' to see heads, 'hg merge' to merge)
-
-  $ for x in E F G; do
-  >     hg -R repo_$x showsort --rev 'e7d9710d9fc6+43227190fef8' > ${x}.non-heads.order;
-  > done
-
-  $ python "$RUNTESTDIR/md5sum.py" *.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  A.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  B.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  C.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  D.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  E.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  F.non-heads.order
-  94e0ea8cdade135dabde4ec5e9954329  G.non-heads.order
-
-Multiple recursions
-===================
-
   $ cat << EOF >> random_rev.py
   > import random
   > import sys
@@ -478,152 +31,6 @@
   >     print(x + random.randint(0, var))
   > EOF
 
-  $ hg init recursion_A
-  $ cd recursion_A
-  $ hg debugbuilddag '
-  > .:base
-  > +3:A
-  > <base.:B
-  > +2/A:C
-  > <A+2:D
-  > <B./D:E
-  > +3:F
-  > <C+3/E
-  > +2
-  > '
-  $ hg log -G
-  o  20 160a7a0adbf4 r20 tip
-  |
-  o  19 1c645e73dbc6 r19
-  |
-  o    18 0496f0a6a143 r18
-  |\
-  | o  17 d64d500024d1 r17
-  | |
-  | o  16 4dbf739dd63f r16
-  | |
-  | o  15 9fff0871d230 r15
-  | |
-  | | o  14 4bbfc6078919 r14 F
-  | | |
-  | | o  13 013b27f11536 r13
-  | | |
-  +---o  12 a66b68853635 r12
-  | |
-  o |    11 001194dd78d5 r11 E
-  |\ \
-  | o |  10 6ee532b68cfa r10
-  | | |
-  o | |  9 529dfc5bb875 r9 D
-  | | |
-  o | |  8 abf57d94268b r8
-  | | |
-  +---o  7 5f18015f9110 r7 C
-  | | |
-  | | o  6 a2f58e9c1e56 r6
-  | | |
-  | | o  5 3a367db1fabc r5
-  | |/
-  | o  4 e7bd5218ca15 r4 B
-  | |
-  o |  3 2dc09a01254d r3 A
-  | |
-  o |  2 01241442b3c2 r2
-  | |
-  o |  1 66f7d451a68b r1
-  |/
-  o  0 1ea73414a91b r0 base
-  
-  $ hg showsort --rev 'all()'
-  1ea73414a91b
-  66f7d451a68b
-  01241442b3c2
-  2dc09a01254d
-  abf57d94268b
-  529dfc5bb875
-  e7bd5218ca15
-  3a367db1fabc
-  a2f58e9c1e56
-  5f18015f9110
-  9fff0871d230
-  4dbf739dd63f
-  d64d500024d1
-  6ee532b68cfa
-  001194dd78d5
-  0496f0a6a143
-  1c645e73dbc6
-  160a7a0adbf4
-  a66b68853635
-  013b27f11536
-  4bbfc6078919
-  $ checktopo 'all()'
-  === checking 1ea73414a91b ===
-  === checking 66f7d451a68b ===
-  === checking 01241442b3c2 ===
-  === checking 2dc09a01254d ===
-  === checking abf57d94268b ===
-  === checking 529dfc5bb875 ===
-  === checking e7bd5218ca15 ===
-  === checking 3a367db1fabc ===
-  === checking a2f58e9c1e56 ===
-  === checking 5f18015f9110 ===
-  === checking 9fff0871d230 ===
-  === checking 4dbf739dd63f ===
-  === checking d64d500024d1 ===
-  === checking 6ee532b68cfa ===
-  === checking 001194dd78d5 ===
-  === checking 0496f0a6a143 ===
-  === checking 1c645e73dbc6 ===
-  === checking 160a7a0adbf4 ===
-  === checking a66b68853635 ===
-  === checking 013b27f11536 ===
-  === checking 4bbfc6078919 ===
-  $ hg showsort --rev 'all()' > ../multiple.source.order
-  $ hg log -r tip
-  20 160a7a0adbf4 r20 tip
-  $ cd ..
-
-  $ hg clone recursion_A recursion_random --rev 0
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 0 changes to 0 files
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cd recursion_random
-  $ for x in `python ../random_rev.py 15 5`; do
-  >   # using python to benefit from the random seed
-  >   hg pull -r $x --quiet
-  > done;
-  $ hg pull --quiet
-  $ hg showsort --rev 'all()' > ../multiple.random.order
-  $ python "$RUNTESTDIR/md5sum.py" ../multiple.*.order
-  6ff802a0a5f0a3ddd82b25f860238fbd  ../multiple.random.order
-  6ff802a0a5f0a3ddd82b25f860238fbd  ../multiple.source.order
-  $ hg showsort --rev 'all()'
-  1ea73414a91b
-  66f7d451a68b
-  01241442b3c2
-  2dc09a01254d
-  abf57d94268b
-  529dfc5bb875
-  e7bd5218ca15
-  3a367db1fabc
-  a2f58e9c1e56
-  5f18015f9110
-  9fff0871d230
-  4dbf739dd63f
-  d64d500024d1
-  6ee532b68cfa
-  001194dd78d5
-  0496f0a6a143
-  1c645e73dbc6
-  160a7a0adbf4
-  a66b68853635
-  013b27f11536
-  4bbfc6078919
-  $ cd ..
-
 Check criss cross merge
 =======================
 
@@ -1311,48 +718,3 @@
   8ae32c3ed670
   84d6ec6a8e21
   01f771406cab
-
-
-Test behavior with oedipus merges
-=================================
-
-  $ hg init recursion_oedipus
-  $ cd recursion_oedipus
-  $ echo base > base
-  $ hg add base
-  $ hg ci -m base
-  $ hg branch foo
-  marked working directory as branch foo
-  (branches are permanent and global, did you want a bookmark?)
-  $ echo foo1 > foo1
-  $ hg add foo1
-  $ hg ci -m foo1
-  $ echo foo2 > foo2
-  $ hg add foo2
-  $ hg ci -m foo2
-  $ hg up default
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg merge foo
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ hg ci -m oedipus_merge
-  $ echo default1 > default1
-  $ hg add default1
-  $ hg ci -m default1
-  $ hg log -G 
-  @  4 7f2454f6b04f default1 tip
-  |
-  o    3 ed776db7ed63 oedipus_merge
-  |\
-  | o  2 0dedbcd995b6 foo2
-  | |
-  | o  1 47da0f2c25e2 foo1
-  |/
-  o  0 d20a80d4def3 base
-  
-  $ hg showsort --rev '.'
-  d20a80d4def3
-  47da0f2c25e2
-  0dedbcd995b6
-  ed776db7ed63
-  7f2454f6b04f
--- a/tests/test-stablesort.t
+++ b/tests/test-stablesort.t
@@ -23,6 +23,16 @@
   >     done;
   > }
 
+  $ cat << EOF >> random_rev.py
+  > import random
+  > import sys
+  > 
+  > loop = int(sys.argv[1])
+  > var = int(sys.argv[2])
+  > for x in range(loop):
+  >     print(x + random.randint(0, var))
+  > EOF
+
 Basic tests
 ===========
 (no criss cross merge)
@@ -468,16 +478,6 @@
 Multiple recursions
 ===================
 
-  $ cat << EOF >> random_rev.py
-  > import random
-  > import sys
-  > 
-  > loop = int(sys.argv[1])
-  > var = int(sys.argv[2])
-  > for x in range(loop):
-  >     print(x + random.randint(0, var))
-  > EOF
-
   $ hg init recursion_A
   $ cd recursion_A
   $ hg debugbuilddag '
@@ -624,694 +624,6 @@
   4bbfc6078919
   $ cd ..
 
-Check criss cross merge
-=======================
-
-  $ hg init crisscross_A
-  $ cd crisscross_A
-  $ hg debugbuilddag '
-  > ...:base         # create some base
-  > # criss cross #1: simple
-  > +3:AbaseA      # "A" branch for CC "A"
-  > <base+2:AbaseB # "B" branch for CC "B"
-  > <AbaseA/AbaseB:AmergeA
-  > <AbaseB/AbaseA:AmergeB
-  > <AmergeA/AmergeB:Afinal
-  > # criss cross #2:multiple closes ones
-  > .:BbaseA
-  > <AmergeB:BbaseB
-  > <BbaseA/BbaseB:BmergeA
-  > <BbaseB/BbaseA:BmergeB
-  > <BmergeA/BmergeB:BmergeC
-  > <BmergeB/BmergeA:BmergeD
-  > <BmergeC/BmergeD:Bfinal
-  > # criss cross #2:many branches
-  > <Bfinal.:CbaseA
-  > <Bfinal+2:CbaseB
-  > <Bfinal.:CbaseC
-  > <Bfinal+5:CbaseD
-  > <Bfinal.:CbaseE
-  > <CbaseA/CbaseB+7:CmergeA
-  > <CbaseA/CbaseC:CmergeB
-  > <CbaseA/CbaseD.:CmergeC
-  > <CbaseA/CbaseE:CmergeD
-  > <CbaseB/CbaseA+2:CmergeE
-  > <CbaseB/CbaseC:CmergeF
-  > <CbaseB/CbaseD.:CmergeG
-  > <CbaseB/CbaseE:CmergeH
-  > <CbaseC/CbaseA.:CmergeI
-  > <CbaseC/CbaseB:CmergeJ
-  > <CbaseC/CbaseD+5:CmergeK
-  > <CbaseC/CbaseE+2:CmergeL
-  > <CbaseD/CbaseA:CmergeM
-  > <CbaseD/CbaseB...:CmergeN
-  > <CbaseD/CbaseC:CmergeO
-  > <CbaseD/CbaseE:CmergeP
-  > <CbaseE/CbaseA:CmergeQ
-  > <CbaseE/CbaseB..:CmergeR
-  > <CbaseE/CbaseC.:CmergeS
-  > <CbaseE/CbaseD:CmergeT
-  > <CmergeA/CmergeG:CmergeWA
-  > <CmergeB/CmergeF:CmergeWB
-  > <CmergeC/CmergeE:CmergeWC
-  > <CmergeD/CmergeH:CmergeWD
-  > <CmergeT/CmergeI:CmergeWE
-  > <CmergeS/CmergeJ:CmergeWF
-  > <CmergeR/CmergeK:CmergeWG
-  > <CmergeQ/CmergeL:CmergeWH
-  > <CmergeP/CmergeM:CmergeWI
-  > <CmergeO/CmergeN:CmergeWJ
-  > <CmergeO/CmergeN:CmergeWK
-  > <CmergeWA/CmergeWG:CmergeXA
-  > <CmergeWB/CmergeWH:CmergeXB
-  > <CmergeWC/CmergeWI:CmergeXC
-  > <CmergeWD/CmergeWJ:CmergeXD
-  > <CmergeWE/CmergeWK:CmergeXE
-  > <CmergeWF/CmergeWA:CmergeXF
-  > <CmergeXA/CmergeXF:CmergeYA
-  > <CmergeXB/CmergeXE:CmergeYB
-  > <CmergeXC/CmergeXD:CmergeYC
-  > <CmergeYA/CmergeYB:CmergeZA
-  > <CmergeYC/CmergeYB:CmergeZB
-  > <CmergeZA/CmergeZB:Cfinal
-  > '
-  $ hg log -G
-  o    94 01f771406cab r94 Cfinal tip
-  |\
-  | o    93 84d6ec6a8e21 r93 CmergeZB
-  | |\
-  o | |  92 721ba7c5f4ff r92 CmergeZA
-  |\| |
-  | | o    91 8ae32c3ed670 r91 CmergeYC
-  | | |\
-  | o \ \    90 8b79544bb56d r90 CmergeYB
-  | |\ \ \
-  o \ \ \ \    89 041e1188f5f1 r89 CmergeYA
-  |\ \ \ \ \
-  | o \ \ \ \    88 2472d042ec95 r88 CmergeXF
-  | |\ \ \ \ \
-  | | | | o \ \    87 c7d3029bf731 r87 CmergeXE
-  | | | | |\ \ \
-  | | | | | | | o    86 469c700e9ed8 r86 CmergeXD
-  | | | | | | | |\
-  | | | | | | o \ \    85 28be96b80dc1 r85 CmergeXC
-  | | | | | | |\ \ \
-  | | | o \ \ \ \ \ \    84 dbde319d43a3 r84 CmergeXB
-  | | | |\ \ \ \ \ \ \
-  o | | | | | | | | | |  83 b3cf98c3d587 r83 CmergeXA
-  |\| | | | | | | | | |
-  | | | | | | o | | | |    82 1da228afcf06 r82 CmergeWK
-  | | | | | | |\ \ \ \ \
-  | | | | | | +-+-------o  81 0bab31f71a21 r81 CmergeWJ
-  | | | | | | | | | | |
-  | | | | | | | | | o |    80 cd345198cf12 r80 CmergeWI
-  | | | | | | | | | |\ \
-  | | | | o \ \ \ \ \ \ \    79 82238c0bc950 r79 CmergeWH
-  | | | | |\ \ \ \ \ \ \ \
-  o \ \ \ \ \ \ \ \ \ \ \ \    78 89a0fe204177 r78 CmergeWG
-  |\ \ \ \ \ \ \ \ \ \ \ \ \
-  | | | o \ \ \ \ \ \ \ \ \ \    77 97d19fc5236f r77 CmergeWF
-  | | | |\ \ \ \ \ \ \ \ \ \ \
-  | | | | | | | | o \ \ \ \ \ \    76 37ad3ab0cddf r76 CmergeWE
-  | | | | | | | | |\ \ \ \ \ \ \
-  | | | | | | | | | | | | | | | o    75 790cdfecd168 r75 CmergeWD
-  | | | | | | | | | | | | | | | |\
-  | | | | | | | | | | | | o \ \ \ \    74 698970a2480b r74 CmergeWC
-  | | | | | | | | | | | | |\ \ \ \ \
-  | | | | | o \ \ \ \ \ \ \ \ \ \ \ \    73 31d7b43cc321 r73 CmergeWB
-  | | | | | |\ \ \ \ \ \ \ \ \ \ \ \ \
-  | | o \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \    72 eed373b0090d r72 CmergeWA
-  | | |\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \
-  | | | | | | | | | | | o \ \ \ \ \ \ \ \    71 4f3b41956174 r71 CmergeT
-  | | | | | | | | | | | |\ \ \ \ \ \ \ \ \
-  | | | | | o | | | | | | | | | | | | | | |  70 c3c7fa726f88 r70 CmergeS
-  | | | | | | | | | | | | | | | | | | | | |
-  | | | | | o-------------+ | | | | | | | |  69 d917f77a6439 r69
-  | | | | | | | | | | | | | | | | | | | | |
-  | o | | | | | | | | | | | | | | | | | | |  68 fac9e582edd1 r68 CmergeR
-  | | | | | | | | | | | | | | | | | | | | |
-  | o | | | | | | | | | | | | | | | | | | |  67 e4cfd6264623 r67
-  | | | | | | | | | | | | | | | | | | | | |
-  | o---------------------+ | | | | | | | |  66 d99e0f7dad5b r66
-  | | | | | | | | | | | | | | | | | | | | |
-  | | | | | | | | | o-----+ | | | | | | | |  65 c713eae2d31f r65 CmergeQ
-  | | | | | | | | | | | | | | | | | | | | |
-  | | | | | | | | | | | +-+-----------o | |  64 b33fd5ad4c0c r64 CmergeP
-  | | | | | | | | | | | | | | | | | |  / /
-  | | | | | +-----------+-----o | | | / /  63 bf6593f7e073 r63 CmergeO
-  | | | | | | | | | | | | | |  / / / / /
-  | | | | | | | | | | | | | o | | | | |  62 3871506da61e r62 CmergeN
-  | | | | | | | | | | | | | | | | | | |
-  | | | | | | | | | | | | | o | | | | |  61 c84da74cf586 r61
-  | | | | | | | | | | | | | | | | | | |
-  | | | | | | | | | | | | | o | | | | |  60 5eec91b12a58 r60
-  | | | | | | | | | | | | | | | | | | |
-  | +-------------------+---o | | | | |  59 0484d39906c8 r59
-  | | | | | | | | | | | | |  / / / / /
-  | | | | | | | | | +---+-------o / /  58 29141354a762 r58 CmergeM
-  | | | | | | | | | | | | | | |  / /
-  | | | | | | | | o | | | | | | | |  57 e7135b665740 r57 CmergeL
-  | | | | | | | | | | | | | | | | |
-  | | | | | | | | o | | | | | | | |  56 c7c1497fc270 r56
-  | | | | | | | | | | | | | | | | |
-  | | | | | +-----o-------+ | | | |  55 76151e8066e1 r55
-  | | | | | | | |  / / / / / / / /
-  o | | | | | | | | | | | | | | |  54 9a67238ad1c4 r54 CmergeK
-  | | | | | | | | | | | | | | | |
-  o | | | | | | | | | | | | | | |  53 c37e7cd9f2bd r53
-  | | | | | | | | | | | | | | | |
-  o | | | | | | | | | | | | | | |  52 0d153e3ad632 r52
-  | | | | | | | | | | | | | | | |
-  o | | | | | | | | | | | | | | |  51 97ac964e34b7 r51
-  | | | | | | | | | | | | | | | |
-  o | | | | | | | | | | | | | | |  50 900dd066a072 r50
-  | | | | | | | | | | | | | | | |
-  o---------+---------+ | | | | |  49 673f5499c8c2 r49
-   / / / / / / / / / / / / / / /
-  +-----o / / / / / / / / / / /  48 8ecb28746ec4 r48 CmergeJ
-  | | | |/ / / / / / / / / / /
-  | | | | | | | o | | | | | |  47 d6c9e2d27f14 r47 CmergeI
-  | | | | | | | | | | | | | |
-  | | | +-------o | | | | | |  46 bfcfd9a61e84 r46
-  | | | | | | |/ / / / / / /
-  +---------------+-------o  45 40553f55397e r45 CmergeH
-  | | | | | | | | | | | |
-  | | o | | | | | | | | |  44 d94da36be176 r44 CmergeG
-  | | | | | | | | | | | |
-  +---o---------+ | | | |  43 4b39f229a0ce r43
-  | |  / / / / / / / / /
-  +---+---o / / / / / /  42 43fc0b77ff07 r42 CmergeF
-  | | | |  / / / / / /
-  | | | | | | | | o |  41 88eace5ce682 r41 CmergeE
-  | | | | | | | | | |
-  | | | | | | | | o |  40 d928b4e8a515 r40
-  | | | | | | | | | |
-  +-------+-------o |  39 88714f4125cb r39
-  | | | | | | | |  /
-  | | | | +---+---o  38 e3e6738c56ce r38 CmergeD
-  | | | | | | | |
-  | | | | | | | o  37 32b41ca704e1 r37 CmergeC
-  | | | | | | | |
-  | | | | +-+---o  36 01e29e20ea3f r36
-  | | | | | | |
-  | | | o | | |  35 1f4a19f83a29 r35 CmergeB
-  | | |/|/ / /
-  | o | | | |  34 722d1b8b8942 r34 CmergeA
-  | | | | | |
-  | o | | | |  33 47c836a1f13e r33
-  | | | | | |
-  | o | | | |  32 2ea3fbf151b5 r32
-  | | | | | |
-  | o | | | |  31 0c3f2ba59eb7 r31
-  | | | | | |
-  | o | | | |  30 f3441cd3e664 r30
-  | | | | | |
-  | o | | | |  29 b9c3aa92fba5 r29
-  | | | | | |
-  | o | | | |  28 3bdb00d5c818 r28
-  | | | | | |
-  | o---+ | |  27 2bd677d0f13a r27
-  |/ / / / /
-  | | | | o  26 de05b9c29ec7 r26 CbaseE
-  | | | | |
-  | | | o |  25 ad46a4a0fc10 r25 CbaseD
-  | | | | |
-  | | | o |  24 a457569c5306 r24
-  | | | | |
-  | | | o |  23 f2bdd828a3aa r23
-  | | | | |
-  | | | o |  22 5ce588c2b7c5 r22
-  | | | | |
-  | | | o |  21 17b6e6bac221 r21
-  | | | |/
-  | o---+  20 b115c694654e r20 CbaseC
-  |  / /
-  o | |  19 884936b34999 r19 CbaseB
-  | | |
-  o---+  18 9729470d9329 r18
-   / /
-  o /  17 4f5078f7da8a r17 CbaseA
-  |/
-  o    16 3e1560705803 r16 Bfinal
-  |\
-  | o    15 55bf3fdb634f r15 BmergeD
-  | |\
-  o---+  14 39bab1cb1cbe r14 BmergeC
-  |/ /
-  | o    13 f7c6e7bfbcd0 r13 BmergeB
-  | |\
-  o---+  12 26f59ee8b1d7 r12 BmergeA
-  |/ /
-  | o  11 3e2da24aee59 r11 BbaseA
-  | |
-  | o  10 5ba9a53052ed r10 Afinal
-  |/|
-  o |    9 07c648efceeb r9 AmergeB BbaseB
-  |\ \
-  +---o  8 c81423bf5a24 r8 AmergeA
-  | |/
-  | o  7 65eb34ffc3a8 r7 AbaseB
-  | |
-  | o  6 0c1445abb33d r6
-  | |
-  o |  5 c8d03c1b5e94 r5 AbaseA
-  | |
-  o |  4 bebd167eb94d r4
-  | |
-  o |  3 2dc09a01254d r3
-  |/
-  o  2 01241442b3c2 r2 base
-  |
-  o  1 66f7d451a68b r1
-  |
-  o  0 1ea73414a91b r0
-  
-
-Basic check
------------
-
-  $ hg showsort --rev 'Afinal'
-  1ea73414a91b
-  66f7d451a68b
-  01241442b3c2
-  0c1445abb33d
-  65eb34ffc3a8
-  2dc09a01254d
-  bebd167eb94d
-  c8d03c1b5e94
-  07c648efceeb
-  c81423bf5a24
-  5ba9a53052ed
-  $ checktopo Afinal
-  === checking 1ea73414a91b ===
-  === checking 66f7d451a68b ===
-  === checking 01241442b3c2 ===
-  === checking 0c1445abb33d ===
-  === checking 65eb34ffc3a8 ===
-  === checking 2dc09a01254d ===
-  === checking bebd167eb94d ===
-  === checking c8d03c1b5e94 ===
-  === checking 07c648efceeb ===
-  === checking c81423bf5a24 ===
-  === checking 5ba9a53052ed ===
-  $ hg showsort --rev 'AmergeA'
-  1ea73414a91b
-  66f7d451a68b
-  01241442b3c2
-  0c1445abb33d
-  65eb34ffc3a8
-  2dc09a01254d
-  bebd167eb94d
-  c8d03c1b5e94
-  c81423bf5a24
-  $ checktopo AmergeA
-  === checking 1ea73414a91b ===
-  === checking 66f7d451a68b ===
-  === checking 01241442b3c2 ===
-  === checking 0c1445abb33d ===
-  === checking 65eb34ffc3a8 ===
-  === checking 2dc09a01254d ===
-  === checking bebd167eb94d ===
-  === checking c8d03c1b5e94 ===
-  === checking c81423bf5a24 ===
-  $ hg showsort --rev 'AmergeB'
-  1ea73414a91b
-  66f7d451a68b
-  01241442b3c2
-  0c1445abb33d
-  65eb34ffc3a8
-  2dc09a01254d
-  bebd167eb94d
-  c8d03c1b5e94
-  07c648efceeb
-  $ checktopo AmergeB
-  === checking 1ea73414a91b ===
-  === checking 66f7d451a68b ===
-  === checking 01241442b3c2 ===
-  === checking 0c1445abb33d ===
-  === checking 65eb34ffc3a8 ===
-  === checking 2dc09a01254d ===
-  === checking bebd167eb94d ===
-  === checking c8d03c1b5e94 ===
-  === checking 07c648efceeb ===
-
-close criss cross
-  $ hg showsort --rev 'Bfinal'
-  1ea73414a91b
-  66f7d451a68b
-  01241442b3c2
-  0c1445abb33d
-  65eb34ffc3a8
-  2dc09a01254d
-  bebd167eb94d
-  c8d03c1b5e94
-  07c648efceeb
-  c81423bf5a24
-  5ba9a53052ed
-  3e2da24aee59
-  26f59ee8b1d7
-  f7c6e7bfbcd0
-  39bab1cb1cbe
-  55bf3fdb634f
-  3e1560705803
-  $ checktopo Bfinal
-  === checking 1ea73414a91b ===
-  === checking 66f7d451a68b ===
-  === checking 01241442b3c2 ===
-  === checking 0c1445abb33d ===
-  === checking 65eb34ffc3a8 ===
-  === checking 2dc09a01254d ===
-  === checking bebd167eb94d ===
-  === checking c8d03c1b5e94 ===
-  === checking 07c648efceeb ===
-  === checking c81423bf5a24 ===
-  === checking 5ba9a53052ed ===
-  === checking 3e2da24aee59 ===
-  === checking 26f59ee8b1d7 ===
-  === checking f7c6e7bfbcd0 ===
-  === checking 39bab1cb1cbe ===
-  === checking 55bf3fdb634f ===
-  === checking 3e1560705803 ===
-
-many branches criss cross
-
-  $ hg showsort --rev 'Cfinal'
-  1ea73414a91b
-  66f7d451a68b
-  01241442b3c2
-  0c1445abb33d
-  65eb34ffc3a8
-  2dc09a01254d
-  bebd167eb94d
-  c8d03c1b5e94
-  07c648efceeb
-  c81423bf5a24
-  5ba9a53052ed
-  3e2da24aee59
-  26f59ee8b1d7
-  f7c6e7bfbcd0
-  39bab1cb1cbe
-  55bf3fdb634f
-  3e1560705803
-  17b6e6bac221
-  5ce588c2b7c5
-  f2bdd828a3aa
-  a457569c5306
-  ad46a4a0fc10
-  4f5078f7da8a
-  01e29e20ea3f
-  32b41ca704e1
-  29141354a762
-  9729470d9329
-  884936b34999
-  0484d39906c8
-  5eec91b12a58
-  c84da74cf586
-  3871506da61e
-  2bd677d0f13a
-  3bdb00d5c818
-  b9c3aa92fba5
-  f3441cd3e664
-  0c3f2ba59eb7
-  2ea3fbf151b5
-  47c836a1f13e
-  722d1b8b8942
-  4b39f229a0ce
-  d94da36be176
-  eed373b0090d
-  88714f4125cb
-  d928b4e8a515
-  88eace5ce682
-  698970a2480b
-  b115c694654e
-  1f4a19f83a29
-  43fc0b77ff07
-  31d7b43cc321
-  673f5499c8c2
-  900dd066a072
-  97ac964e34b7
-  0d153e3ad632
-  c37e7cd9f2bd
-  9a67238ad1c4
-  8ecb28746ec4
-  bf6593f7e073
-  0bab31f71a21
-  1da228afcf06
-  bfcfd9a61e84
-  d6c9e2d27f14
-  de05b9c29ec7
-  40553f55397e
-  4f3b41956174
-  37ad3ab0cddf
-  c7d3029bf731
-  76151e8066e1
-  c7c1497fc270
-  e7135b665740
-  b33fd5ad4c0c
-  cd345198cf12
-  28be96b80dc1
-  c713eae2d31f
-  82238c0bc950
-  dbde319d43a3
-  8b79544bb56d
-  d917f77a6439
-  c3c7fa726f88
-  97d19fc5236f
-  2472d042ec95
-  d99e0f7dad5b
-  e4cfd6264623
-  fac9e582edd1
-  89a0fe204177
-  b3cf98c3d587
-  041e1188f5f1
-  721ba7c5f4ff
-  e3e6738c56ce
-  790cdfecd168
-  469c700e9ed8
-  8ae32c3ed670
-  84d6ec6a8e21
-  01f771406cab
-  $ checktopo Cfinal
-  === checking 1ea73414a91b ===
-  === checking 66f7d451a68b ===
-  === checking 01241442b3c2 ===
-  === checking 0c1445abb33d ===
-  === checking 65eb34ffc3a8 ===
-  === checking 2dc09a01254d ===
-  === checking bebd167eb94d ===
-  === checking c8d03c1b5e94 ===
-  === checking 07c648efceeb ===
-  === checking c81423bf5a24 ===
-  === checking 5ba9a53052ed ===
-  === checking 3e2da24aee59 ===
-  === checking 26f59ee8b1d7 ===
-  === checking f7c6e7bfbcd0 ===
-  === checking 39bab1cb1cbe ===
-  === checking 55bf3fdb634f ===
-  === checking 3e1560705803 ===
-  === checking 17b6e6bac221 ===
-  === checking 5ce588c2b7c5 ===
-  === checking f2bdd828a3aa ===
-  === checking a457569c5306 ===
-  === checking ad46a4a0fc10 ===
-  === checking 4f5078f7da8a ===
-  === checking 01e29e20ea3f ===
-  === checking 32b41ca704e1 ===
-  === checking 29141354a762 ===
-  === checking 9729470d9329 ===
-  === checking 884936b34999 ===
-  === checking 0484d39906c8 ===
-  === checking 5eec91b12a58 ===
-  === checking c84da74cf586 ===
-  === checking 3871506da61e ===
-  === checking 2bd677d0f13a ===
-  === checking 3bdb00d5c818 ===
-  === checking b9c3aa92fba5 ===
-  === checking f3441cd3e664 ===
-  === checking 0c3f2ba59eb7 ===
-  === checking 2ea3fbf151b5 ===
-  === checking 47c836a1f13e ===
-  === checking 722d1b8b8942 ===
-  === checking 4b39f229a0ce ===
-  === checking d94da36be176 ===
-  === checking eed373b0090d ===
-  === checking 88714f4125cb ===
-  === checking d928b4e8a515 ===
-  === checking 88eace5ce682 ===
-  === checking 698970a2480b ===
-  === checking b115c694654e ===
-  === checking 1f4a19f83a29 ===
-  === checking 43fc0b77ff07 ===
-  === checking 31d7b43cc321 ===
-  === checking 673f5499c8c2 ===
-  === checking 900dd066a072 ===
-  === checking 97ac964e34b7 ===
-  === checking 0d153e3ad632 ===
-  === checking c37e7cd9f2bd ===
-  === checking 9a67238ad1c4 ===
-  === checking 8ecb28746ec4 ===
-  === checking bf6593f7e073 ===
-  === checking 0bab31f71a21 ===
-  === checking 1da228afcf06 ===
-  === checking bfcfd9a61e84 ===
-  === checking d6c9e2d27f14 ===
-  === checking de05b9c29ec7 ===
-  === checking 40553f55397e ===
-  === checking 4f3b41956174 ===
-  === checking 37ad3ab0cddf ===
-  === checking c7d3029bf731 ===
-  === checking 76151e8066e1 ===
-  === checking c7c1497fc270 ===
-  === checking e7135b665740 ===
-  === checking b33fd5ad4c0c ===
-  === checking cd345198cf12 ===
-  === checking 28be96b80dc1 ===
-  === checking c713eae2d31f ===
-  === checking 82238c0bc950 ===
-  === checking dbde319d43a3 ===
-  === checking 8b79544bb56d ===
-  === checking d917f77a6439 ===
-  === checking c3c7fa726f88 ===
-  === checking 97d19fc5236f ===
-  === checking 2472d042ec95 ===
-  === checking d99e0f7dad5b ===
-  === checking e4cfd6264623 ===
-  === checking fac9e582edd1 ===
-  === checking 89a0fe204177 ===
-  === checking b3cf98c3d587 ===
-  === checking 041e1188f5f1 ===
-  === checking 721ba7c5f4ff ===
-  === checking e3e6738c56ce ===
-  === checking 790cdfecd168 ===
-  === checking 469c700e9ed8 ===
-  === checking 8ae32c3ed670 ===
-  === checking 84d6ec6a8e21 ===
-  === checking 01f771406cab ===
-
-Test stability of this mess
----------------------------
-
-  $ hg log -r tip
-  94 01f771406cab r94 Cfinal tip
-  $ hg showsort --rev 'all()' > ../crisscross.source.order
-  $ cd ..
-
-  $ hg clone crisscross_A crisscross_random --rev 0
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 0 changes to 0 files
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cd crisscross_random
-  $ for x in `python ../random_rev.py 50 44`; do
-  >   # using python to benefit from the random seed
-  >   hg pull -r $x --quiet
-  > done;
-  $ hg pull --quiet
-
-  $ hg showsort --rev 'all()' > ../crisscross.random.order
-  $ python "$RUNTESTDIR/md5sum.py" ../crisscross.*.order
-  d9aab0d1907d5cf64d205a8b9036e959  ../crisscross.random.order
-  d9aab0d1907d5cf64d205a8b9036e959  ../crisscross.source.order
-  $ diff -u ../crisscross.*.order
-  $ hg showsort --rev 'all()'
-  1ea73414a91b
-  66f7d451a68b
-  01241442b3c2
-  0c1445abb33d
-  65eb34ffc3a8
-  2dc09a01254d
-  bebd167eb94d
-  c8d03c1b5e94
-  07c648efceeb
-  c81423bf5a24
-  5ba9a53052ed
-  3e2da24aee59
-  26f59ee8b1d7
-  f7c6e7bfbcd0
-  39bab1cb1cbe
-  55bf3fdb634f
-  3e1560705803
-  17b6e6bac221
-  5ce588c2b7c5
-  f2bdd828a3aa
-  a457569c5306
-  ad46a4a0fc10
-  4f5078f7da8a
-  01e29e20ea3f
-  32b41ca704e1
-  29141354a762
-  9729470d9329
-  884936b34999
-  0484d39906c8
-  5eec91b12a58
-  c84da74cf586
-  3871506da61e
-  2bd677d0f13a
-  3bdb00d5c818
-  b9c3aa92fba5
-  f3441cd3e664
-  0c3f2ba59eb7
-  2ea3fbf151b5
-  47c836a1f13e
-  722d1b8b8942
-  4b39f229a0ce
-  d94da36be176
-  eed373b0090d
-  88714f4125cb
-  d928b4e8a515
-  88eace5ce682
-  698970a2480b
-  b115c694654e
-  1f4a19f83a29
-  43fc0b77ff07
-  31d7b43cc321
-  673f5499c8c2
-  900dd066a072
-  97ac964e34b7
-  0d153e3ad632
-  c37e7cd9f2bd
-  9a67238ad1c4
-  8ecb28746ec4
-  bf6593f7e073
-  0bab31f71a21
-  1da228afcf06
-  bfcfd9a61e84
-  d6c9e2d27f14
-  de05b9c29ec7
-  40553f55397e
-  4f3b41956174
-  37ad3ab0cddf
-  c7d3029bf731
-  76151e8066e1
-  c7c1497fc270
-  e7135b665740
-  b33fd5ad4c0c
-  cd345198cf12
-  28be96b80dc1
-  c713eae2d31f
-  82238c0bc950
-  dbde319d43a3
-  8b79544bb56d
-  d917f77a6439
-  c3c7fa726f88
-  97d19fc5236f
-  2472d042ec95
-  d99e0f7dad5b
-  e4cfd6264623
-  fac9e582edd1
-  89a0fe204177
-  b3cf98c3d587
-  041e1188f5f1
-  721ba7c5f4ff
-  e3e6738c56ce
-  790cdfecd168
-  469c700e9ed8
-  8ae32c3ed670
-  84d6ec6a8e21
-  01f771406cab
-
 
 Test behavior with oedipus merges
 =================================
--- a/tests/test-wireproto.t
+++ b/tests/test-wireproto.t
@@ -149,4 +149,34 @@
   1 new obsolescence markers
   (run 'hg heads' to see heads)
 
+test discovery avoid exchanging known markers
+
+  $ hg push
+  pushing to ssh://user@dummy/server
+  searching for changes
+  no changes found
+  [1]
+  $ hg -R ../other pull
+  pulling from ssh://user@dummy/server
+  searching for changes
+  no changes found
+
+test discovery can be disabled
+
+  $ hg push --config experimental.evolution.obsdiscovery=no
+  pushing to ssh://user@dummy/server
+  searching for changes
+  (skipping discovery of obsolescence markers, will exchange everything)
+  (controled by 'experimental.evolution.obsdiscovery' configuration)
+  no changes found
+  remote: obsmarker-exchange: 346 bytes received
+  [1]
+  $ hg -R ../other pull --config experimental.evolution.obsdiscovery=no
+  pulling from ssh://user@dummy/server
+  searching for changes
+  no changes found
+  (skipping discovery of obsolescence markers, will exchange everything)
+  (controled by 'experimental.evolution.obsdiscovery' configuration)
+  obsmarker-exchange: 346 bytes received
+
   $ cd ..