merge with stable stable
authorPierre-Yves David <pierre-yves.david@fb.com>
Thu, 25 Jun 2015 16:55:27 -0700
branchstable
changeset 1450 5f6e78aea094
parent 1438 3295353b1363 (current diff)
parent 1449 9be1cadf7a07 (diff)
child 1451 73eb4f33f9dc
child 1452 1bcbd14cf159
merge with stable Test are updated to run with current mercurial stable (3.4.1)
tests/test-amend.t
tests/test-corrupt.t
tests/test-evolve-bumped.t
tests/test-evolve.t
tests/test-inhibit.t
tests/test-obsolete.t
tests/test-sharing.t
tests/test-stabilize-result.t
tests/test-tutorial.t
tests/test-uncommit.t
tests/test-wireproto-bundle1.t
--- a/README	Tue Jun 23 16:50:06 2015 -0700
+++ b/README	Thu Jun 25 16:55:27 2015 -0700
@@ -51,6 +51,25 @@
 Changelog
 =========
 
+5.2.0 --
+
+- evolve: gain a --rev option to control what revisions to evolve (issue4391)
+- evolve: revision are processed in the order they stack on destination
+- evolve: properly skip unstable revision with non-evolved unstable parent
+- evolve: gain --unstable --divergent --bumped flag to select the trouble
+- evolve: issue more useful error message and hint when evolve has nothing to
+          do as invocated.
+- evolve: bare `hg evolve` commands now abort when multiple changesets could be
+          a target.
+- evolve: `hg evolve --all` only evolve changeset that will end up as
+          descendant of the current working copy. The old behavior of `--all`
+          in now in `--all --any`.
+- evolve: add a 'experimental.evolutioncommands' for fine grained commands
+          enabling
+- next/prev: requires `--merge` to move with uncommited changes
+- next: significantly reword error messages
+- next: add a --evolve flag to evolve aspiring children when on a head
+
 5.1.5 -- 2015-06-23
 
 - minor documentation cleanup
@@ -62,6 +81,8 @@
 - prune no longer move the active bookmark for no reason (issue4559)
 - evolve: stop reporting divergence base as missing when we actually have it
 - significant performance improvement for all revsets.
+- provide a hint of how to update to the successor of an obsolete working copy
+  parent.
 
 5.1.4 -- 2015-04-23
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/directaccess.py	Thu Jun 25 16:55:27 2015 -0700
@@ -0,0 +1,175 @@
+""" This extension provides direct access
+It is the ability to refer and access hidden sha in commands provided that you 
+know their value.
+For example hg log -r xxx where xxx is a commit has should work whether xxx is
+hidden or not as we assume that the user knows what he is doing when referring
+to xxx.
+"""
+from mercurial import extensions
+from mercurial import cmdutil
+from mercurial import repoview
+from mercurial import branchmap
+from mercurial import revset
+from mercurial import error
+from mercurial import commands
+from mercurial import hg
+from mercurial.i18n import _
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+# By default, all the commands have directaccess with warnings
+# List of commands that have no directaccess and directaccess with no warning
+directaccesslevel = [
+    # Format:
+    # ('nowarning', 'evolve', 'prune'),
+    # means: no directaccess warning, for the command in evolve named prune
+    #
+    # ('error', None, 'serve'),
+    # means: no directaccess for the command in core named serve
+    #
+    # The list is ordered alphabetically by command names, starting with all
+    # the commands in core then all the commands in the extensions
+    #
+    # The general guideline is:
+    # - remove directaccess warnings for read only commands
+    # - no direct access for commands with consequences outside of the repo
+    # - leave directaccess warnings for all the other commands
+    #
+    ('nowarning', None, 'annotate'),
+    ('nowarning', None, 'archive'),
+    ('nowarning', None, 'bisect'),
+    ('nowarning', None, 'bookmarks'),
+    ('nowarning', None, 'bundle'),
+    ('nowarning', None, 'cat'),
+    ('nowarning', None, 'diff'),
+    ('nowarning', None, 'export'),
+    ('nowarning', None, 'identify'),
+    ('nowarning', None, 'incoming'),
+    ('nowarning', None, 'log'),
+    ('nowarning', None, 'manifest'),
+    ('error', None, 'outgoing'), # confusing if push errors and not outgoing
+    ('error', None, 'push'), # destructive
+    ('nowarning', None, 'revert'),
+    ('error', None, 'serve'),
+    ('nowarning', None, 'tags'),
+    ('nowarning', None, 'unbundle'),
+    ('nowarning', None, 'update'),
+]
+
+def reposetup(ui, repo):
+    repo._explicitaccess = set()
+
+def _computehidden(repo):
+    hidden = repoview.filterrevs(repo, 'visible')
+    cl = repo.changelog
+    dynamic = hidden & repo._explicitaccess
+    if dynamic:
+        blocked = cl.ancestors(dynamic, inclusive=True)
+        hidden = frozenset(r for r in hidden if r not in blocked)
+    return hidden
+
+def setupdirectaccess():
+    """ Add two new filtername that behave like visible to provide direct access
+    and direct access with warning. Wraps the commands to setup direct access """
+    repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden})
+    repoview.filtertable.update({'visible-directaccess-warn': _computehidden})
+    branchmap.subsettable['visible-directaccess-nowarn'] = 'visible'
+    branchmap.subsettable['visible-directaccess-warn'] = 'visible'
+
+    for warn, ext, cmd in directaccesslevel:
+        try:
+            cmdtable = extensions.find(ext).cmdtable if ext else commands.table
+            wrapper = wrapwitherror if warn == 'error' else wrapwithoutwarning
+            extensions.wrapcommand(cmdtable, cmd, wrapper)
+        except (error.UnknownCommand, KeyError):
+            pass
+
+def wrapwitherror(orig, ui, repo, *args, **kwargs):
+    if repo and repo.filtername == 'visible-directaccess-warn':
+        repo = repo.filtered('visible')
+    return orig(ui, repo, *args, **kwargs)
+
+def wrapwithoutwarning(orig, ui, repo, *args, **kwargs):
+    if repo and repo.filtername == 'visible-directaccess-warn':
+        repo = repo.filtered("visible-directaccess-nowarn")
+    return orig(ui, repo, *args, **kwargs)
+
+def uisetup(ui):
+    """ Change ordering of extensions to ensure that directaccess extsetup comes
+    after the one of the extensions in the loadsafter list """
+    loadsafter = ui.configlist('directaccess','loadsafter')
+    order = list(extensions._order)
+    directaccesidx = order.index('directaccess')
+
+    # The min idx for directaccess to load after all the extensions in loadafter
+    minidxdirectaccess = directaccesidx
+
+    for ext in loadsafter:
+        try:
+            minidxdirectaccess = max(minidxdirectaccess, order.index(ext))
+        except ValueError:
+            pass # extension not loaded
+
+    if minidxdirectaccess > directaccesidx:
+        order.insert(minidxdirectaccess + 1, 'directaccess')
+        order.remove('directaccess')
+        extensions._order = order
+
+def _repository(orig, *args, **kwargs):
+    """Make visible-directaccess-warn the default filter for new repos"""
+    repo = orig(*args, **kwargs)
+    return repo.filtered("visible-directaccess-warn")
+
+def extsetup(ui):
+    extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook)
+    extensions.wrapfunction(hg, 'repository', _repository)
+    setupdirectaccess()
+
+def gethashsymbols(tree):
+    # Returns the list of symbols of the tree that look like hashes
+    # for example for the revset 3::abe3ff it will return ('abe3ff')
+    if not tree:
+        return []
+
+    if len(tree) == 2 and tree[0] == "symbol":
+        try:
+            int(tree[1])
+            return []
+        except ValueError as e:
+            return [tree[1]]
+    elif len(tree) == 3:
+        return gethashsymbols(tree[1]) + gethashsymbols(tree[2])
+    else:
+        return []
+
+def _posttreebuilthook(orig, tree, repo):
+    # This is use to enabled direct hash access
+    # We extract the symbols that look like hashes and add them to the
+    # explicitaccess set
+    orig(tree, repo)
+    filternm = ""
+    if repo is not None:
+        filternm = repo.filtername
+    if filternm is not None and filternm.startswith('visible-directaccess'):
+        prelength = len(repo._explicitaccess)
+        accessbefore = set(repo._explicitaccess)
+        repo.symbols = gethashsymbols(tree)
+        cl = repo.unfiltered().changelog
+        for node in repo.symbols:
+            try:
+                node = cl._partialmatch(node)
+            except error.LookupError:
+                node = None
+            if node is not None:
+                rev = cl.rev(node)
+                if rev not in repo.changelog:
+                    repo._explicitaccess.add(rev)
+        if prelength != len(repo._explicitaccess):
+            if repo.filtername != 'visible-directaccess-nowarn':
+                unhiddencommits = repo._explicitaccess - accessbefore
+                repo.ui.warn( _("Warning: accessing hidden changesets %s " 
+                                "for write operation\n") % 
+                                (",".join([str(repo.unfiltered()[l]) 
+                                    for l in unhiddencommits])))
+            repo.invalidatevolatilesets()
--- a/hgext/evolve.py	Tue Jun 23 16:50:06 2015 -0700
+++ b/hgext/evolve.py	Thu Jun 25 16:55:27 2015 -0700
@@ -23,11 +23,48 @@
 testedwith = '3.3.3 3.4.1'
 buglink = 'http://bz.selenic.com/'
 
+
+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:`hg 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 enable 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 sys, os
 import random
 from StringIO import StringIO
 import struct
 import re
+import collections
 import socket
 import errno
 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
@@ -45,6 +82,8 @@
 except (ImportError, AttributeError):
     gboptslist = gboptsmap = None
 
+# Flags for enabling optional parts of evolve
+commandopt = 'allnewcommands'
 
 from mercurial import bookmarks
 from mercurial import cmdutil
@@ -54,6 +93,7 @@
 from mercurial import error
 from mercurial import exchange
 from mercurial import extensions
+from mercurial import help
 from mercurial import httppeer
 from mercurial import hg
 from mercurial import lock as lockmod
@@ -144,8 +184,11 @@
         """
         for cont, funcname, func in self._duckpunchers:
             setattr(cont, funcname, func)
-        for command, wrapper in self._commandwrappers:
-            extensions.wrapcommand(commands.table, command, wrapper)
+        for command, wrapper, opts in self._commandwrappers:
+            entry = extensions.wrapcommand(commands.table, command, wrapper)
+            if opts:
+                for short, long, val, msg in opts:
+                    entry[1].append((short, long, val, msg))
         for cont, funcname, wrapper in self._functionwrappers:
             extensions.wrapfunction(cont, funcname, wrapper)
         for c in self._uicallables:
@@ -166,13 +209,20 @@
             revset.symbols[name] = symbol
         for name, kw in self._templatekws:
             templatekw.keywords[name] = kw
-        for ext, command, wrapper in self._extcommandwrappers:
+        for ext, command, wrapper, opts in self._extcommandwrappers:
             if ext not in knownexts:
-                e = extensions.find(ext)
-                if e is None:
-                    raise util.Abort('extension %s not found' % ext)
+                try:
+                    e = extensions.find(ext)
+                except KeyError:
+                    # Extension isn't enabled, so don't bother trying to wrap
+                    # it.
+                    continue
                 knownexts[ext] = e.cmdtable
-            extensions.wrapcommand(knownexts[ext], commands, wrapper)
+            entry = extensions.wrapcommand(knownexts[ext], command, wrapper)
+            if opts:
+                for short, long, val, msg in opts:
+                    entry[1].append((short, long, val, msg))
+
         for c in self._extcallables:
             c(ui)
 
@@ -260,7 +310,7 @@
             return keyword
         return dec
 
-    def wrapcommand(self, command, extension=None):
+    def wrapcommand(self, command, extension=None, opts=[]):
         """Decorated function is a command wrapper
 
         The name of the command must be given as the decorator argument.
@@ -279,12 +329,16 @@
                 ui.note('Barry!')
                 return orig(ui, repo, *args, **kwargs)
 
+        The `opts` argument allows specifying additional arguments for the
+        command.
+
         """
         def dec(wrapper):
             if extension is None:
-                self._commandwrappers.append((command, wrapper))
+                self._commandwrappers.append((command, wrapper, opts))
             else:
-                self._extcommandwrappers.append((extension, command, wrapper))
+                self._extcommandwrappers.append((extension, command, wrapper,
+                    opts))
             return wrapper
         return dec
 
@@ -330,6 +384,44 @@
 reposetup = eh.final_reposetup
 
 #####################################################################
+### 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)
+
+@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:
+                raise error.Abort(_('ambiguous command specification: "%s" matches %r')
+                                  % (cmd, matchingevolvecommands))
+            else:
+                whitelist.add(matchingevolvecommands[0])
+        for disabledcmd in set(cmdtable) - whitelist:
+            del cmdtable[disabledcmd]
+
+#####################################################################
 ### experimental behavior                                         ###
 #####################################################################
 
@@ -590,9 +682,18 @@
 @eh.wrapcommand("pull")
 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
     """Warn that the working directory parent is an obsolete changeset"""
-    res = origfn(ui, repo, *args, **opts)
-    if repo['.'].obsolete():
-        ui.warn(_('working directory parent is obsolete!\n'))
+    def warnobsolete():
+        if repo['.'].obsolete():
+            ui.warn(_('working directory parent is obsolete!\n'))
+            if (not ui.quiet) and obsolete.isenabled(repo, commandopt):
+                ui.warn(_('(use "hg evolve" to update to its successor)\n'))
+    wlock = None
+    try:
+        wlock = repo.wlock()
+        repo._afterlock(warnobsolete)
+        res = origfn(ui, repo, *args, **opts)
+    finally:
+        lockmod.release(wlock)
     return res
 
 # XXX this could wrap transaction code
@@ -962,7 +1063,11 @@
 
     This function is loosely based on the extensions.wrapcommand function.
     '''
-    aliases, entry = cmdutil.findcmd(newalias, cmdtable)
+    try:
+        aliases, entry = cmdutil.findcmd(newalias, cmdtable)
+    except error.UnknownCommand:
+        # Commands may be disabled
+        return
     for alias, e in cmdtable.iteritems():
         if e is entry:
             break
@@ -1025,6 +1130,23 @@
 
 @command('debugobsstorestat', [], '')
 def cmddebugobsstorestat(ui, repo):
+    def _updateclustermap(nodes, mark, clustersmap):
+        c = (set(nodes), set([mark]))
+        toproceed = set(nodes)
+        while toproceed:
+            n = toproceed.pop()
+            other = clustersmap.get(n)
+            if (other is not None
+                and other is not c):
+                other[0].update(c[0])
+                other[1].update(c[1])
+                for on in c[0]:
+                    if on in toproceed:
+                        continue
+                    clustersmap[on] = other
+                c = other
+            clustersmap[n] = c
+
     """print statistic about obsolescence markers in the repo"""
     store = repo.obsstore
     unfi = repo.unfiltered()
@@ -1054,42 +1176,12 @@
         if parents:
             parentsdata += 1
         # cluster handling
-        nodes = set()
+        nodes = set(mark[1])
         nodes.add(mark[0])
-        nodes.update(mark[1])
-        c = (set(nodes), set([mark]))
-
-        toproceed = set(nodes)
-        while toproceed:
-            n = toproceed.pop()
-            other = clustersmap.get(n)
-            if (other is not None
-                and other is not c):
-                other[0].update(c[0])
-                other[1].update(c[1])
-                for on in c[0]:
-                    if on in toproceed:
-                        continue
-                    clustersmap[on] = other
-                c = other
-            clustersmap[n] = c
+        _updateclustermap(nodes, mark, clustersmap) 
         # same with parent data
         nodes.update(parents)
-        c = (set(nodes), set([mark]))
-        toproceed = set(nodes)
-        while toproceed:
-            n = toproceed.pop()
-            other = pclustersmap.get(n)
-            if (other is not None
-                and other is not c):
-                other[0].update(c[0])
-                other[1].update(c[1])
-                for on in c[0]:
-                    if on in toproceed:
-                        continue
-                    pclustersmap[on] = other
-                c = other
-            pclustersmap[n] = c
+        _updateclustermap(nodes, mark, pclustersmap) 
 
     # freezing the result
     for c in clustersmap.values():
@@ -1143,51 +1235,347 @@
         mean = sum(len(x[1]) for x in allpclusters) // nbcluster
         ui.write('        mean length:        %9i\n' % mean)
 
+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(msg+"\n")
+    if hint:
+        ui.write_err("("+hint+")\n")
+        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 sucessors sets exists
+
+    The object contains the list of successorssets in its 'successorssets'
+    attribute to call to easily recover.
+    """
+
+    def __init__(self, successorssets):
+        self.successorssets = successorssets
+
+def _singlesuccessor(repo, p):
+    """returns p (as rev) if not obsolete or its unique latest successors
+
+    fail if there are no such successor"""
+
+    if not p.obsolete():
+        return p.rev()
+    obs = repo[p]
+    ui = repo.ui
+    newer = 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)"""
+
+    # For each troubled revision we keep track of what instability if any should
+    # be resolved in order to resolve it. Example:
+    # dependencies = {3: [6], 6:[]}
+    # Means that: 6 has no dependency, 3 depends on 6 to be solved
+    dependencies = {}
+    # rdependencies is the inverted dict of dependencies
+    rdependencies = collections.defaultdict(set)
+
+    for r in revs:
+        dependencies[r] = set()
+        for p in repo[r].parents():
+            try:
+                succ = _singlesuccessor(repo, p)
+            except MultipleSuccessorsError, exc:
+                dependencies[r] = exc.successorssets
+                continue
+            if succ in revs:
+                dependencies[r].add(succ)
+                rdependencies[succ].add(r)
+    return dependencies, rdependencies
+
+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(targetcat+'()')
+        if revopt:
+            revs = scmutil.revrange(repo, revopt) & revs
+        elif not anyopt and targetcat == 'unstable':
+            revs = set(_aspiringdescendant(repo, repo.revs('(.::) - obsolete()::')))
+    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
+
+    - Takes revs a list of instable 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 ensure the minimal number of stabilization as we can stabilize each
+    revision on its final, stabilized, destination.
+    """
+    # Step 1: Build the dependency graph
+    dependencies, rdependencies = builddependencies(repo, revs)
+    # Step 2: Build the ordering
+    # Remove the revisions with no dependency(A) and add them to the ordering.
+    # Removing these revisions leads to new revisions with no dependency (the
+    # one depending on A) that we can remove from the dependency graph and add
+    # to the ordering. We progress in a similar fashion until the ordering is
+    # built
+    solvablerevs = collections.deque([r for r in sorted(dependencies.keys())
+                                      if not dependencies[r]])
+    ordering = []
+    while solvablerevs:
+        rev = solvablerevs.popleft()
+        for dependent in rdependencies[rev]:
+            dependencies[dependent].remove(rev)
+            if not dependencies[dependent]:
+                solvablerevs.append(dependent)
+        del dependencies[rev]
+        ordering.append(rev)
+
+    ordering.extend(sorted(dependencies))
+    return ordering
+
 @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'),
-    ('a', 'all', False, 'evolve all troubled changesets in the repo '
-                        '(implies any)'),
+    ('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'),
     ] + mergetoolopts,
     _('[OPTIONS]...'))
 def evolve(ui, repo, **opts):
-    """solve trouble in your repository
+    """solve troubles in your repository
 
     - rebase unstable changesets to make them stable again,
     - create proper diffs from bumped changesets,
-    - merge divergent changesets,
+    - fuse divergent changesets back together,
     - update to a successor if the working directory parent is
       obsolete
 
-    By default a single changeset is evolved for each invocation and only
-    troubled changesets that would evolve as a descendant of the current
-    working directory will be considered. See --all and --any options to change
-    this behavior.
-
-    - For unstable, this means taking the first which could be rebased as a
-      child of the working directory parent revision or one of its descendants
-      and rebasing it.
-
-    - For divergent, this means taking "." if applicable.
-
-    With --any, evolve picks any troubled changeset to repair.
+    If no argument are passed and the current working copy parent is obsolete,
+    :hg:`evolve` will update the working copy to the successors of this working
+    copy parent. If the working copy parent is not obsolete (and still no
+    argument passed) each invocation of :hg:`evolve` will evolve a single
+    unstable changeset, It will only select a changeset to be evolved if it
+    will result in a new children for the current working copy parent or its
+    descendants. The working copy will be updated on the result
+    (this last behavior will most likely to change in the future).
+    You can evolve all the unstable changesets that will be evolved on the
+    parent of the working copy and all its descendants recursively by using
+    :hg:`evolve` --all.
+
+    You can decide to evolve other categories of trouble using the --divergent
+    and --bumped flags. If no other option are specified, this will try to
+    solve the specified troubles for the working copy parent.
+
+    You can also evolve changesets affected by troubles of the selected
+    category using the --rev options. You can pick the next one anywhere in the
+    repo using --any.
+
+    You can evolve all the changesets affected by troubles of the selected
+    category using --all --any.
 
     The working directory is updated to the newly created revision.
     """
 
+    # Options
     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]]
+    targetcat = 'unstable'
+    if 1 < len(specifiedcategories):
+        msg = _('cannot specify more than one trouble category to solve (yet)')
+        raise util.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, 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')
-
-    startnode = repo['.']
-
+    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 util.Abort('cannot specify both "--any" and "--continue"')
@@ -1195,164 +1583,95 @@
             raise util.Abort('cannot specify both "--all" and "--continue"')
         graftcmd = commands.table['graft'][0]
         return graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
-
-    tro = _picknexttroubled(ui, repo, anyopt or allopt)
-    if tro is None:
-        if repo['.'].obsolete():
-            displayer = cmdutil.show_changeset(
-                ui, repo, {'template': shorttemplate})
-            successors = set()
-
-            for successorsset in obsolete.successorssets(repo, repo['.'].node()):
-                for nodeid in successorsset:
-                    successors.add(repo[nodeid])
-
-            if not successors:
-                ui.warn(_('parent is obsolete without successors; ' +
-                          'likely killed\n'))
-                return 2
-
-            elif len(successors) > 1:
-                ui.warn(_('parent is obsolete with multiple successors:\n'))
-
-                for ctx in sorted(successors, key=lambda ctx: ctx.rev()):
-                    displayer.show(ctx)
-
-                return 2
-
-            else:
-                ctx = successors.pop()
-
-                ui.status(_('update:'))
-                if not ui.quiet:
-                    displayer.show(ctx)
-
-                if dryrunopt:
-                    return 0
-                else:
-                    res = hg.update(repo, ctx.rev())
-                    if ctx != startnode:
-                        ui.status(_('working directory is now at %s\n') % ctx)
-                    return res
-
-        troubled = repo.revs('troubled()')
-        if troubled:
-            ui.write_err(_('nothing to evolve here\n'))
-            ui.status(_('(%i troubled changesets, do you want --any ?)\n')
-                      % len(troubled))
-            return 2
-        else:
-            ui.write_err(_('no troubled changesets\n'))
-            return 1
-
-    def progresscb():
-        if allopt:
-            ui.progress('evolve', seen, unit='changesets', total=count)
-    seen = 1
-    count = allopt and _counttroubled(ui, repo) or 1
-
-    while tro is not None:
-        progresscb()
-        wlock = lock = tr = None
-        try:
-            wlock = repo.wlock()
-            lock = repo.lock()
-            tr = repo.transaction("evolve")
-            result = _evolveany(ui, repo, tro, dryrunopt, confirmopt,
-                                progresscb=progresscb)
-            tr.close()
-        finally:
-            lockmod.release(tr, lock, wlock)
-        progresscb()
-        seen += 1
-        if not allopt:
-            if repo['.'] != startnode:
-                ui.status(_('working directory is now at %s\n') % repo['.'])
-            return result
+    cmdutil.bailifchanged(repo)
+
+
+    if revopt and allopt:
+        raise util.Abort('cannot specify both "--rev" and "--all"')
+    if revopt and anyopt:
+        raise util.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()
-        tro = _picknexttroubled(ui, repo, anyopt or allopt)
-
-    if allopt:
-        ui.progress('evolve', None)
-
-    if repo['.'] != startnode:
-        ui.status(_('working directory is now at %s\n') % repo['.'])
-
-
-def _evolveany(ui, repo, tro, dryrunopt, confirmopt, progresscb):
-    repo = repo.unfiltered()
-    tro = repo[tro.rev()]
-    cmdutil.bailifchanged(repo)
-    troubles = tro.troubles()
-    if 'unstable' in troubles:
-        return _solveunstable(ui, repo, tro, dryrunopt, confirmopt, progresscb)
-    elif 'bumped' in troubles:
-        return _solvebumped(ui, repo, tro, dryrunopt, confirmopt, progresscb)
-    elif 'divergent' in troubles:
-        return _solvedivergent(ui, repo, tro, dryrunopt, confirmopt,
-                               progresscb)
-    else:
-        assert False  # WHAT? unknown troubles
-
-def _counttroubled(ui, repo):
-    """Count the amount of troubled changesets"""
-    troubled = set()
-    troubled.update(getrevs(repo, 'unstable'))
-    troubled.update(getrevs(repo, 'bumped'))
-    troubled.update(getrevs(repo, 'divergent'))
-    return len(troubled)
-
-def _picknexttroubled(ui, repo, pickany=False, progresscb=None):
-    """Pick a the next trouble changeset to solve"""
-    if progresscb: progresscb()
-    tro = _stabilizableunstable(repo, repo['.'])
-    if tro is None:
-        wdp = repo['.']
-        if 'divergent' in wdp.troubles():
-            tro = wdp
-    if tro is None and pickany:
-        troubled = list(repo.set('unstable()'))
-        if not troubled:
-            troubled = list(repo.set('bumped()'))
-        if not troubled:
-            troubled = list(repo.set('divergent()'))
-        if troubled:
-            tro = troubled[0]
-
-    return tro
-
-def _stabilizableunstable(repo, pctx):
-    """Return a changectx for an unstable changeset which can be
-    stabilized on top of pctx or one of its descendants. None if none
-    can be found.
-    """
-    def selfanddescendants(repo, pctx):
-        yield pctx
-        for prec in repo.set('allprecursors(%d)', pctx):
-            yield prec
-        for ctx in pctx.descendants():
-            yield ctx
-            for prec in repo.set('allprecursors(%d)', ctx):
-                yield prec
-
-    # Look for an unstable which can be stabilized as a child of
-    # node. The unstable must be a child of one of node predecessors.
-    directdesc = set([pctx.rev()])
-    for ctx in selfanddescendants(repo, pctx):
-        for child in ctx.children():
-            if ctx.rev() in directdesc and not child.obsolete():
-                directdesc.add(child.rev())
-            elif child.unstable():
-                return child
-    return None
+        _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 a unstable changeset"""
     obs = orig.parents()[0]
-    if not obs.obsolete():
+    if not obs.obsolete() and len(orig.parents()) == 2:
         obs = orig.parents()[1] # second parent is obsolete ?
-    assert obs.obsolete()
+
+    if not obs.obsolete():
+        ui.warn("cannot solve instability of %s, skipping\n" % orig)
+        return False
     newer = obsolete.successorssets(repo, obs.node())
     # search of a parent which is not killed
     while not newer or newer == [()]:
@@ -1362,11 +1681,14 @@
         obs = obs.parents()[0]
         newer = obsolete.successorssets(repo, obs.node())
     if len(newer) > 1:
-        raise util.Abort(_("conflict rewriting. can't choose destination\n"))
+        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:
-        raise util.Abort(_("does not handle split parents yet\n"))
+        msg = _("does not handle split parents yet\n")
+        ui.write_err(msg)
         return 2
     target = targets[0]
     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
@@ -1399,15 +1721,19 @@
 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:
-        raise util.Abort('late comer stabilization is confused by bumped'
-                         ' %s being a merge' % bumped)
+        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:
-        raise util.Abort('late comer evolution is confused by precursors'
-                         ' %s being a merge' % prec)
+        msg = _('skipping: %s: public version is a merge, this 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:
@@ -1451,6 +1777,9 @@
         files = set()
         copied = copies.pathcopies(prec, bumped)
         precmanifest = prec.manifest()
+        # 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:
@@ -1502,40 +1831,51 @@
 
 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]))
-        hint = ("changeset %d is 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))
-        raise util.Abort("we do not handle divergence with split yet",
-                         hint=hint)
+        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 divergent.phase() <= phases.public:
-        raise util.Abort("we can't resolve this conflict from the public side",
-                    hint="%s is public, try from %s" % (divergent, other))
+        msg = _("skipping %s: we can't resolve divergence from the public side\n") % divergent
+        ui.write_err(msg)
+        hint = _("(%s is public, try from %s)\n" % (divergent, other))
+        ui.write_err(hint)
+        return 2
     if len(other.parents()) > 1:
-        raise util.Abort("divergent changeset can't be a merge (yet)",
-                    hint="You have to fallback to solving this by hand...\n"
-                         "| This probably means redoing the merge and using "
-                         "| `hg prune` to kill older version.")
+        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():
-        raise util.Abort("parents are not common (not handled yet)",
-                    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"
-                              % {'d': divergent, 'o': other})
+        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:
@@ -1625,7 +1965,8 @@
 
 @command('^previous',
          [('B', 'move-bookmark', False,
-             _('Move active bookmark after update'))],
+             _('Move active bookmark after update')),
+          ('', 'merge', False, _('bring uncommited change along'))],
          '[-B]')
 def cmdprevious(ui, repo, **opts):
     """update to parent and display summary lines"""
@@ -1633,6 +1974,12 @@
     wparents = wkctx.parents()
     if len(wparents) != 1:
         raise util.Abort('merge in progress')
+    if not opts['merge']:
+        try:
+            cmdutil.bailifchanged(repo)
+        except error.Abort, exc:
+            exc.hint = _('do you want --merge?')
+            raise
 
     parents = wparents[0].parents()
     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
@@ -1657,20 +2004,29 @@
 
 @command('^next',
          [('B', 'move-bookmark', False,
-             _('Move active bookmark after update'))],
+             _('Move active bookmark after update')),
+          ('', 'merge', False, _('bring uncommited change along')),
+          ('', 'evolve', False, _('evolve the next changeset if necessary'))],
          '[-B]')
 def cmdnext(ui, repo, **opts):
-    """update to child and display summary lines"""
+    """update to next child
+
+    You can use the --evolve flag to get unstable children evolved on demand.
+
+    The summary line of the destination is displayed for clarity"""
     wkctx = repo[None]
     wparents = wkctx.parents()
     if len(wparents) != 1:
         raise util.Abort('merge in progress')
+    if not opts['merge']:
+        try:
+            cmdutil.bailifchanged(repo)
+        except error.Abort, exc:
+            exc.hint = _('do you want --merge?')
+            raise
 
     children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()]
     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    if not children:
-        ui.warn(_('no non-obsolete children\n'))
-        return 1
     if len(children) == 1:
         c = children[0]
         bm = bmactive(repo)
@@ -1683,13 +2039,37 @@
             else:
                 bmdeactivate(repo)
         displayer.show(c)
-        return 0
-    else:
+        result = 0
+    elif children:
+        ui.warn("ambigious next changeset:\n")
         for c in children:
             displayer.show(c)
-        ui.warn(_('multiple non-obsolete children, '
-            'explicitly update to one of them\n'))
+        ui.warn(_('explicitly update to one of them\n'))
+        result = 1
+    else:
+        aspchildren = _aspiringchildren(repo, [repo['.'].rev()])
+        if not opts['evolve']:
+            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]], False,
+                               False, lambda:None, category='unstable')
+            if not result:
+                ui.status(_('working directory now at %s\n') % repo['.'])
+            return result
         return 1
+    return result
 
 def _reachablefrombookmark(repo, revs, mark):
     """filter revisions and bookmarks reachable from the given bookmark
@@ -1740,6 +2120,7 @@
     [('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")),
      ('B', 'bookmark', '', _("remove revs only reachable from given"
                              " bookmark"))] + metadataopts,
@@ -1779,10 +2160,11 @@
     if not revs:
         raise util.Abort(_('nothing to prune'))
 
-    wlock = lock = None
+    wlock = lock = tr = None
     try:
         wlock = repo.wlock()
         lock = repo.lock()
+        tr = repo.transaction('prune')
         # defines pruned changesets
         precs = []
         revs.sort()
@@ -1796,6 +2178,10 @@
         if not precs:
             raise util.Abort('nothing to prune')
 
+        if not obsolete.isenabled(repo, obsolete.allowunstableopt):
+            if repo.revs("(%ld::) - %ld", revs, revs):
+                raise util.Abort(_("cannot prune in the middle of a stack"))
+
         # defines successors changesets
         sucs = scmutil.revrange(repo, succs)
         sucs.sort()
@@ -1813,12 +2199,6 @@
         if biject:
             relations = [(p, (s,)) for p, s in zip(precs, sucs)]
 
-        # create markers
-        obsolete.createmarkers(repo, relations, metadata=metadata)
-
-        # informs that changeset have been pruned
-        ui.status(_('%i changesets pruned\n') % len(precs))
-
         wdp = repo['.']
 
         if len(sucs) == 1 and len(precs) == 1 and wdp in precs:
@@ -1828,27 +2208,55 @@
             # update to an unkilled parent
             newnode = wdp
 
-            while newnode.obsolete():
+            while newnode in precs or newnode.obsolete():
                 newnode = newnode.parents()[0]
 
+
         if newnode.node() != wdp.node():
-            bookactive = bmactive(repo)
-            # 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 bookmark
-            if movebookmark:
-                bmdeactivate(repo)
-                repo._bookmarks[bookactive] = newnode.node()
-                repo._bookmarks.write()
-            commands.update(ui, repo, newnode.rev())
-            ui.status(_('working directory now at %s\n') % newnode)
-            if movebookmark:
-                bmactivate(repo, bookactive)
+            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)
+                repo.dirstate.write()
+            else:
+                bookactive = bmactive(repo)
+                # 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 bookmark
+                if movebookmark:
+                    bmdeactivate(repo)
+                    repo._bookmarks[bookactive] = newnode.node()
+                    repo._bookmarks.write()
+                commands.update(ui, repo, newnode.rev())
+                ui.status(_('working directory now at %s\n') % newnode)
+                if movebookmark:
+                    bmactivate(repo, bookactive)
 
         # update bookmarks
         if bookmark:
             _deletebookmark(ui, marks, bookmark)
+
+        # 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:
             #
@@ -1863,8 +2271,10 @@
                     updatebookmarks = _bookmarksupdater(repo, ctx.node())
                     updatebookmarks(dest.node())
                     break
+
+        tr.close()
     finally:
-        lockmod.release(lock, wlock)
+        lockmod.release(tr, lock, wlock)
 
 @command('amend|refresh',
     [('A', 'addremove', None,
@@ -2053,11 +2463,16 @@
             if ctx.p1() == rev or ctx.p2() == rev:
                 raise util.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 util.Abort(_("cannot uncommit in the middle of a stack"))
+
         # Recommit the filtered changeset
         tr = repo.transaction('uncommit')
         newid = None
-        if (pats or opts.get('include') or opts.get('exclude')
-            or opts.get('all')):
+        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:
@@ -2107,6 +2522,31 @@
     finally:
         lockmod.release(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)
+
 @command('^touch',
     [('r', 'rev', [], 'revision to update'),
      ('D', 'duplicate', False,
@@ -2233,6 +2673,11 @@
         raise util.Abort(_("cannot fold non-linear revisions "
                            "(multiple heads given)"))
     head = repo[heads.first()]
+    disallowunstable = not obsolete.isenabled(repo, obsolete.allowunstableopt)
+    if disallowunstable:
+        if repo.revs("(%ld::) - %ld", revs, revs):
+            raise util.Abort(_("cannot fold chain not ending with a head "\
+                               "or with branching"))
     wlock = lock = None
     try:
         wlock = repo.wlock()
@@ -2299,8 +2744,12 @@
 @eh.extsetup
 def oldevolveextsetup(ui):
     for cmd in ['kill', 'uncommit', 'touch', 'fold']:
-        entry = extensions.wrapcommand(cmdtable, cmd,
-                                       warnobserrors)
+        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', [],
@@ -2329,38 +2778,19 @@
         topic = 'OBSEXC'
     ui.progress(topic, *args, **kwargs)
 
-if getattr(exchange, '_pushdiscoveryobsmarkers', None) is not None:
-    @eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers')
-    def _pushdiscoveryobsmarkers(orig, pushop):
-        if (obsolete._enabled
-            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 = []
-            obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
-                               % len(revs))
-            commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads))
-            common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, commonrevs)
-
-            revs = list(unfi.revs('%ld - (::%ln)', revs, common))
+@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"
@@ -2369,12 +2799,30 @@
             else:
                 obsexcmsg(repo.ui, "markers already in sync\n")
                 pushop.outobsmarkers = []
+                pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes)
+            return
+
+        common = []
+        obsexcmsg(repo.ui, "looking for common markers in %i nodes\n"
+                           % len(revs))
+        commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads))
+        common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, commonrevs)
+
+        revs = list(unfi.revs('%ld - (::%ln)', revs, common))
+        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 = []
 
 @eh.wrapfunction(wireproto, 'capabilities')
 def discocapabilities(orig, repo, proto):
     """wrapper to advertise new capability"""
     caps = orig(repo, proto)
-    if obsolete._enabled:
+    if obsolete.isenabled(repo, obsolete.exchangeopt):
         caps += ' _evoext_obshash_0'
     return caps
 
@@ -2534,7 +2982,7 @@
     pushop.ui.debug('try to push obsolete markers to remote\n')
     repo = pushop.repo
     remote = pushop.remote
-    if (obsolete._enabled  and repo.obsstore and
+    if (obsolete.isenabled(repo, obsolete.exchangeopt) and repo.obsstore and
         'obsolete' in remote.listkeys('namespaces')):
         markers = pushop.outobsmarkers
         if not markers:
@@ -2609,28 +3057,7 @@
     caps.add('_evoext_pushobsmarkers_0')
     return caps
 
-@eh.addattr(localrepo.localpeer, 'evoext_pushobsmarkers_0')
-def local_pushobsmarkers(peer, obsfile):
-    data = obsfile.read()
-    tr = lock = None
-    try:
-        lock = peer._repo.lock()
-        tr = peer._repo.transaction('pushkey: obsolete markers')
-        new = peer._repo.obsstore.mergemarkers(tr, data)
-        if new is not None:
-            obsexcmsg(peer._repo.ui, "%i obsolescence markers added\n" % new, True)
-        tr.close()
-    finally:
-        lockmod.release(tr, lock)
-    peer._repo.hook('evolve_pushobsmarkers')
-
-def srv_pushobsmarkers(repo, proto):
-    """wireprotocol command"""
-    fp = StringIO()
-    proto.redirect()
-    proto.getfile(fp)
-    data = fp.getvalue()
-    fp.close()
+def _pushobsmarkers(repo, data):
     tr = lock = None
     try:
         lock = repo.lock()
@@ -2642,6 +3069,20 @@
     finally:
         lockmod.release(tr, lock)
     repo.hook('evolve_pushobsmarkers')
+
+@eh.addattr(localrepo.localpeer, 'evoext_pushobsmarkers_0')
+def local_pushobsmarkers(peer, obsfile):
+    data = obsfile.read()
+    _pushobsmarkers(peer._repo, data)
+
+def srv_pushobsmarkers(repo, proto):
+    """wireprotocol command"""
+    fp = StringIO()
+    proto.redirect()
+    proto.getfile(fp)
+    data = fp.getvalue()
+    fp.close()
+    _pushobsmarkers(repo, data)
     return wireproto.pushres(0)
 
 def _buildpullobsmarkersboundaries(pullop):
@@ -2674,34 +3115,32 @@
             kwargs['evo_obscommon'] = common
     return ret
 
-if getattr(exchange, '_getbundleobsmarkerpart', None) is not None:
-    @eh.wrapfunction(exchange, '_getbundleobsmarkerpart')
-    def _getbundleobsmarkerpart(orig, bundler, repo, source, **kwargs):
-        if 'evo_obscommon' not in kwargs:
-            return orig(bundler, repo, source, **kwargs)
-
-        heads = kwargs.get('heads')
-        if kwargs.get('obsmarkers', False):
-            if heads is None:
-                heads = repo.heads()
-            obscommon = kwargs.get('evo_obscommon', ())
-            assert obscommon
-            obsset = repo.unfiltered().set('::%ln - ::%ln', heads, obscommon)
-            subset = [c.node() for c in obsset]
-            markers = repo.obsstore.relevantmarkers(subset)
-            exchange.buildobsmarkerspart(bundler, markers)
-
-    @eh.uisetup
-    def installgetbundlepartgen(ui):
-        origfunc = exchange.getbundle2partsmapping['obsmarkers']
-        def newfunc(*args, **kwargs):
-            return _getbundleobsmarkerpart(origfunc, *args, **kwargs)
-        exchange.getbundle2partsmapping['obsmarkers'] = newfunc
-
+@eh.wrapfunction(exchange, '_getbundleobsmarkerpart')
+def _getbundleobsmarkerpart(orig, bundler, repo, source, **kwargs):
+    if 'evo_obscommon' not in kwargs:
+        return orig(bundler, repo, source, **kwargs)
+
+    heads = kwargs.get('heads')
+    if kwargs.get('obsmarkers', False):
+        if heads is None:
+            heads = repo.heads()
+        obscommon = kwargs.get('evo_obscommon', ())
+        assert obscommon
+        obsset = repo.unfiltered().set('::%ln - ::%ln', heads, obscommon)
+        subset = [c.node() for c in obsset]
+        markers = repo.obsstore.relevantmarkers(subset)
+        exchange.buildobsmarkerspart(bundler, markers)
+
+@eh.uisetup
+def installgetbundlepartgen(ui):
+    origfunc = exchange.getbundle2partsmapping['obsmarkers']
+    def newfunc(*args, **kwargs):
+        return _getbundleobsmarkerpart(origfunc, *args, **kwargs)
+    exchange.getbundle2partsmapping['obsmarkers'] = newfunc
 
 @eh.wrapfunction(exchange, '_pullobsolete')
 def _pullobsolete(orig, pullop):
-    if not obsolete._enabled:
+    if not obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
         return None
     if 'obsmarkers' not in getattr(pullop, 'todosteps', ['obsmarkers']):
         return None
@@ -2871,21 +3310,20 @@
 _bestformat = max(obsolete.formats.keys())
 
 
-if getattr(obsolete, '_checkinvalidmarkers', None) is not None:
-    @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]:
-                raise util.Abort(_('bad obsolescence marker detected: '
-                                   'invalid successors nullid'),
-                                 hint=_('You should run `hg debugobsconvert`'))
+@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]:
+            raise util.Abort(_('bad obsolescence marker detected: '
+                               'invalid successors nullid'),
+                             hint=_('You should run `hg debugobsconvert`'))
 
 @command(
     'debugobsconvert',
@@ -2920,7 +3358,7 @@
 def capabilities(orig, repo, proto):
     """wrapper to advertise new capability"""
     caps = orig(repo, proto)
-    if obsolete._enabled:
+    if obsolete.isenabled(repo, obsolete.exchangeopt):
         caps += ' _evoext_pushobsmarkers_0'
         caps += ' _evoext_pullobsmarkers_0'
         caps += ' _evoext_obshash_0'
@@ -2941,3 +3379,16 @@
     def newcap(repo, proto):
         return capabilities(oldcap, repo, proto)
     wireproto.commands['capabilities'] = (newcap, args)
+
+def _helploader():
+    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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/inhibit.py	Thu Jun 25 16:55:27 2015 -0700
@@ -0,0 +1,227 @@
+"""reduce the changesets evolution feature scope for early and noob friendly ui
+
+the full scale changeset evolution have some massive bleeding edge and it is
+very easy for people not very intimate with the concept to end up in intricate
+situation. in order to get some of the benefit sooner, this extension is
+disabling some of the less polished aspect of evolution. it should gradually
+get thinner and thinner as changeset evolution will get more polished. this
+extension is only recommended for large scale organisations. individual user
+should probably stick on using evolution in its current state, understand its
+concept and provide feedback
+
+This extension provides the ability to "inhibit" obsolescence markers. obsolete 
+revision can be cheaply brought back to life that way. 
+However as the inhibitor are not fitting in an append only model, this is 
+incompatible with sharing mutable history.
+"""
+from mercurial import localrepo
+from mercurial import obsolete
+from mercurial import extensions
+from mercurial import cmdutil
+from mercurial import error
+from mercurial import scmutil
+from mercurial import commands
+from mercurial import lock as lockmod
+from mercurial import bookmarks
+from mercurial.i18n import _
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+def reposetup(ui, repo):
+
+    class obsinhibitedrepo(repo.__class__):
+
+        @localrepo.storecache('obsinhibit')
+        def _obsinhibit(self):
+            # XXX we should make sure it is invalidated by transaction failure
+            obsinhibit = set()
+            raw = self.sopener.tryread('obsinhibit')
+            for i in xrange(0, len(raw), 20):
+                obsinhibit.add(raw[i:i+20])
+            return obsinhibit
+
+        def commit(self, *args, **kwargs):
+            newnode = super(obsinhibitedrepo, self).commit(*args, **kwargs)
+            if newnode is not None:
+                _inhibitmarkers(repo, [newnode])
+            return newnode
+
+    repo.__class__ = obsinhibitedrepo
+
+def _update(orig, ui, repo, *args, **kwargs):
+    """
+    When moving to a commit we want to inhibit any obsolete commit affecting
+    the changeset we are updating to. In other words we don't want any visible
+    commit to be obsolete.
+    """
+    wlock = None
+    try:
+        # Evolve is running a hook on lock release to display a warning message 
+        # if the workind dir's parent is obsolete.
+        # We take the lock here to make sure that we inhibit the parent before
+        # that hook get a chance to run.
+        wlock = repo.wlock()
+        res = orig(ui, repo, *args, **kwargs)
+        newhead = repo['.'].node()
+        _inhibitmarkers(repo, [newhead])
+        return res
+    finally:
+        lockmod.release(wlock)
+
+def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
+    """ Add inhibition markers to every obsolete bookmarks """
+    repo = bkmstoreinst._repo
+    bkmstorenodes = [repo[v].node() for v in bkmstoreinst.values()]
+    _inhibitmarkers(repo, bkmstorenodes)
+    return orig(bkmstoreinst, *args, **kwargs)
+
+def _bookmark(orig, ui, repo, *bookmarks, **opts):
+    """ Add a -D option to the bookmark command, map it to prune -B """
+    haspruneopt = opts.get('prune', False)
+    if not haspruneopt:
+        return orig(ui, repo, *bookmarks, **opts)
+
+    # Call prune -B
+    evolve = extensions.find('evolve')
+    optsdict = {
+        'new': [],
+        'succ': [],
+        'rev': [],
+        'bookmark': bookmarks[0],
+        'keep': None,
+        'biject': False,
+    }
+    evolve.cmdprune(ui, repo, **optsdict)
+
+# obsolescence inhibitor
+########################
+
+def _schedulewrite(tr, obsinhibit):
+    """Make sure on disk content will be updated on transaction commit"""
+    def writer(fp):
+        """Serialize the inhibited list to disk.
+        """
+        raw = ''.join(obsinhibit)
+        fp.write(raw)
+    tr.addfilegenerator('obsinhibit', ('obsinhibit',), writer)
+    tr.hookargs['obs_inbihited'] = '1'
+
+def _filterpublic(repo, nodes):
+    """filter out inhibitor on public changeset
+
+    Public changesets are already immune to obsolescence"""
+    getrev = repo.changelog.nodemap.get
+    getphase = repo._phasecache.phase
+    return (n for n in repo._obsinhibit
+            if getrev(n) is not None and getphase(repo, getrev(n)))
+
+def _inhibitmarkers(repo, nodes):
+    """add marker inhibitor for all obsolete revision under <nodes>
+
+    Content of <nodes> and all mutable ancestors are considered. Marker for
+    obsolete revision only are created.
+    """
+    newinhibit = repo.set('::%ln and obsolete()', nodes)
+    if newinhibit:
+        lock = tr = None
+        try:
+            lock = repo.lock()
+            tr = repo.transaction('obsinhibit')
+            repo._obsinhibit.update(c.node() for c in newinhibit)
+            _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
+            repo.invalidatevolatilesets()
+            tr.close()
+        finally:
+            lockmod.release(tr, lock)
+
+def _deinhibitmarkers(repo, nodes):
+    """lift obsolescence inhibition on a set of nodes
+
+    This will be triggered when inhibited nodes received new obsolescence
+    markers. Otherwise the new obsolescence markers would also be inhibited.
+    """
+    deinhibited = repo._obsinhibit & set(nodes)
+    if deinhibited:
+        tr = repo.transaction('obsinhibit')
+        try:
+            repo._obsinhibit -= deinhibited
+            _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
+            repo.invalidatevolatilesets()
+            tr.close()
+        finally:
+            tr.release()
+
+def _createmarkers(orig, repo, relations, flag=0, date=None, metadata=None):
+    """wrap markers create to make sure we de-inhibit target nodes"""
+    # wrapping transactio to unify the one in each function
+    tr = repo.transaction('add-obsolescence-marker')
+    try:
+        orig(repo, relations, flag, date, metadata)
+        precs = (r[0].node() for r in relations)
+        _deinhibitmarkers(repo, precs)
+        tr.close()
+    finally:
+        tr.release()
+
+def transactioncallback(orig, repo, *args, **kwargs):
+    """ Wrap localrepo.transaction to inhibit new obsolete changes """
+    def inhibitposttransaction(transaction):
+        # At the end of the transaction we catch all the new visible and
+        # obsolete commit to inhibit them
+        visibleobsolete = repo.revs('obsolete() - hidden()')
+        ignoreset = set(getattr(repo, '_rebaseset', []))
+        visibleobsolete = list(r for r in visibleobsolete if r not in ignoreset)
+        if visibleobsolete:
+            _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
+    transaction = orig(repo, *args, **kwargs)
+    transaction.addpostclose('inhibitposttransaction', inhibitposttransaction)
+    return transaction
+
+def extsetup(ui):
+    # lets wrap the computation of the obsolete set
+    # We apply inhibition there
+    obsfunc = obsolete.cachefuncs['obsolete']
+    def _computeobsoleteset(repo):
+        """remove any inhibited nodes from the obsolete set
+
+        This will trickle down to other part of mercurial (hidden, log, etc)"""
+        obs = obsfunc(repo)
+        getrev = repo.changelog.nodemap.get
+        for n in repo._obsinhibit:
+            obs.discard(getrev(n))
+        return obs
+    try:
+        extensions.find('directaccess')
+    except KeyError:
+        errormsg = _('Cannot use inhibit without the direct access extension')
+        raise error.Abort(errormsg)
+
+    # Wrapping this to inhibit obsolete revs resulting from a transaction
+    extensions.wrapfunction(localrepo.localrepository,
+                            'transaction', transactioncallback)
+
+    obsolete.cachefuncs['obsolete'] = _computeobsoleteset
+    # wrap create marker to make it able to lift the inhibition
+    extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)
+    # drop divergence computation since it is incompatible with "light revive"
+    obsolete.cachefuncs['divergent'] = lambda repo: set()
+    # drop bumped computation since it is incompatible with "light revive"
+    obsolete.cachefuncs['bumped'] = lambda repo: set()
+    # wrap update to make sure that no obsolete commit is visible after an
+    # update
+    extensions.wrapcommand(commands.table, 'update', _update)
+    # There are two ways to save bookmark changes during a transation, we
+    # wrap both to add inhibition markers.
+    extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged)
+    extensions.wrapfunction(bookmarks.bmstore, 'write', _bookmarkchanged)
+    # Add bookmark -D option
+    entry = extensions.wrapcommand(commands.table, 'bookmark', _bookmark)
+    entry[1].append(('D','prune',None,
+                    _('delete the bookmark and prune the commits underneath')))
+
+@command('debugobsinhibit', [], '')
+def cmddebugobsinhibit(ui, repo, *revs):
+    """inhibit obsolescence markers effect on a set of revs"""
+    nodes = (repo[r].node() for r in scmutil.revrange(repo, revs))
+    _inhibitmarkers(repo, nodes)
--- a/hgext/obsolete.py	Tue Jun 23 16:50:06 2015 -0700
+++ b/hgext/obsolete.py	Thu Jun 25 16:55:27 2015 -0700
@@ -14,8 +14,6 @@
 
 try:
     from mercurial import obsolete
-    if not obsolete._enabled:
-        obsolete._enabled = True
 except ImportError:
     raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)')
 
@@ -40,6 +38,10 @@
     """
     if not repo.local():
         return
+    evolveopts = ui.configlist('experimental', 'evolution')
+    if not evolveopts:
+        evolveopts = 'all'
+        ui.setconfig('experimental', 'evolution', evolveopts)
     for arg in sys.argv:
         if 'debugc' in arg:
             break
--- a/hgext/pushexperiment.py	Tue Jun 23 16:50:06 2015 -0700
+++ b/hgext/pushexperiment.py	Thu Jun 25 16:55:27 2015 -0700
@@ -49,7 +49,8 @@
 
 def syncpush(orig, repo, remote):
     """wraper for obsolete.syncpush to use the fast way if possible"""
-    if not (obsolete._enabled and repo.obsstore):
+    if not (obsolete.isenabled(repo, obsolete.exchangeopt) and
+            repo.obsstore):
         return
     if remote.capable('_push_experiment_pushobsmarkers_0'):
         return # already pushed before changeset
@@ -75,7 +76,7 @@
     """push wrapped that call the wire protocol command"""
     if not remote.canpush():
         raise util.Abort(_("destination does not support push"))
-    if (obsolete._enabled and repo.obsstore
+    if (obsolete.isenabled(repo, obsolete.exchangeopt) and repo.obsstore
         and remote.capable('_push_experiment_pushobsmarkers_0')):
         # push marker early to limit damage of pushing too early.
         try:
@@ -94,7 +95,7 @@
 def capabilities(orig, repo, proto):
     """wrapper to advertise new capability"""
     caps = orig(repo, proto)
-    if obsolete._enabled:
+    if obsolete.isenabled(repo, obsolete.exchangeopt):
         caps += ' _push_experiment_pushobsmarkers_0'
     caps += ' _push_experiment_notifypushend_0'
     return caps
--- a/hgext/simple4server.py	Tue Jun 23 16:50:06 2015 -0700
+++ b/hgext/simple4server.py	Thu Jun 25 16:55:27 2015 -0700
@@ -12,7 +12,6 @@
 buglink = 'http://bz.selenic.com/'
 
 import mercurial.obsolete
-mercurial.obsolete._enabled = True
 
 import struct
 from mercurial import util
@@ -31,8 +30,6 @@
 gboptslist = gboptsmap = None
 try:
     from mercurial import obsolete
-    if not obsolete._enabled:
-        obsolete._enabled = True
     from mercurial import wireproto
     gboptslist = getattr(wireproto, 'gboptslist', None)
     gboptsmap = getattr(wireproto, 'gboptsmap', None)
@@ -247,7 +244,7 @@
     """wrapper to advertise new capability"""
     caps = orig(repo, proto)
     advertise = repo.ui.configbool('__temporary__', 'advertiseobsolete', True)
-    if obsolete._enabled and advertise:
+    if obsolete.isenabled(repo, obsolete.exchangeopt) and advertise:
         caps += ' _evoext_pushobsmarkers_0'
         caps += ' _evoext_pullobsmarkers_0'
         caps += ' _evoext_obshash_0'
@@ -302,3 +299,8 @@
     extensions.wrapfunction(pushkey, '_nslist', _nslist)
     pushkey._namespaces['namespaces'] = (lambda *x: False, pushkey._nslist)
 
+def reposetup(ui, repo):
+    evolveopts = ui.configlist('experimental', 'evolution')
+    if not evolveopts:
+        evolveopts = 'all'
+        ui.setconfig('experimental', 'evolution', evolveopts)
--- a/setup.py	Tue Jun 23 16:50:06 2015 -0700
+++ b/setup.py	Thu Jun 25 16:55:27 2015 -0700
@@ -1,6 +1,7 @@
 # Copied from histedit setup.py
 # Credit to Augie Fackler <durin42@gmail.com>
 
+import os
 from distutils.core import setup
 from os.path import dirname, join
 
@@ -14,6 +15,14 @@
           if "'" in line:
             return line.split("'")[1]
 
+py_modules = [
+    'hgext.evolve',
+]
+
+if os.environ.get('INCLUDE_INHIBIT'):
+    py_modules.append('hgext.inhibit')
+    py_modules.append('hgext.directaccess')
+
 setup(
     name='hg-evolve',
     version=get_version('hgext/evolve.py'),
@@ -25,5 +34,5 @@
     long_description=open('README').read(),
     keywords='hg mercurial',
     license='GPLv2+',
-    py_modules=['hgext.evolve'],
+    py_modules=py_modules
 )
--- a/tests/killdaemons.py	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/killdaemons.py	Thu Jun 25 16:55:27 2015 -0700
@@ -1,25 +1,91 @@
 #!/usr/bin/env python
 
-import os, time, errno, signal
+import os, sys, time, errno, signal
+
+if os.name =='nt':
+    import ctypes
+
+    def _check(ret, expectederr=None):
+        if ret == 0:
+            winerrno = ctypes.GetLastError()
+            if winerrno == expectederr:
+                return True
+            raise ctypes.WinError(winerrno)
 
-# Kill off any leftover daemon processes
-try:
-    fp = open(os.environ['DAEMON_PIDS'])
-    for line in fp:
+    def kill(pid, logfn, tryhard=True):
+        logfn('# Killing daemon process %d' % pid)
+        PROCESS_TERMINATE = 1
+        PROCESS_QUERY_INFORMATION = 0x400
+        SYNCHRONIZE = 0x00100000
+        WAIT_OBJECT_0 = 0
+        WAIT_TIMEOUT = 258
+        handle = ctypes.windll.kernel32.OpenProcess(
+                PROCESS_TERMINATE|SYNCHRONIZE|PROCESS_QUERY_INFORMATION,
+                False, pid)
+        if handle == 0:
+            _check(0, 87) # err 87 when process not found
+            return # process not found, already finished
         try:
-            pid = int(line)
-        except ValueError:
-            continue
+            r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
+            if r == WAIT_OBJECT_0:
+                pass # terminated, but process handle still available
+            elif r == WAIT_TIMEOUT:
+                _check(ctypes.windll.kernel32.TerminateProcess(handle, -1))
+            else:
+                _check(r)
+
+            # TODO?: forcefully kill when timeout
+            #        and ?shorter waiting time? when tryhard==True
+            r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
+                                                       # timeout = 100 ms
+            if r == WAIT_OBJECT_0:
+                pass # process is terminated
+            elif r == WAIT_TIMEOUT:
+                logfn('# Daemon process %d is stuck')
+            else:
+                _check(r) # any error
+        except: #re-raises
+            ctypes.windll.kernel32.CloseHandle(handle) # no _check, keep error
+            raise
+        _check(ctypes.windll.kernel32.CloseHandle(handle))
+
+else:
+    def kill(pid, logfn, tryhard=True):
         try:
             os.kill(pid, 0)
+            logfn('# Killing daemon process %d' % pid)
             os.kill(pid, signal.SIGTERM)
-            for i in range(10):
-                time.sleep(0.05)
+            if tryhard:
+                for i in range(10):
+                    time.sleep(0.05)
+                    os.kill(pid, 0)
+            else:
+                time.sleep(0.1)
                 os.kill(pid, 0)
+            logfn('# Daemon process %d is stuck - really killing it' % pid)
             os.kill(pid, signal.SIGKILL)
         except OSError, err:
             if err.errno != errno.ESRCH:
                 raise
-    fp.close()
-except IOError:
-    pass
+
+def killdaemons(pidfile, tryhard=True, remove=False, logfn=None):
+    if not logfn:
+        logfn = lambda s: s
+    # Kill off any leftover daemon processes
+    try:
+        fp = open(pidfile)
+        for line in fp:
+            try:
+                pid = int(line)
+            except ValueError:
+                continue
+            kill(pid, logfn, tryhard)
+        fp.close()
+        if remove:
+            os.unlink(pidfile)
+    except IOError:
+        pass
+
+if __name__ == '__main__':
+    path, = sys.argv[1:]
+    killdaemons(path)
--- a/tests/test-corrupt.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-corrupt.t	Thu Jun 25 16:55:27 2015 -0700
@@ -110,7 +110,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
-  pushing 2 obsolescence markers (* bytes) (glob)
+  pushing 2 obsolescence markers (160 bytes)
   2 obsolescence markers added
   $ hg -R ../other verify
   checking changesets
--- a/tests/test-evolve-bumped.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-evolve-bumped.t	Thu Jun 25 16:55:27 2015 -0700
@@ -1,6 +1,11 @@
   $ hg init public
   $ cd public
   $ echo a > a
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
   $ hg commit -A -m init
   adding a
   $ cd ..
@@ -11,6 +16,8 @@
   $ cat >> .hg/hgrc <<EOF
   > [extensions]
   > evolve = $evolvepath
+  > [ui]
+  > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n
   > [phases]
   > publish = false
   > EOF
@@ -32,12 +39,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   $ hg log -r 'draft()'
-  changeset:   1:4d1169d82e47
-  tag:         tip
-  user:        alice
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     modify a
-  
+  1:4d1169d82e47@default(draft) modify a
 
   $ cd ../bob
   $ hg pull ../private
@@ -50,12 +52,7 @@
   pull obsolescence markers
   (run 'hg update' to get a working copy)
   $ hg log -r 'draft()'
-  changeset:   1:4d1169d82e47
-  tag:         tip
-  user:        alice
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     modify a
-  
+  1:4d1169d82e47@default(draft) modify a
   $ hg push ../public
   pushing to ../public
   searching for changes
@@ -74,9 +71,57 @@
   pull obsolescence markers
   1 new bumped changesets
 
-  $ hg evolve -a
+  $ hg evolve -a -A --bumped
   recreate:[2] tweak a
   atop:[1] modify a
   computing new diff
   committed as 4d1169d82e47
   working directory is now at 4d1169d82e47
+
+Bumped Merge changeset:
+-----------------------
+
+We currently cannot automatically solve bumped changeset that is the
+product of a merge, we add a test for it.
+
+  $ mkcommit _a
+  $ hg up .^
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit _b
+  created new head
+  $ mkcommit _c
+  $ hg log -G
+  @  5:eeaf70969381@default(draft) add _c
+  |
+  o  4:6612fc0ddeb6@default(draft) add _b
+  |
+  | o  3:154ad198ff4a@default(draft) add _a
+  |/
+  o  1:4d1169d82e47@default(public) modify a
+  |
+  o  0:d3873e73d99e@default(public) init
+  
+  $ hg merge 3
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg commit -m "merge"
+  $ hg commit --amend -m "New message"
+  $ hg phase --public 551127da2a8a --hidden
+  1 new bumped changesets
+  $ hg log -G
+  @    7:b28e84916d8c@default(draft) New message
+  |\
+  +---o  6:551127da2a8a@default(public) merge
+  | |/
+  | o  5:eeaf70969381@default(public) add _c
+  | |
+  | o  4:6612fc0ddeb6@default(public) add _b
+  | |
+  o |  3:154ad198ff4a@default(public) add _a
+  |/
+  o  1:4d1169d82e47@default(public) modify a
+  |
+  o  0:d3873e73d99e@default(public) init
+  
+  $ hg evolve --all --bumped
+  skipping b28e84916d8c : we do not handle merge yet
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-evolve-order.t	Thu Jun 25 16:55:27 2015 -0700
@@ -0,0 +1,263 @@
+evolve --rev reordering
+-----------------------
+
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > fold=-d "0 0"
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish = False
+  > [diff]
+  > git = 1
+  > unified = 0
+  > [ui]
+  > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n
+  > [extensions]
+  > hgext.graphlog=
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+  $ mkstack() {
+  >    # Creates a stack of commit based on $1 with messages from $2, $3 ..
+  >    hg update "$1" -C
+  >    shift
+  >    mkcommits $*
+  > }
+
+  $ mkcommits() {
+  >   for i in $@; do mkcommit $i ; done
+  > }
+
+Initial setup
+  $ hg init testrevorder
+  $ cd testrevorder
+  $ mkcommits p _a _b _c
+  $ hg phase --public 0
+  $ hg up 'desc(_a)'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo "aaa" > _a
+  $ hg amend
+  2 new unstable changesets
+  $ hg log -G
+  @  5:12d519679175@default(draft) add _a
+  |
+  | o  3:4d156641b718@default(draft) add _c
+  | |
+  | o  2:4d7242ebb004@default(draft) add _b
+  | |
+  | x  1:2d73fcd7f07d@default(draft) add _a
+  |/
+  o  0:f92638be10c7@default(public) add p
+  
+
+evolve --rev reorders the rev to solve instability, trivial case 2 revs wrong order
+  $ hg evolve --rev 'desc(_c) + desc(_b)'
+  move:[2] add _b
+  atop:[5] add _a
+  move:[3] add _c
+  atop:[6] add _b
+  working directory is now at 52b8f9b04f83
+
+evolve --rev reorders the rev to solve instability. Harder case, obsolescence
+accross three stacks in growing rev numbers.
+  $ hg up "desc(_c)"
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ mkcommit _d
+  $ hg up "desc(_a)"
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg amend -m "aprime"
+  3 new unstable changesets
+  $ hg evolve --rev "desc(_b)"
+  move:[6] add _b
+  atop:[9] aprime
+  working directory is now at 476c9c052aae
+  $ hg up "desc(_b) - obsolete()"
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg amend -m "bprime"
+  $ hg up "desc(aprime)"
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg amend -m "asecond"
+  1 new unstable changesets
+  $ hg log -G
+  @  12:9a584314f3f3@default(draft) asecond
+  |
+  | o  11:a59c79776f7c@default(draft) bprime
+  | |
+  | x  9:81a687b96d4d@default(draft) aprime
+  |/
+  | o  8:464731bc0ed0@default(draft) add _d
+  | |
+  | o  7:52b8f9b04f83@default(draft) add _c
+  | |
+  | x  6:59476c3836ef@default(draft) add _b
+  | |
+  | x  5:12d519679175@default(draft) add _a
+  |/
+  o  0:f92638be10c7@default(public) add p
+  
+  $ hg evolve --rev "unstable()"
+  move:[11] bprime
+  atop:[12] asecond
+  move:[7] add _c
+  atop:[13] bprime
+  move:[8] add _d
+  atop:[14] add _c
+  working directory is now at 225d2cc5d3fc
+  $ hg log -G
+  @  15:225d2cc5d3fc@default(draft) add _d
+  |
+  o  14:0fc229278e4d@default(draft) add _c
+  |
+  o  13:c3741b9eafae@default(draft) bprime
+  |
+  o  12:9a584314f3f3@default(draft) asecond
+  |
+  o  0:f92638be10c7@default(public) add p
+  
+
+Evolve --rev more complex case: two sets of stacks one with prune an no successor, the other one
+partially solvable
+ 
+First set of stack:
+  $ mkstack "desc(_d)" c1_ c2_ c3_ c4_ >/dev/null
+  $ mkstack "desc(_d)" c1prime c2prime >/dev/null
+  $ mkstack "desc(_d)" c1second >/dev/null
+  $ hg prune "desc(c1_)" -s "desc(c1prime)"
+  1 changesets pruned
+  3 new unstable changesets
+  $ hg prune "desc(c2_)" -s "desc(c2prime)"
+  1 changesets pruned
+  $ hg prune "desc(c1prime)" -s "desc(c1second)"
+  1 changesets pruned
+  1 new unstable changesets
+  $ hg log -G -r "desc(_d)::"
+  @  22:a329855d0bc1@default(draft) add c1second
+  |
+  | o  21:072276ece1bf@default(draft) add c2prime
+  | |
+  | x  20:f137acd06692@default(draft) add c1prime
+  |/
+  | o  19:0a1d9b2ce733@default(draft) add c4_
+  | |
+  | o  18:e2874f41c56c@default(draft) add c3_
+  | |
+  | x  17:3247c33339fa@default(draft) add c2_
+  | |
+  | x  16:df322257c182@default(draft) add c1_
+  |/
+  o  15:225d2cc5d3fc@default(draft) add _d
+  |
+
+Second set of stack with no successor for b2_:
+  $ mkstack "desc(_d)" b1_ b2_ b3_ b4_ >/dev/null
+  $ mkstack "desc(_d)" b1prime b3prime >/dev/null
+  $ hg prune "desc(b1_)" -s "desc(b1prime)"
+  1 changesets pruned
+  3 new unstable changesets
+  $ hg prune "desc(b3_)" -s "desc(b3prime)"
+  1 changesets pruned
+  $ hg prune "desc(b2_)"
+  1 changesets pruned
+
+  $ hg log -G -r "desc(_d)::"
+  @  28:ba4c348b6d5e@default(draft) add b3prime
+  |
+  o  27:8fe985f5d0aa@default(draft) add b1prime
+  |
+  | o  26:1d9ba2e75c93@default(draft) add b4_
+  | |
+  | x  25:aec6a9657b6c@default(draft) add b3_
+  | |
+  | x  24:a69b58575918@default(draft) add b2_
+  | |
+  | x  23:3564eb18e448@default(draft) add b1_
+  |/
+  | o  22:a329855d0bc1@default(draft) add c1second
+  |/
+  | o  21:072276ece1bf@default(draft) add c2prime
+  | |
+  | x  20:f137acd06692@default(draft) add c1prime
+  |/
+  | o  19:0a1d9b2ce733@default(draft) add c4_
+  | |
+  | o  18:e2874f41c56c@default(draft) add c3_
+  | |
+  | x  17:3247c33339fa@default(draft) add c2_
+  | |
+  | x  16:df322257c182@default(draft) add c1_
+  |/
+  o  15:225d2cc5d3fc@default(draft) add _d
+  |
+
+Solve the full second stack and only part of the first one
+  $ echo "(desc(_d)::) - desc(c3_)"
+  (desc(_d)::) - desc(c3_)
+  $ hg evolve --rev "(desc(_d)::) - desc(c3_)"
+  cannot solve instability of 0a1d9b2ce733, skipping
+  move:[21] add c2prime
+  atop:[22] add c1second
+  move:[26] add b4_
+  atop:[28] add b3prime
+  working directory is now at 4897c8ed7645
+
+Cleanup
+  $ hg evolve --rev "(desc(_d)::)"
+  move:[18] add c3_
+  atop:[29] add c2prime
+  move:[19] add c4_
+  atop:[31] add c3_
+  working directory is now at 4ee8feb52325
+  $ hg log -G -r "desc(_d)::"
+  @  32:4ee8feb52325@default(draft) add c4_
+  |
+  o  31:08a530ce67e1@default(draft) add c3_
+  |
+  | o  30:4897c8ed7645@default(draft) add b4_
+  | |
+  o |  29:3abc7618dd5f@default(draft) add c2prime
+  | |
+  | o  28:ba4c348b6d5e@default(draft) add b3prime
+  | |
+  | o  27:8fe985f5d0aa@default(draft) add b1prime
+  | |
+  o |  22:a329855d0bc1@default(draft) add c1second
+  |/
+  o  15:225d2cc5d3fc@default(draft) add _d
+  |
+
+Test multiple revision with some un-evolvable because parent is splitted
+------------------------------------------------------------------------
+
+  $ hg up 'desc(c2prime)'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ mkcommit c3part1
+  created new head
+  $ hg prev
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [29] add c2prime
+  $ mkcommit c3part2
+  created new head
+  $ hg prune -s 'desc(c3part1)' 'desc(c3_)'
+  1 changesets pruned
+  1 new unstable changesets
+  $ hg prune -s 'desc(c3part2)' 'desc(c3_)'
+  1 changesets pruned
+  2 new divergent changesets
+  $ hg up 'desc(b3prime)'
+  2 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg amend -m 'b3second'
+  1 new unstable changesets
+  $ hg evolve --rev 'unstable()'
+  move:[30] add b4_
+  atop:[35] b3second
+  skipping 08a530ce67e1: divergent rewriting. can't choose destination
+  working directory is now at a51a8a82fdba
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-evolve-split.t	Thu Jun 25 16:55:27 2015 -0700
@@ -0,0 +1,61 @@
+Check that evolve shows error while handling split commits
+--------------------------------------
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > fold=-d "0 0"
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish = False
+  > [diff]
+  > git = 1
+  > unified = 0
+  > [ui]
+  > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n
+  > [extensions]
+  > hgext.graphlog=
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+  $ hg init split
+  $ cd split
+  $ mkcommit aa
+
+Create a split commit
+  $ printf "oo" > oo;
+  $ printf "pp" > pp;
+  $ hg add oo pp
+  $ hg commit -m "oo+pp"
+  $ mkcommit uu
+  $ hg up 0
+  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')"
+  1 changesets pruned
+  1 new unstable changesets
+  $ hg log -G
+  @  4:d0dcf24cddd3@default(draft) _pp
+  |
+  o  3:a7fdfda64c08@default(draft) _oo
+  |
+  | o  2:f52200b086ca@default(draft) add uu
+  | |
+  | x  1:d55647aaa0c6@default(draft) oo+pp
+  |/
+  o  0:58663bb03074@default(draft) add aa
+  
+  $ hg evolve --rev "0::"
+  does not handle split parents yet
--- a/tests/test-evolve.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-evolve.t	Thu Jun 25 16:55:27 2015 -0700
@@ -22,10 +22,65 @@
   >    hg ci -m "add $1"
   > }
 
+  $ mkstack() {
+  >    # Creates a stack of commit based on $1 with messages from $2, $3 ..
+  >    hg update $1 -C
+  >    shift
+  >    mkcommits $*
+  > }
+
   $ glog() {
   >   hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
   > }
 
+  $ shaof() {
+  >   hg log -T {node} -r "first(desc($1))"
+  > }
+
+  $ mkcommits() {
+  >   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 hg 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
+      enable 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
@@ -69,9 +124,9 @@
   $ hg id -n
   5
   $ hg kill .
-  1 changesets pruned
   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)
@@ -82,9 +137,9 @@
 test multiple kill
 
   $ hg kill 4 -r 3
-  2 changesets pruned
   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)
@@ -97,9 +152,9 @@
   $ echo 4 > g
   $ hg add g
   $ hg kill .
-  1 changesets pruned
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory now at 7c3bad9141dc
+  1 changesets pruned
   $ hg st
   A g
 
@@ -317,7 +372,7 @@
   |
   o  0	: base - test
   
-  $ hg evolve --any --traceback
+  $ hg evolve --any --traceback --bumped
   recreate:[8] another feature that rox
   atop:[7] another feature (child of ba0ec09b1bab)
   computing new diff
@@ -866,9 +921,20 @@
 
   $ 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 here
-  (2 troubled changesets, do you want --any ?)
+  nothing to evolve on current working copy parent
+  (2 other unstable in the repository, do you want --any or --rev)
   [2]
 
 
@@ -887,3 +953,448 @@
   working directory is now at f37ed7a60f43
   $ 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  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 d66b1e328488
+  working directory is now at d66b1e328488
+  $ hg evolve --any
+  move:[9] a3
+  atop:[13] bumped update to f37ed7a60f43:
+  working directory is now at 7d2ce5f38f9b
+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 f37ed7a60f43: - 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  16	: add gh - test
+  | |
+  | | o  15	: add gg - test
+  | |/
+  | x  14	: a3 - test
+  |/
+  o  13	: bumped update to f37ed7a60f43: - 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 db3d894869b0
+  $ hg glog
+  @  20	: add gh - test
+  |
+  | o  19	: add gg - test
+  |/
+  o  18	: a3 - test
+  |
+  o  13	: bumped update to f37ed7a60f43: - test
+  |
+  o  11	: a2 - 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:
+  
+   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:4c0bc042ef3b@default(draft) add j1
+  |
+  | o  23:c70048fd3350@default(draft) add j3
+  | |
+  | o  22:714e60ca57b7@default(draft) add j2
+  | |
+  | x  21:b430835af718@default(draft) add j1
+  |/
+  | o  20:db3d894869b0@default(draft) add gh
+  | |
+  o |  19:10ffdd7e3cc9@default(draft) add gg
+  |/
+  o  18:0bb66d4c1968@default(draft) a3
+  |
+
+  $ hg evolve --rev 23 --any
+  abort: cannot specify both "--rev" and "--any"
+  [255]
+  $ hg evolve --rev 23
+  cannot solve instability of c70048fd3350, 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 4c0bc042ef3b^
+  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 4c0bc042ef3b
+  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:04b32348803e@default(draft) add j1
+  |
+  | o  23:c70048fd3350@default(draft) add j3
+  | |
+  | o  22:714e60ca57b7@default(draft) add j2
+  | |
+  | x  21:b430835af718@default(draft) add j1
+  |/
+  | o  20:db3d894869b0@default(draft) add gh
+  | |
+  o |  19:10ffdd7e3cc9@default(draft) add gg
+  |/
+  o  18:0bb66d4c1968@default(draft) a3
+  |
+
+Check that prune respects the allowunstable option
+  $ hg up -C .
+  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 920a35e8dbd0
+  $ glog -r "18::"
+  @  28:920a35e8dbd0@default(draft) add j3
+  |
+  o  27:31e050d895dd@default(draft) add j2
+  |
+  o  26:04b32348803e@default(draft) add j1
+  |
+  | o  20:db3d894869b0@default(draft) add gh
+  | |
+  o |  19:10ffdd7e3cc9@default(draft) add gg
+  |/
+  o  18:0bb66d4c1968@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
+  [255]
+  $ hg prune '19::28'
+  abort: cannot prune in the middle of a stack
+  [255]
+  $ hg prune '26::'
+  3 changesets pruned
+  $ glog -r "18::"
+  @  29:5a6c53544778@default(draft) add c5_
+  |
+  | o  20:db3d894869b0@default(draft) add gh
+  | |
+  o |  19:10ffdd7e3cc9@default(draft) add gg
+  |/
+  o  18:0bb66d4c1968@default(draft) a3
+  |
+
+Check that fold respects the allowunstable option
+  $ hg up 0bb66d4c1968
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ mkcommit unstableifparentisfolded
+  created new head
+  $ glog -r "18::"
+  @  30:30ecefd67c0a@default(draft) add unstableifparentisfolded
+  |
+  | o  29:5a6c53544778@default(draft) add c5_
+  | |
+  +---o  20:db3d894869b0@default(draft) add gh
+  | |
+  | o  19:10ffdd7e3cc9@default(draft) add gg
+  |/
+  o  18:0bb66d4c1968@default(draft) a3
+  |
+
+  $ hg fold --exact "19 + 18"
+  abort: cannot fold chain not ending with a head or with branching
+  [255]
+  $ hg fold --exact "18::29"
+  abort: cannot fold chain not ending with a head or with branching
+  [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:5cc6eda0f00d@default(draft) add gg
+  |
+  | @  30:30ecefd67c0a@default(draft) add unstableifparentisfolded
+  |/
+  | o  20:db3d894869b0@default(draft) add gh
+  |/
+  o  18:0bb66d4c1968@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')"
+  1 changesets pruned
+  1 new unstable changesets
+  $ glog -r "18::"
+  @  35:072908d77206@default(draft) _pp
+  |
+  o  34:68e429987343@default(draft) _oo
+  |
+  | o  33:030868870864@default(draft) add uu
+  | |
+  | x  32:7e9688cf0a1b@default(draft) oo+pp
+  |/
+  | o  31:5cc6eda0f00d@default(draft) add gg
+  | |
+  o |  30:30ecefd67c0a@default(draft) add unstableifparentisfolded
+  |/
+  | o  20:db3d894869b0@default(draft) add gh
+  |/
+  o  18:0bb66d4c1968@default(draft) a3
+  |
+  $ hg evolve --rev "18::"
+  does not handle split parents yet
+
+
--- a/tests/test-exchange-D2.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-exchange-D2.t	Thu Jun 25 16:55:27 2015 -0700
@@ -37,9 +37,9 @@
   created new head
   $ hg debugobsolete `getid 'desc(A0)'` `getid 'desc(A1)'`
   $ hg prune --date '0 0' .
-  1 changesets pruned
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory now at a9bdc8b26820
+  1 changesets pruned
   $ hg strip --hidden -q 'desc(A1)'
   $ hg log -G --hidden
   x  28b51eb45704 (draft): A0
--- a/tests/test-exchange-D3.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-exchange-D3.t	Thu Jun 25 16:55:27 2015 -0700
@@ -39,9 +39,9 @@
   $ mkcommit A1
   $ hg debugobsolete `getid 'desc(A0)'` `getid 'desc(A1)'`
   $ hg prune -d '0 0' .
-  1 changesets pruned
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory now at 35b183996678
+  1 changesets pruned
   $ hg strip --hidden -q 'desc(A1)'
   $ hg log -G --hidden
   @  35b183996678 (draft): B
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-inhibit.t	Thu Jun 25 16:55:27 2015 -0700
@@ -0,0 +1,762 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > logtemplate = {rev}:{node|short} {desc}\n
+  > [experimental]
+  > prunestrip=True
+  > evolution=createmarkers
+  > [extensions]
+  > rebase=
+  > strip=
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+  $ echo "directaccess=$(echo $(dirname $TESTDIR))/hgext/directaccess.py" >> $HGRCPATH
+  $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext/inhibit.py" >> $HGRCPATH
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+  $ hg init inhibit
+  $ cd inhibit
+  $ mkcommit cA
+  $ mkcommit cB
+  $ mkcommit cC
+  $ mkcommit cD
+  $ hg up 'desc(cA)'
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ mkcommit cE
+  created new head
+  $ mkcommit cG
+  $ mkcommit cH
+  $ mkcommit cJ
+  $ hg log -G
+  @  7:18214586bf78 add cJ
+  |
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  | o  3:2db36d8066ff add cD
+  | |
+  | o  2:7df62a38b9bf add cC
+  | |
+  | o  1:02bcbc3f6e56 add cB
+  |/
+  o  0:54ccbc537fc2 add cA
+  
+
+plain prune
+
+  $ hg strip 1::
+  3 changesets pruned
+  $ hg log -G
+  @  7:18214586bf78 add cJ
+  |
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg debugobsinhibit --hidden 1::
+  $ hg log -G
+  @  7:18214586bf78 add cJ
+  |
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  | o  3:2db36d8066ff add cD
+  | |
+  | o  2:7df62a38b9bf add cC
+  | |
+  | o  1:02bcbc3f6e56 add cB
+  |/
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg strip --hidden 1::
+  3 changesets pruned
+  $ hg log -G
+  @  7:18214586bf78 add cJ
+  |
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+
+after amend
+
+  $ echo babar > cJ
+  $ hg commit --amend
+  $ hg log -G
+  @  9:55c73a90e4b4 add cJ
+  |
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg debugobsinhibit --hidden 18214586bf78
+  $ hg log -G
+  @  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+
+and no divergence
+
+  $ hg summary
+  parent: 9:55c73a90e4b4 tip
+   add cJ
+  branch: default
+  commit: (clean)
+  update: 1 new changesets, 2 branch heads (merge)
+
+check public revision got cleared
+(when adding the second inhibitor, the first one is removed because it is public)
+
+  $ wc -m .hg/store/obsinhibit | sed -e 's/^[ \t]*//'
+  20 .hg/store/obsinhibit
+  $ hg strip 7
+  1 changesets pruned
+  $ hg debugobsinhibit --hidden 18214586bf78
+  $ wc -m .hg/store/obsinhibit | sed -e 's/^[ \t]*//'
+  20 .hg/store/obsinhibit
+  $ hg log -G
+  @  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg phase --public 7
+  $ hg strip 9
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory now at cf5c4f4554ce
+  1 changesets pruned
+  $ hg log -G
+  o  7:18214586bf78 add cJ
+  |
+  @  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg debugobsinhibit --hidden 55c73a90e4b4
+  $ wc -m .hg/store/obsinhibit | sed -e 's/^[ \t]*//'
+  20 .hg/store/obsinhibit
+  $ hg log -G
+  o  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  @  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+Update should inhibit all related unstable commits
+
+  $ hg update 2 --hidden
+  2 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg log -G
+  o  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  | @  2:7df62a38b9bf add cC
+  | |
+  | o  1:02bcbc3f6e56 add cB
+  |/
+  o  0:54ccbc537fc2 add cA
+  
+
+  $ hg update 9
+  4 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg log -G
+  @  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  | o  2:7df62a38b9bf add cC
+  | |
+  | o  1:02bcbc3f6e56 add cB
+  |/
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg strip --hidden 1::
+  3 changesets pruned
+  $ hg log -G
+  @  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+
+Bookmark should inhibit all related unstable commits
+  $ hg bookmark -r 2 book1  --hidden
+  $ hg log -G
+  @  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  | o  2:7df62a38b9bf add cC
+  | |
+  | o  1:02bcbc3f6e56 add cB
+  |/
+  o  0:54ccbc537fc2 add cA
+  
+
+Removing a bookmark with bookmark -D should prune the changes underneath
+that are not reachable from another bookmark or head
+
+  $ hg bookmark -r 1 book2
+  $ hg bookmark -D book1  --config experimental.evolution=createmarkers #--config to make sure prune is not registered as a command.
+  bookmark 'book1' deleted
+  1 changesets pruned
+  $ hg log -G
+  @  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  | o  1:02bcbc3f6e56 add cB
+  |/
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg bookmark -D book2
+  bookmark 'book2' deleted
+  1 changesets pruned
+  $ hg log -G
+  @  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+Test that direct access make changesets visible
+
+  $ hg export 2db36d8066ff 02bcbc3f6e56
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 2db36d8066ff50e8be3d3e6c2da1ebc0a8381d82
+  # Parent  7df62a38b9bf9daf968de235043ba88a8ef43393
+  add cD
+  
+  diff -r 7df62a38b9bf -r 2db36d8066ff cD
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/cD	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +cD
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 02bcbc3f6e56fb2928efec2c6e24472720bf5511
+  # Parent  54ccbc537fc2d6845a5d61337c1cfb80d1d2815e
+  add cB
+  
+  diff -r 54ccbc537fc2 -r 02bcbc3f6e56 cB
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/cB	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +cB
+
+But only with hash
+
+  $ hg export 2db36d8066ff::
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 2db36d8066ff50e8be3d3e6c2da1ebc0a8381d82
+  # Parent  7df62a38b9bf9daf968de235043ba88a8ef43393
+  add cD
+  
+  diff -r 7df62a38b9bf -r 2db36d8066ff cD
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/cD	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +cD
+
+  $ hg export 1 3
+  abort: filtered revision '1' (not in 'visible-directaccess-nowarn' subset)!
+  [255]
+
+
+With severals hidden sha, rebase of one hidden stack onto another one:
+  $ hg update -C 0
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ mkcommit cK
+  created new head
+  $ mkcommit cL
+  $ hg update -C 9
+  4 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg log -G
+  o  11:53a94305e133 add cL
+  |
+  o  10:ad78ff7d621f add cK
+  |
+  | @  9:55c73a90e4b4 add cJ
+  | |
+  | | o  7:18214586bf78 add cJ
+  | |/
+  | o  6:cf5c4f4554ce add cH
+  | |
+  | o  5:5419eb264a33 add cG
+  | |
+  | o  4:98065434e5c6 add cE
+  |/
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg strip --hidden 10:
+  2 changesets pruned
+  $ hg log -G
+  @  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg rebase -s 10 -d 3 
+  abort: filtered revision '3' (not in 'visible-directaccess-warn' subset)!
+  [255]
+  $ hg rebase -r ad78ff7d621f -r 53a94305e133 -d  2db36d8066ff
+  Warning: accessing hidden changesets 2db36d8066ff for write operation
+  Warning: accessing hidden changesets ad78ff7d621f for write operation
+  Warning: accessing hidden changesets 53a94305e133 for write operation
+  rebasing 10:ad78ff7d621f "add cK"
+  rebasing 11:53a94305e133 "add cL"
+  $ hg log -G
+  o  13:2f7b7704d714 add cL
+  |
+  o  12:fe1634cbe235 add cK
+  |
+  | @  9:55c73a90e4b4 add cJ
+  | |
+  | | o  7:18214586bf78 add cJ
+  | |/
+  | o  6:cf5c4f4554ce add cH
+  | |
+  | o  5:5419eb264a33 add cG
+  | |
+  | o  4:98065434e5c6 add cE
+  | |
+  o |  3:2db36d8066ff add cD
+  | |
+  o |  2:7df62a38b9bf add cC
+  | |
+  o |  1:02bcbc3f6e56 add cB
+  |/
+  o  0:54ccbc537fc2 add cA
+  
+Check that amending in the middle of a stack does not show obsolete revs
+Since we are doing operation in the middle of the stack we cannot just
+have createmarkers as we are creating instability
+
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution=all
+  > EOF
+
+  $ hg strip --hidden 1::
+  5 changesets pruned
+  $ hg log -G
+  @  9:55c73a90e4b4 add cJ
+  |
+  | o  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg up 7
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ mkcommit cL
+  $ mkcommit cM
+  $ mkcommit cN
+  $ hg log -G
+  @  16:a438c045eb37 add cN
+  |
+  o  15:2d66e189f5b5 add cM
+  |
+  o  14:d66ccb8c5871 add cL
+  |
+  | o  9:55c73a90e4b4 add cJ
+  | |
+  o |  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg up 15
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo "mmm" >> cM
+  $ hg amend
+  $ hg log -G
+  @  18:210589181b14 add cM
+  |
+  | o  16:a438c045eb37 add cN
+  | |
+  | o  15:2d66e189f5b5 add cM
+  |/
+  o  14:d66ccb8c5871 add cL
+  |
+  | o  9:55c73a90e4b4 add cJ
+  | |
+  o |  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+Check that rebasing a commit twice makes the commit visible again
+
+  $ hg rebase -d 18 -r 16 --keep
+  rebasing 16:a438c045eb37 "add cN"
+  $ hg log -r 14:: -G
+  o  19:104eed5354c7 add cN
+  |
+  @  18:210589181b14 add cM
+  |
+  | o  16:a438c045eb37 add cN
+  | |
+  | o  15:2d66e189f5b5 add cM
+  |/
+  o  14:d66ccb8c5871 add cL
+  |
+  $ hg strip -r 104eed5354c7
+  1 changesets pruned
+  $ hg rebase -d 18 -r 16 --keep
+  rebasing 16:a438c045eb37 "add cN"
+  $ hg log -r 14:: -G
+  o  19:104eed5354c7 add cN
+  |
+  @  18:210589181b14 add cM
+  |
+  | o  16:a438c045eb37 add cN
+  | |
+  | o  15:2d66e189f5b5 add cM
+  |/
+  o  14:d66ccb8c5871 add cL
+  |
+
+Test prunestrip
+
+  $ hg book foo -r 104eed5354c7
+  $ hg strip -r 210589181b14
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory now at d66ccb8c5871
+  2 changesets pruned
+  $ hg log -r 14:: -G -T '{rev}:{node|short} {desc|firstline} {bookmarks}\n'
+  o  16:a438c045eb37 add cN
+  |
+  o  15:2d66e189f5b5 add cM
+  |
+  @  14:d66ccb8c5871 add cL foo
+  |
+
+Check that --hidden used with inhibit does not hide every obsolete commit
+We show the log before and after a log -G --hidden, they should be the same
+  $ hg log -G
+  o  16:a438c045eb37 add cN
+  |
+  o  15:2d66e189f5b5 add cM
+  |
+  @  14:d66ccb8c5871 add cL
+  |
+  | o  9:55c73a90e4b4 add cJ
+  | |
+  o |  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+  $ hg log -G --hidden
+  x  19:104eed5354c7 add cN
+  |
+  x  18:210589181b14 add cM
+  |
+  | x  17:b3c3274523f9 temporary amend commit for 2d66e189f5b5
+  | |
+  | | o  16:a438c045eb37 add cN
+  | |/
+  | o  15:2d66e189f5b5 add cM
+  |/
+  @  14:d66ccb8c5871 add cL
+  |
+  | x  13:2f7b7704d714 add cL
+  | |
+  | x  12:fe1634cbe235 add cK
+  | |
+  | | x  11:53a94305e133 add cL
+  | | |
+  | | x  10:ad78ff7d621f add cK
+  | | |
+  | | | o  9:55c73a90e4b4 add cJ
+  | | | |
+  +-------x  8:e84f73d9ad36 temporary amend commit for 18214586bf78
+  | | | |
+  o-----+  7:18214586bf78 add cJ
+   / / /
+  | | o  6:cf5c4f4554ce add cH
+  | | |
+  | | o  5:5419eb264a33 add cG
+  | | |
+  | | o  4:98065434e5c6 add cE
+  | |/
+  x |  3:2db36d8066ff add cD
+  | |
+  x |  2:7df62a38b9bf add cC
+  | |
+  x |  1:02bcbc3f6e56 add cB
+  |/
+  o  0:54ccbc537fc2 add cA
+  
+
+  $ hg log -G
+  o  16:a438c045eb37 add cN
+  |
+  o  15:2d66e189f5b5 add cM
+  |
+  @  14:d66ccb8c5871 add cL
+  |
+  | o  9:55c73a90e4b4 add cJ
+  | |
+  o |  7:18214586bf78 add cJ
+  |/
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+ 
+check that pruning and inhibited node does not confuse anything
+
+  $ hg up --hidden 210589181b14
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg strip --bundle 210589181b14
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/inhibit/.hg/strip-backup/210589181b14-e09c7b88-backup.hg (glob)
+  $ hg unbundle .hg/strip-backup/210589181b14-e09c7b88-backup.hg # restore state
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 1 changes to 2 files (+1 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+
+ Only allow direct access and check that evolve works like before
+(also disable evolve commands to avoid hint about using evolve)
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > inhibit=!
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+
+  $ hg up 15
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete!
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution=all
+  > EOF
+  $ echo "CM" > cM
+  $ hg amend
+  $ hg log -G
+  @  21:721c3c279519 add cM
+  |
+  | o  16:a438c045eb37 add cN
+  | |
+  | x  15:2d66e189f5b5 add cM
+  |/
+  o  14:d66ccb8c5871 add cL
+  |
+  o  7:18214586bf78 add cJ
+  |
+  o  6:cf5c4f4554ce add cH
+  |
+  o  5:5419eb264a33 add cG
+  |
+  o  4:98065434e5c6 add cE
+  |
+  o  0:54ccbc537fc2 add cA
+  
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > EOF
+  $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext/inhibit.py" >> $HGRCPATH
+
+Empty commit
+  $ hg amend
+  nothing changed
+  [1]
+
+Directaccess should load after some extensions precised in the conf
+With no extension specified:
+
+  $ cat >$TESTTMP/test_extension.py  << EOF
+  > from mercurial import extensions
+  > def uisetup(ui):
+  >   print extensions._order
+  > EOF
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > testextension=$TESTTMP/test_extension.py
+  > EOF
+  $ hg id
+  ['rebase', 'strip', 'evolve', 'directaccess', 'inhibit', 'testextension']
+  721c3c279519 tip
+
+With test_extension specified:
+  $ cat >> $HGRCPATH << EOF
+  > [directaccess]
+  > loadsafter=testextension
+  > EOF
+  $ hg id
+  ['rebase', 'strip', 'evolve', 'inhibit', 'testextension', 'directaccess']
+  721c3c279519 tip
+
+Inhibit should not work without directaccess
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > directaccess=!
+  > testextension=!
+  > EOF
+  $ hg up 15
+  abort: Cannot use inhibit without the direct access extension
+  [255]
+  $ echo "directaccess=$(echo $(dirname $TESTDIR))/hgext/directaccess.py" >> $HGRCPATH
+  $ cd ..
+
+
+hg push should not allow directaccess unless forced with --hidden
+We copy the inhibhit repo to inhibit2 and make some changes to push to inhibit
+
+  $ cp -r inhibit inhibit2
+  $ pwd=$(pwd)
+  $ cd inhibit
+  $ mkcommit pk
+  $ hg id
+  003a4735afde tip
+  $ echo "OO" > pk
+  $ hg amend
+  $ hg id
+  71eb4f100663 tip
+
+Hidden commits cannot be pushed without --hidden
+  $ hg push -r 003a4735afde file://$pwd/inhibit2
+  pushing to file://$TESTTMP/inhibit2
+  abort: hidden revision '003a4735afde'!
+  (use --hidden to access hidden revisions)
+  [255]
+
+Visible commits can still be pushed
+  $ hg push -r 71eb4f100663 file://$pwd/inhibit2
+  pushing to file://$TESTTMP/inhibit2
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  pushing 33 obsolescence markers (3284 bytes)
+  2 obsolescence markers added
--- a/tests/test-obsolete.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-obsolete.t	Thu Jun 25 16:55:27 2015 -0700
@@ -131,6 +131,7 @@
   summary:     add obsol_c
   
   working directory parent is obsolete!
+  (use "hg evolve" to update to its successor)
   $ mkcommit d # 5 (on 3)
   1 new unstable changesets
   $ qlog -r 'obsolete()'
@@ -183,7 +184,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
-  pushing 2 obsolescence markers (* bytes) (glob)
+  pushing 2 obsolescence markers (133 bytes)
   2 obsolescence markers added
   $ hg -R ../other-new verify
   checking changesets
@@ -238,7 +239,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  pushing 3 obsolescence markers (* bytes) (glob)
+  pushing 3 obsolescence markers (199 bytes)
   1 obsolescence markers added
   $ qlog -R ../other-new
   5
@@ -261,7 +262,7 @@
   pushing to ../other-new
   searching for changes
   no changes found
-  pushing 3 obsolescence markers (* bytes) (glob)
+  pushing 3 obsolescence markers (199 bytes)
   0 obsolescence markers added
   [1]
 
@@ -543,7 +544,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to [12] files (re)
-  pushing 7 obsolescence markers (* bytes) (glob)
+  pushing 7 obsolescence markers (491 bytes)
   3 obsolescence markers added
   $ hg up -q 10
   $ mkcommit "obsol_d'''"
@@ -556,7 +557,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  pushing 8 obsolescence markers (* bytes) (glob)
+  pushing 8 obsolescence markers (557 bytes)
   1 obsolescence markers added
   $ cd ..
 
@@ -705,6 +706,7 @@
   $ hg up --hidden 2
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory parent is obsolete!
+  (use "hg evolve" to update to its successor)
   $ hg export 9468a5f5d8b2 | hg import -
   applying patch from stdin
   1 new unstable changesets
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-options.t	Thu Jun 25 16:55:27 2015 -0700
@@ -0,0 +1,30 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > logtemplate={rev}:{node|short}[{bookmarks}] ({obsolete}/{phase}) {desc|firstline}\n
+  > [extensions]
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+  $ hg init repo
+  $ cd repo
+  $ mkcommit a
+  $ mkcommit b
+
+test disabling commands
+
+  $ cat >> .hg/hgrc <<EOF
+  > [experimental]
+  > evolution=createmarkers
+  >   allowunstable
+  >   exchange
+  > EOF
+  $ hg prune | head -n 2
+  hg: unknown command 'prune'
+  Mercurial Distributed SCM
+  
--- a/tests/test-prev-next.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-prev-next.t	Thu Jun 25 16:55:27 2015 -0700
@@ -82,3 +82,112 @@
   $ hg bookmarks
      mark                      2:4e26ef31f919
      no-move                   2:4e26ef31f919
+
+
+Behavior with local modification
+--------------------------------
+
+  $ echo foo > modified-bar
+  $ hg add modified-bar
+  $ hg prev
+  abort: uncommitted changes
+  (do you want --merge?)
+  [255]
+  $ hg prev --merge
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [0] added a
+  $ hg next
+  abort: uncommitted changes
+  (do you want --merge?)
+  [255]
+  $ hg next --merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  [1] added b
+
+Behavior with aspiring children
+-------------------------------
+
+  $ hg revert --all
+  forgetting modified-bar
+  $ hg log -G
+  o  changeset:   2:4e26ef31f919
+  |  bookmark:    mark
+  |  bookmark:    no-move
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     added c
+  |
+  @  changeset:   1:6e742c9127b3
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     added b
+  |
+  o  changeset:   0:a154386e50d1
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     added a
+  
+
+no children of any kind
+
+  $ hg next
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  [2] added c
+  $ hg next
+  no children
+  [1]
+  $ hg prev
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [1] added b
+
+some aspiring children
+
+  $ hg amend -m 'added b (2)'
+  1 new unstable changesets
+  $ hg next
+  no children
+  (1 unstable changesets to be evolved here, do you want --evolve?)
+  [1]
+  $ hg next --evolve
+  move:[2] added c
+  atop:[3] added b (2)
+  working directory now at e3b6d5df389b
+
+next with ambiguity
+
+  $ hg prev
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [3] added b (2)
+  $ echo d > d
+  $ hg add d
+  $ hg commit -m 'added d'
+  created new head
+  $ hg prev
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [3] added b (2)
+  $ hg next
+  ambigious next changeset:
+  [4] added c
+  [5] added d
+  explicitly update to one of them
+  [1]
+
+next with ambiguity in aspiring children
+
+  $ hg am -m 'added b (3)'
+  2 new unstable changesets
+  $ hg next
+  no children
+  (2 unstable changesets to be evolved here, do you want --evolve?)
+  [1]
+  $ hg next --evolve
+  ambigious next (unstable) changeset:
+  [4] added c
+  [5] added d
+  (run "hg evolve --rev REV" on one of them)
+  [1]
+  $ hg evolve -r 5
+  move:[5] added d
+  atop:[6] added b (3)
+  working directory is now at 47ea25be8aea
--- a/tests/test-prune.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-prune.t	Thu Jun 25 16:55:27 2015 -0700
@@ -38,9 +38,9 @@
 prune current and tip changeset
 
   $ hg prune --user blah --date '1979-12-15' .
-  1 changesets pruned
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory now at 47d2a3944de8
+  1 changesets pruned
   $ hg bookmark
    * BABAR                     3:47d2a3944de8
   $ hg debugobsolete
@@ -59,9 +59,9 @@
 pruning multiple changeset at once
 
   $ hg prune 2:
-  2 changesets pruned
   0 files updated, 0 files merged, 3 files removed, 0 files unresolved
   working directory now at 1f0dee641bb7
+  2 changesets pruned
   $ hg debugobsolete
   9d206ffc875e1bc304590549be293be36821e66c 0 {47d2a3944de8b013de3be9578e8e344ea2e6c097} (Sat Dec 15 00:00:00 1979 +0000) {'user': 'blah'}
   7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {1f0dee641bb7258c56bd60e93edfa2405381c41e} (*) {'user': 'test'} (glob)
@@ -120,9 +120,9 @@
   $ hg up 'desc("add ee")'
   4 files updated, 0 files merged, 4 files removed, 0 files unresolved
   $ hg prune 'desc("add ee")' -s 'desc("add nE")'
-  1 changesets pruned
   4 files updated, 0 files merged, 4 files removed, 0 files unresolved
   working directory now at 6e8148413dd5
+  1 changesets pruned
   $ hg debugobsolete
   9d206ffc875e1bc304590549be293be36821e66c 0 {47d2a3944de8b013de3be9578e8e344ea2e6c097} (Sat Dec 15 00:00:00 1979 +0000) {'user': 'blah'}
   7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {1f0dee641bb7258c56bd60e93edfa2405381c41e} (*) {'user': 'test'} (glob)
@@ -210,9 +210,9 @@
   $ mkcommit n2
 
   $ hg prune 'desc("add n1")::desc("add n2")' -s 'desc("add nD")::desc("add nE")' --biject
-  2 changesets pruned
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   working directory now at 1f0dee641bb7
+  2 changesets pruned
   $ hg debugobsolete
   9d206ffc875e1bc304590549be293be36821e66c 0 {47d2a3944de8b013de3be9578e8e344ea2e6c097} (Sat Dec 15 00:00:00 1979 +0000) {'user': 'blah'}
   7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {1f0dee641bb7258c56bd60e93edfa2405381c41e} (*) {'user': 'test'} (glob)
@@ -225,6 +225,35 @@
   cb7f8f706a6532967b98cf8583a81baab79a0fa7 8ee176ff1d4b2034ce51e3efc579c2de346b631d 0 (*) {'user': 'test'} (glob)
   21b6f2f1cece8c10326e575dd38239189d467190 6e8148413dd541855b72a920a90c06fca127c7e7 0 (*) {'user': 'test'} (glob)
 
+test hg strip replacement
+
+  $ hg up 10
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ mkcommit n1
+  created new head
+  $ mkcommit n2
+  $ hg --config extensions.strip= --config experimental.prunestrip=True strip -r .
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory now at c7e58696a948
+  1 changesets pruned
+  $ hg --config extensions.strip= --config experimental.prunestrip=True strip -r . --bundle
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/c7e58696a948-69ca36d3-backup.hg (glob)
+
+test hg prune --keep
+  $ mkcommit n1
+  created new head
+  $ hg diff -r .^
+  diff -r aa96dc3f04c2 n1
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/n1	* +0000 (glob)
+  @@ -0,0 +1,1 @@
+  +n1
+  $ hg prune -r . --keep
+  1 changesets pruned
+  $ hg status
+  ? n1
+
 test hg prune -B bookmark
 yoinked from test-mq-strip.t
 
@@ -245,11 +274,11 @@
   [255]
   $ hg tag --remove --local a
   $ hg prune -B todelete
-  1 changesets pruned
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark todelete)
   working directory now at d62d843c9a01
   bookmark 'todelete' deleted
+  1 changesets pruned
   $ hg id -ir dcbb326fdec2
   abort: hidden revision 'dcbb326fdec2'!
   (use --hidden to access hidden revisions)
@@ -260,8 +289,8 @@
      B                         10:ff43616e5d0f
      delete                    6:2702dd0c91e7
   $ hg prune -B delete
+  bookmark 'delete' deleted
   3 changesets pruned
-  bookmark 'delete' deleted
   $ hg tag --remove --local c
   $ hg id -ir 6:2702dd0c91e7
   abort: hidden revision '6'!
@@ -332,3 +361,12 @@
   |/
   o  0:1ea73414a91b[] (stable/draft) r0
   
+  $ hg book CELESTE
+  $ hg prune -r . --keep
+  1 changesets pruned
+  $ hg book
+     B                         8:d62d843c9a01
+   * CELESTE                   8:d62d843c9a01
+     r10                       8:d62d843c9a01
+     rg                        8:d62d843c9a01
+
--- a/tests/test-sharing.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-sharing.t	Thu Jun 25 16:55:27 2015 -0700
@@ -140,7 +140,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  pushing 4 obsolescence markers (* bytes) (glob)
+  pushing 4 obsolescence markers (369 bytes)
   4 obsolescence markers added
 
 Now that the fix is public, we cannot amend it any more.
@@ -161,7 +161,7 @@
   pushing to ../dev-repo
   searching for changes
   no changes found
-  pushing 4 obsolescence markers (* bytes) (glob)
+  pushing 4 obsolescence markers (369 bytes)
   0 obsolescence markers added
   [1]
   $ hg -R ../dev-repo shortlog -r 'draft()'
@@ -196,7 +196,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  pushing 4 obsolescence markers (* bytes) (glob)
+  pushing 4 obsolescence markers (369 bytes)
   0 obsolescence markers added
   exporting bookmark bug15
   $ hg -R ../review bookmarks
@@ -213,7 +213,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  pushing 6 obsolescence markers (* bytes) (glob)
+  pushing 6 obsolescence markers (553 bytes)
   2 obsolescence markers added
   updating bookmark bug15
   $ hg -R ../review bookmarks
@@ -241,7 +241,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  pushing 4 obsolescence markers (* bytes) (glob)
+  pushing 4 obsolescence markers (369 bytes)
   0 obsolescence markers added
   exporting bookmark featureX
   $ hg -R ../review bookmarks
@@ -259,7 +259,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  pushing 6 obsolescence markers (* bytes) (glob)
+  pushing 6 obsolescence markers (553 bytes)
   2 obsolescence markers added
   updating bookmark featureX
 
@@ -274,7 +274,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  pushing 8 obsolescence markers (* bytes) (glob)
+  pushing 8 obsolescence markers (736 bytes)
   4 obsolescence markers added
   $ hg -R ../public bookmarks
   no bookmarks set
@@ -286,7 +286,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  pushing 8 obsolescence markers (* bytes) (glob)
+  pushing 8 obsolescence markers (736 bytes)
   2 obsolescence markers added
   updating bookmark featureX
   $ hg -R ../review bookmarks
@@ -388,7 +388,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  pushing 11 obsolescence markers (* bytes) (glob)
+  pushing 11 obsolescence markers (999 bytes)
   3 obsolescence markers added
   $ hg push ../review
   pushing to ../review
@@ -397,7 +397,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 1 files
-  pushing 11 obsolescence markers (* bytes) (glob)
+  pushing 11 obsolescence markers (999 bytes)
   1 obsolescence markers added
   updating bookmark bug15
 
@@ -511,7 +511,7 @@
   7:e3f99ce9d9cd  draft  fix bug 24 (v2 by alice)
 
 Use evolve to fix the divergence.
-  $ HGMERGE=internal:other hg evolve
+  $ HGMERGE=internal:other hg evolve --divergent
   merge:[6] fix bug 24 (v2 by bob)
   with: [7] fix bug 24 (v2 by alice)
   base: [4] fix bug 24 (v1)
--- a/tests/test-simple4server-bundle2.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-simple4server-bundle2.t	Thu Jun 25 16:55:27 2015 -0700
@@ -72,9 +72,9 @@
 ===================
 
   $ wget -q -O - http://localhost:$HGPORT/?cmd=hello
-  capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon
+  capabilities: * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (glob)
   $ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol)
+  * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol) (glob)
 
   $ wget -q -O - "http://localhost:$HGPORT/?cmd=listkeys&namespace=namespaces" | sort
   bookmarks	
@@ -134,13 +134,13 @@
   obsolete	
   phases	
   $ wget -q -O - http://localhost:$HGPORT/?cmd=hello
-  capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon
+  capabilities: * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (glob)
   $ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol)
+  * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol) (glob)
 
   $ echo '[__temporary__]' >> server/.hg/hgrc
   $ echo 'advertiseobsolete=False' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py
+  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
   $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
@@ -148,13 +148,9 @@
   bookmarks	
   namespaces	
   phases	
-  $ wget -q -O - http://localhost:$HGPORT/?cmd=hello
-  capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
-  $ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 (no-eol)
 
   $ echo 'advertiseobsolete=True' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py
+  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
   $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
@@ -163,7 +159,8 @@
   namespaces	
   obsolete	
   phases	
+
   $ wget -q -O - http://localhost:$HGPORT/?cmd=hello
-  capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon
+  capabilities: * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (glob)
   $ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol)
+  * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol) (glob)
--- a/tests/test-simple4server.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-simple4server.t	Thu Jun 25 16:55:27 2015 -0700
@@ -7,6 +7,8 @@
   > allow_push = *
   > [phases]
   > publish = False
+  > [experimental]
+  > bundle2-exp=False
   > [extensions]
   > EOF
 
@@ -72,9 +74,9 @@
 ===================
 
   $ wget -q -O - http://localhost:$HGPORT/?cmd=hello
-  capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon
+  capabilities: * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (glob)
   $ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol)
+  * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol) (glob)
 
   $ wget -q -O - "http://localhost:$HGPORT/?cmd=listkeys&namespace=namespaces" | sort
   bookmarks	
@@ -135,13 +137,13 @@
   obsolete	
   phases	
   $ wget -q -O - http://localhost:$HGPORT/?cmd=hello
-  capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon
+  capabilities: * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (glob)
   $ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol)
+  * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol) (glob)
 
   $ echo '[__temporary__]' >> server/.hg/hgrc
   $ echo 'advertiseobsolete=False' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py
+  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
   $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
@@ -149,13 +151,13 @@
   bookmarks	
   namespaces	
   phases	
-  $ wget -q -O - http://localhost:$HGPORT/?cmd=hello
-  capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
-  $ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 (no-eol)
+  $ wget -q -O - http://localhost:$HGPORT/?cmd=hello | grep _evoext_pushobsmarkers_0
+  [1]
+  $ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities | grep _evoext_pushobsmarkers_0
+  [1]
 
   $ echo 'advertiseobsolete=True' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py
+  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
   $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
@@ -165,6 +167,6 @@
   obsolete	
   phases	
   $ wget -q -O - http://localhost:$HGPORT/?cmd=hello
-  capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon
+  capabilities: * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (glob)
   $ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol)
+  * _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon (no-eol) (glob)
--- a/tests/test-stabilize-order.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-stabilize-order.t	Thu Jun 25 16:55:27 2015 -0700
@@ -153,8 +153,8 @@
   $ hg up 9
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg evolve -v
-  nothing to evolve here
-  (1 troubled changesets, do you want --any ?)
+  nothing to evolve on current working copy parent
+  (1 other unstable in the repository, do you want --any or --rev)
   [2]
   $ hg evolve --any -v
   move:[9] addc
@@ -180,5 +180,68 @@
   o  0:c471ef929e6a@default(draft) addroot
   
   $ hg evolve --any -v
-  no troubled changesets
+  no unstable changesets to evolve
   [1]
+
+Ambiguous evolution
+  $ echo a > k
+  $ hg add k
+  $ hg ci -m firstambiguous
+  $ hg up .^
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo a > l
+  $ hg add l
+  $ hg ci -m secondambiguous
+  created new head
+  $ hg up .^
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg commit --amend -m "newmessage"
+  2 new unstable changesets
+  $ hg log -G
+  @  changeset:   15:49773ccde390
+  |  tag:         tip
+  |  parent:      11:036cf654e942
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     newmessage
+  |
+  | o  changeset:   14:a9892777b519
+  | |  parent:      12:e99ecf51c867
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     secondambiguous
+  | |
+  | | o  changeset:   13:0b6e26b2472d
+  | |/   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     firstambiguous
+  | |
+  | x  changeset:   12:e99ecf51c867
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     addc
+  |
+  o  changeset:   11:036cf654e942
+  |  parent:      7:005fe5914f78
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     addb
+  |
+  o  changeset:   7:005fe5914f78
+  |  parent:      0:c471ef929e6a
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     adda
+  |
+  o  changeset:   0:c471ef929e6a
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     addroot
+  
+  $ hg evolve
+  abort: multiple evolve candidates
+  (select one of *, * with --rev) (glob)
+  [255]
+
+
+
--- a/tests/test-stabilize-result.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-stabilize-result.t	Thu Jun 25 16:55:27 2015 -0700
@@ -158,20 +158,20 @@
 
 Stabilize!
 
-  $ hg evolve --any --dry-run
+  $ hg evolve --any --dry-run --bumped
   recreate:[12] newer a
   atop:[8] newer a
   hg rebase --rev (73b15c7566e9|d5c7ef82d003) --dest 66719795a494; (re)
   hg update 1cf0aacfd363;
   hg revert --all --rev (73b15c7566e9|d5c7ef82d003); (re)
   hg commit --msg "bumped update to %s" (no-eol)
-  $ hg evolve --any --confirm
+  $ hg evolve --any --confirm --bumped
   recreate:[12] newer a
   atop:[8] newer a
   perform evolve? [Ny] n
   abort: evolve aborted by user
   [255]
-  $ echo y | hg evolve --any --confirm --config ui.interactive=True
+  $ echo y | hg evolve --any --confirm --config ui.interactive=True --bumped
   recreate:[12] newer a
   atop:[8] newer a
   perform evolve? [Ny] y
@@ -224,6 +224,7 @@
   $ hg up --hidden 15
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory parent is obsolete!
+  (use "hg evolve" to update to its successor)
   $ mv a a.old
   $ echo 'jungle' > a
   $ cat a.old >> a
@@ -248,14 +249,14 @@
 
 Stabilize it
 
-  $ hg evolve -qn --confirm
+  $ hg evolve -qn --confirm --divergent
   merge:[19] More addition
   with: [17] More addition
   base: [15] More addition
   perform evolve? [Ny] n
   abort: evolve aborted by user
   [255]
-  $ echo y | hg evolve -qn --confirm --config ui.interactive=True
+  $ echo y | hg evolve -qn --confirm --config ui.interactive=True --divergent
   merge:[19] More addition
   with: [17] More addition
   base: [15] More addition
@@ -266,7 +267,7 @@
   hg up -C 3932c176bbaa &&
   hg revert --all --rev tip &&
   hg commit -m "`hg log -r eacc9c8240fe --template={desc}`";
-  $ hg evolve -v
+  $ hg evolve -v --divergent
   merge:[19] More addition
   with: [17] More addition
   base: [15] More addition
@@ -335,6 +336,7 @@
   $ hg up --hidden 15
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory parent is obsolete!
+  (use "hg evolve" to update to its successor)
   $ echo 'gotta break' >> a
   $ hg amend
   2 new divergent changesets
@@ -343,14 +345,14 @@
   $ hg phase 'divergent()'
   21: draft
   24: draft
-  $ hg evolve -qn
+  $ hg evolve -qn --divergent
   hg update -c 0b336205a5d0 &&
   hg merge f344982e63c4 &&
   hg commit -m "auto merge resolving conflict between 0b336205a5d0 and f344982e63c4"&&
   hg up -C 3932c176bbaa &&
   hg revert --all --rev tip &&
   hg commit -m "`hg log -r 0b336205a5d0 --template={desc}`";
-  $ hg evolve
+  $ hg evolve --divergent
   merge:[24] More addition (2)
   with: [21] More addition
   base: [15] More addition
--- a/tests/test-touch.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-touch.t	Thu Jun 25 16:55:27 2015 -0700
@@ -34,6 +34,7 @@
   $ hg up --hidden 1
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory parent is obsolete!
+  (use "hg evolve" to update to its successor)
   $ hg log -G
   o  3:[0-9a-f]{12} ab (re)
   
--- a/tests/test-tutorial.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-tutorial.t	Thu Jun 25 16:55:27 2015 -0700
@@ -289,9 +289,9 @@
 not fit well in my standard shopping list)
 
   $ hg prune . # "." is for working directory parent
-  1 changesets pruned
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory now at 41aff6a42b75
+  1 changesets pruned
 
 The silly changeset is gone.
 
@@ -406,7 +406,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
-  pushing 6 obsolescence markers (* bytes) (glob)
+  pushing 6 obsolescence markers (529 bytes)
   6 obsolescence markers added
 
 for simplicity sake we get the bathroom change in line again
@@ -738,7 +738,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
-  pushing 10 obsolescence markers (* bytes) (glob)
+  pushing 10 obsolescence markers (873 bytes)
   3 obsolescence markers added
 
 remote get a warning that current working directory is based on an obsolete changeset
@@ -751,12 +751,14 @@
   pull obsolescence markers
   0 obsolescence markers added
   working directory parent is obsolete!
+  (use "hg evolve" to update to its successor)
 
 now let's see where we are, and update to the successor
 
   $ hg parents
   bf1b0d202029 (draft): animals
   working directory parent is obsolete!
+  (use "hg evolve" to update to its successor)
   $ hg evolve
   update:[8] animals
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -804,9 +806,9 @@
 In the mean time I noticed you can't buy animals in a super market and I prune the animal changeset:
 
   $ hg prune ee942144f952
-  1 changesets pruned
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory now at a44c85f957d3
+  1 changesets pruned
   1 new unstable changesets
 
 
--- a/tests/test-uncommit.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-uncommit.t	Thu Jun 25 16:55:27 2015 -0700
@@ -241,6 +241,7 @@
   8 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (leaving bookmark touncommit-bm)
   working directory parent is obsolete!
+  (use "hg evolve" to update to its successor)
   $ hg --config extensions.purge= purge
   $ hg uncommit -I 'set:added() and e'
   2 new divergent changesets
@@ -285,6 +286,7 @@
   $ hg up -C 3 --hidden
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory parent is obsolete!
+  (use "hg evolve" to update to its successor)
   $ hg --config extensions.purge= purge
   $ hg uncommit --all -X e
   1 new divergent changesets
--- a/tests/test-userguide.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-userguide.t	Thu Jun 25 16:55:27 2015 -0700
@@ -66,9 +66,9 @@
   $ echo 'debug hack' >> file1.c
   $ hg commit -m 'debug hack'
   $ hg prune .
-  1 changesets pruned
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory now at 934359450037
+  1 changesets pruned
   $ hg parents --template '{rev}:{node|short}  {desc|firstline}\n'
   3:934359450037  implement feature Y
   $ hg --hidden shortlog -G -r 3:
@@ -219,7 +219,7 @@
   |
   o  18:1f33e68b18b9  draft  useful work
   |
-  $ hg evolve -q --all
+  $ hg evolve -q --all --any
   $ hg --hidden shortlog -G -r 18::
   @  21:4393e5877437  draft  more work
   |
@@ -251,7 +251,7 @@
   $ hg status
   M file2.c
   $ hg revert file2.c
-  $ hg evolve --all
+  $ hg evolve --all --any
   move:[23] fix bug 67
   atop:[24] fix bug 53
   working directory is now at 0d972d6888e6
@@ -300,7 +300,7 @@
   |/
   o  25:0d972d6888e6  draft  fix bug 67
   |
-  $ hg evolve --all
+  $ hg evolve --all --any
   move:[27] new feature
   atop:[28] fix a bug
   working directory is now at 166c1c368ab6
--- a/tests/test-wireproto-bundle1.t	Tue Jun 23 16:50:06 2015 -0700
+++ b/tests/test-wireproto-bundle1.t	Thu Jun 25 16:55:27 2015 -0700
@@ -70,7 +70,7 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files (+1 heads)
-  pushing 2 obsolescence markers (* bytes) (glob)
+  pushing 2 obsolescence markers (184 bytes)
   remote: 2 obsolescence markers added
   $ hg push
   pushing to ssh://user@dummy/server