changeset 1579:4f83b2d2d20d

merge with stable
author Pierre-Yves David <pierre-yves.david@fb.com>
date Sun, 03 Jan 2016 16:47:57 +0100
parents 526253198860 (diff) ec4fae88831d (current diff)
children b915e0d54db0
files
diffstat 25 files changed, 719 insertions(+), 1719 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sun Jan 03 15:51:36 2016 +0100
+++ b/Makefile	Sun Jan 03 16:47:57 2016 +0100
@@ -1,5 +1,3 @@
-PYTHON=python
-HG=`which hg`
 VERSION=$(shell python setup.py --version)
 
 
@@ -11,21 +9,6 @@
 
 all: help
 
-tests:
-	cd tests && $(PYTHON) run-tests.py --with-hg=$(HG) $(TESTFLAGS)
-
-test-%:
-	cd tests && $(PYTHON) run-tests.py --with-hg=$(HG) $(TESTFLAGS) $@
-
-tests-%:
-	@echo "Path to crew repo is $(CREW) - set this with CREW= if needed."
-	hg -R $(CREW) checkout $$(echo $@ | sed s/tests-//) && \
-	(cd $(CREW) ; $(MAKE) clean ) && \
-	cd tests && $(PYTHON) $(CREW)/tests/run-tests.py $(TESTFLAGS)
-
-all-version-tests: tests-1.3.1 tests-1.4.3 tests-1.5.4 \
-                   tests-1.6.4 tests-1.7.5 tests-1.8 tests-tip
-
 deb-prepare:
 	python setup.py sdist --dist-dir ..
 	mv -f ../hg-evolve-$(VERSION).tar.gz ../mercurial-evolve_$(VERSION).orig.tar.gz
@@ -34,5 +17,3 @@
 	mv hg-evolve-$(VERSION) ../mercurial-evolve_$(VERSION).orig
 	cp -r debian/ ../mercurial-evolve_$(VERSION).orig/
 	@cd ../mercurial-evolve_$(VERSION).orig && echo 'debian build directory ready at' `pwd`
-
-.PHONY: tests all-version-tests
--- a/README	Sun Jan 03 15:51:36 2016 +0100
+++ b/README	Sun Jan 03 16:47:57 2016 +0100
@@ -58,7 +58,11 @@
 
 5.3.0 --
 
-- split: add a new command to split changesets
+- split: add a new command to split changesets,
+- tests: drop our copy of 'run-tests.py' use core one instead,
+- bookmark: do all bookmark movement within a transaction.
+- evolve: compatibility with Mercurial 3.7
+- evolve: support merge with a single obsolete parent.
 
 5.2.2 --
 
--- a/debian/rules	Sun Jan 03 15:51:36 2016 +0100
+++ b/debian/rules	Sun Jan 03 16:47:57 2016 +0100
@@ -8,9 +8,13 @@
 	dh_auto_build
 	$(MAKE) -C docs
 
+hgsrc_defined:
+	# Use "! -z" instead of "-n", because "-n" without arguments is true
+	test ! -z $(HGSRC) && test -d $(HGSRC) || (echo "$(HGSRC) is not a directory"; false)
+
 ifeq (,$(filter nocheck, $(DEB_BUILD_OPTIONS)))
-override_dh_auto_test:
-	cd tests &&  python run-tests.py --with-hg=`which hg` --blacklist=$(CURDIR)/debian/test-blacklist
+override_dh_auto_test: hgsrc_defined
+	cd tests && python $(HGSRC)/tests/run-tests.py --with-hg=$(HGSRC)/hg --blacklist=$(CURDIR)/debian/test-blacklist
 endif
 
 override_dh_python2:
--- a/hgext/directaccess.py	Sun Jan 03 15:51:36 2016 +0100
+++ b/hgext/directaccess.py	Sun Jan 03 16:47:57 2016 +0100
@@ -1,5 +1,5 @@
 """ This extension provides direct access
-It is the ability to refer and access hidden sha in commands provided that you 
+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
@@ -72,7 +72,8 @@
 
 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 """
+    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'
@@ -131,7 +132,7 @@
 
 _listtuple = ('symbol', '_list')
 
-def gethashsymbols(tree):
+def gethashsymbols(tree, maxrev):
     # 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:
@@ -139,8 +140,12 @@
 
     if len(tree) == 2 and tree[0] == "symbol":
         try:
-            int(tree[1])
-            return []
+            n = int(tree[1])
+            # This isn't necessarily a rev number, could be a hash prefix
+            if n > maxrev:
+                return [tree[1]]
+            else:
+                return []
         except ValueError as e:
             if hashre.match(tree[1]):
                 return [tree[1]]
@@ -155,7 +160,7 @@
     elif len(tree) >= 3:
         results = []
         for subtree in tree[1:]:
-            results += gethashsymbols(subtree)
+            results += gethashsymbols(subtree, maxrev)
         return results
     else:
         return []
@@ -171,8 +176,8 @@
     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
+        repo.symbols = gethashsymbols(tree, len(cl))
         for node in repo.symbols:
             try:
                 node = cl._partialmatch(node)
@@ -185,8 +190,8 @@
         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]) 
+                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/drophack.py	Sun Jan 03 15:51:36 2016 +0100
+++ b/hgext/drophack.py	Sun Jan 03 16:47:57 2016 +0100
@@ -150,7 +150,8 @@
                 stripmarker(ui, repo, markers)
         # strip the changeset
         with timed(ui, 'strip nodes'):
-            repair.strip(ui, repo, list(allnodes), backup="all", topic='drophack')
+            repair.strip(ui, repo, list(allnodes), backup="all",
+                         topic='drophack')
 
     finally:
         lockmod.release(lock, wlock)
--- a/hgext/evolve.py	Sun Jan 03 15:51:36 2016 +0100
+++ b/hgext/evolve.py	Sun Jan 03 16:47:57 2016 +0100
@@ -86,7 +86,7 @@
 # Flags for enabling optional parts of evolve
 commandopt = 'allnewcommands'
 
-from mercurial import bookmarks
+from mercurial import bookmarks as bookmarksmod
 from mercurial import cmdutil
 from mercurial import commands
 from mercurial import context
@@ -124,7 +124,8 @@
     def memfilectx(repo, *args, **kwargs):
         return oldmemfilectx(*args, **kwargs)
 else:
-    raise ImportError('evolve needs version %s or above' % min(testedwith.split()))
+    raise ImportError('evolve needs version %s or above' %
+                      min(testedwith.split()))
 
 aliases, entry = cmdutil.findcmd('commit', commands.table)
 hasinteractivemode = any(['interactive' in e for e in entry[1]])
@@ -415,8 +416,8 @@
             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))
+                msg = _('ambiguous command specification: "%s" matches %r')
+                raise error.Abort(msg % (cmd, matchingevolvecommands))
             else:
                 whitelist.add(matchingevolvecommands[0])
         for disabledcmd in set(cmdtable) - whitelist:
@@ -747,7 +748,7 @@
     """
     try:
         return orig(repo, *args, **opts)
-    except util.Abort, ex:
+    except error.Abort as ex:
         hint = _("use 'hg evolve' to get a stable history "
                  "or --force to ignore warnings")
         if (len(ex.args) >= 1
@@ -789,13 +790,13 @@
         if rebase:
             extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
     except KeyError:
-        pass  # rebase not found
+        pass # rebase not found
     try:
         histedit = extensions.find('histedit')
         if histedit:
             extensions.wrapcommand(histedit.cmdtable, 'histedit', warnobserrors)
     except KeyError:
-        pass  # histedit not found
+        pass # histedit not found
 
 #####################################################################
 ### Old Evolve extension content                                  ###
@@ -890,24 +891,27 @@
         tr.close()
         return newid, created
     finally:
-        lockmod.release(lock, wlock, tr)
-
-class MergeFailure(util.Abort):
+        lockmod.release(tr, lock, wlock)
+
+class MergeFailure(error.Abort):
     pass
 
-def relocate(repo, orig, dest, keepbranch=False):
+def relocate(repo, orig, dest, pctx=None, keepbranch=False):
     """rewrite <rev> on dest"""
     if orig.rev() == dest.rev():
-        raise util.Abort(_('tried to relocate a node on top of itself'),
+        raise error.Abort(_('tried to relocate a node on top of itself'),
                          hint=_("This shouldn't happen. If you still "
                                 "need to move changesets, please do so "
                                 "manually with nothing to rebase - working "
                                 "directory parent is also destination"))
 
-    if not orig.p2().rev() == node.nullrev:
-        raise util.Abort(
-            'no support for evolving merge changesets yet',
-            hint="Redo the merge and use `hg prune <old> --succ <new>` to obsolete the old one")
+    if pctx is None:
+        if len(orig.parents()) == 2:
+            raise error.Abort(_("tried to relocate a merge commit without "
+                                "specifying which parent should be moved"),
+                              hint=_("Specify the parent by passing in pctx"))
+        pctx = orig.p1()
+
     destbookmarks = repo.nodebookmarks(dest.node())
     nodesrc = orig.node()
     destphase = repo[nodesrc].phase()
@@ -947,27 +951,24 @@
             bmdeactivate(repo)
             if keepbranch:
                 repo.dirstate.setbranch(orig.branch())
-            r = merge.graft(repo, orig, orig.p1(), ['local', 'graft'])
+
+            try:
+                r = merge.graft(repo, orig, pctx, ['local', 'graft'], True)
+            except TypeError:
+                # not using recent enough mercurial
+                if len(orig.parents()) == 2:
+                    raise error.Abort(
+                        _("no support for evolving merge changesets yet"),
+                        hint=_("Redo the merge and use `hg prune <old> --succ "
+                               "<new>` to obsolete the old one"))
+
+                r = merge.graft(repo, orig, pctx, ['local', 'graft'])
+
             if r[-1]:  #some conflict
-                raise util.Abort(
+                raise error.Abort(
                         'unresolved merge conflicts (see hg help resolve)')
-            if commitmsg is None:
-                commitmsg = orig.description()
-            extra = dict(orig.extra())
-            if 'branch' in extra:
-                del extra['branch']
-            extra['rebase_source'] = orig.hex()
-
-            backup = repo.ui.backupconfig('phases', 'new-commit')
-            try:
-                targetphase = max(orig.phase(), phases.draft)
-                repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
-                # Commit might fail if unresolved files exist
-                nodenew = repo.commit(text=commitmsg, user=orig.user(),
-                                      date=orig.date(), extra=extra)
-            finally:
-                repo.ui.restoreconfig(backup)
-        except util.Abort, exc:
+            nodenew = _relocatecommit(repo, orig, commitmsg)
+        except error.Abort as exc:
             repo.dirstate.beginparentchange()
             repo.setparents(repo['.'].node(), nullid)
             writedirstate(repo.dirstate, tr)
@@ -979,20 +980,7 @@
             exc.__class__ = LocalMergeFailure
             raise
         oldbookmarks = repo.nodebookmarks(nodesrc)
-        if nodenew is not None:
-            phases.retractboundary(repo, tr, destphase, [nodenew])
-            obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))])
-            for book in oldbookmarks:
-                repo._bookmarks[book] = nodenew
-        else:
-            obsolete.createmarkers(repo, [(repo[nodesrc], ())])
-            # Behave like rebase, move bookmarks to dest
-            for book in oldbookmarks:
-                repo._bookmarks[book] = dest.node()
-        for book in destbookmarks: # restore bookmark that rebase move
-            repo._bookmarks[book] = dest.node()
-        if oldbookmarks or destbookmarks:
-            repo._bookmarks.recordchange(tr)
+        _finalizerelocate(repo, orig, dest, nodenew, tr)
         tr.close()
     finally:
         tr.release()
@@ -1016,14 +1004,14 @@
 ### bookmarks api compatibility layer ###
 def bmdeactivate(repo):
     try:
-        return bookmarks.deactivate(repo)
+        return bookmarksmod.deactivate(repo)
     except AttributeError:
-        return bookmarks.unsetcurrent(repo)
+        return bookmarksmod.unsetcurrent(repo)
 def bmactivate(repo, book):
     try:
-        return bookmarks.activate(repo, book)
+        return bookmarksmod.activate(repo, book)
     except AttributeError:
-        return bookmarks.setcurrent(repo, book)
+        return bookmarksmod.setcurrent(repo, book)
 
 def bmactive(repo):
     try:
@@ -1156,7 +1144,7 @@
                     store.create(tr, mark[0], mark[1], mark[2], marks[3],
                                  parents=parents)
                     if len(store._all) - before:
-                        ui.write('created new markers for %i\n' % rev)
+                        ui.write(_('created new markers for %i\n') % rev)
             ui.progress(pgop, idx, total=pgtotal)
         tr.close()
         ui.progress(pgop, None)
@@ -1186,7 +1174,7 @@
     store = repo.obsstore
     unfi = repo.unfiltered()
     nm = unfi.changelog.nodemap
-    ui.write('markers total:              %9i\n' % len(store._all))
+    ui.write(_('markers total:              %9i\n') % len(store._all))
     sucscount = [0, 0 , 0, 0]
     known = 0
     parentsdata = 0
@@ -1195,7 +1183,7 @@
     #   a cluster is a (set(nodes), set(markers)) tuple
     clustersmap = {}
     # same data using parent information
-    pclustersmap= {}
+    pclustersmap = {}
     for mark in store:
         if mark[0] in nm:
             known += 1
@@ -1228,47 +1216,50 @@
         fc = (frozenset(c[0]), frozenset(c[1]))
         for n in fc[0]:
             pclustersmap[n] = fc
-    ui.write('    for known precursors:   %9i\n' % known)
-    ui.write('    with parents data:      %9i\n' % parentsdata)
+    ui.write(('    for known precursors:   %9i\n' % known))
+    ui.write(('    with parents data:      %9i\n' % parentsdata))
     # successors data
-    ui.write('markers with no successors: %9i\n' % sucscount[0])
-    ui.write('              1 successors: %9i\n' % sucscount[1])
-    ui.write('              2 successors: %9i\n' % sucscount[2])
-    ui.write('    more than 2 successors: %9i\n' % sucscount[3])
+    ui.write(('markers with no successors: %9i\n' % sucscount[0]))
+    ui.write(('              1 successors: %9i\n' % sucscount[1]))
+    ui.write(('              2 successors: %9i\n' % sucscount[2]))
+    ui.write(('    more than 2 successors: %9i\n' % sucscount[3]))
     # meta data info
-    ui.write('    available  keys:\n')
+    ui.write(('    available  keys:\n'))
     for key in sorted(metakeys):
-        ui.write('    %15s:        %9i\n' % (key, metakeys[key]))
+        ui.write(('    %15s:        %9i\n' % (key, metakeys[key])))
 
     allclusters = list(set(clustersmap.values()))
     allclusters.sort(key=lambda x: len(x[1]))
-    ui.write('disconnected clusters:      %9i\n' % len(allclusters))
+    ui.write(('disconnected clusters:      %9i\n' % len(allclusters)))
 
     ui.write('        any known node:     %9i\n'
              % len([c for c in allclusters
                     if [n for n in c[0] if nm.get(n) is not None]]))
     if allclusters:
         nbcluster = len(allclusters)
-        ui.write('        smallest length:    %9i\n' % len(allclusters[0][1]))
-        ui.write('        longer length:      %9i\n' % len(allclusters[-1][1]))
+        ui.write(('        smallest length:    %9i\n' % len(allclusters[0][1])))
+        ui.write(('        longer length:      %9i\n'
+                 % len(allclusters[-1][1])))
         median = len(allclusters[nbcluster//2][1])
-        ui.write('        median length:      %9i\n' % median)
+        ui.write(('        median length:      %9i\n' % median))
         mean = sum(len(x[1]) for x in allclusters) // nbcluster
-        ui.write('        mean length:        %9i\n' % mean)
+        ui.write(('        mean length:        %9i\n' % mean))
     allpclusters = list(set(pclustersmap.values()))
     allpclusters.sort(key=lambda x: len(x[1]))
-    ui.write('    using parents data:     %9i\n' % len(allpclusters))
+    ui.write(('    using parents data:     %9i\n' % len(allpclusters)))
     ui.write('        any known node:     %9i\n'
              % len([c for c in allclusters
                     if [n for n in c[0] if nm.get(n) is not None]]))
     if allpclusters:
         nbcluster = len(allpclusters)
-        ui.write('        smallest length:    %9i\n' % len(allpclusters[0][1]))
-        ui.write('        longer length:      %9i\n' % len(allpclusters[-1][1]))
+        ui.write(('        smallest length:    %9i\n'
+                 % len(allpclusters[0][1])))
+        ui.write(('        longer length:      %9i\n'
+                 % len(allpclusters[-1][1])))
         median = len(allpclusters[nbcluster//2][1])
-        ui.write('        median length:      %9i\n' % median)
+        ui.write(('        median length:      %9i\n' % median))
         mean = sum(len(x[1]) for x in allpclusters) // nbcluster
-        ui.write('        mean length:        %9i\n' % mean)
+        ui.write(('        mean length:        %9i\n' % mean))
 
 def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category):
     """Resolve the troubles affecting one revision"""
@@ -1367,8 +1358,8 @@
         else:
             l = len(troubled[targetcat])
             if l:
-                hint = (_("%d other %s in the repository, do you want --any or --rev")
-                        % (l, targetcat))
+                hint = _("%d other %s in the repository, do you want --any "
+                        "or --rev") % (l, targetcat)
             else:
                 othertroubles = []
                 for cat in unselectedcategories:
@@ -1389,7 +1380,7 @@
 
 def _cleanup(ui, repo, startnode, showprogress):
     if showprogress:
-        ui.progress('evolve', None)
+        ui.progress(_('evolve'), None)
     if repo['.'] != startnode:
         ui.status(_('working directory is now at %s\n') % repo['.'])
 
@@ -1442,7 +1433,7 @@
         for p in repo[r].parents():
             try:
                 succ = _singlesuccessor(repo, p)
-            except MultipleSuccessorsError, exc:
+            except MultipleSuccessorsError as exc:
                 dependencies[r] = exc.successorssets
                 continue
             if succ in revs:
@@ -1476,7 +1467,8 @@
         if revopt:
             revs = scmutil.revrange(repo, revopt) & revs
         elif not anyopt and targetcat == 'unstable':
-            revs = set(_aspiringdescendant(repo, repo.revs('(.::) - obsolete()::')))
+            revs = set(_aspiringdescendant(repo,
+                                           repo.revs('(.::) - obsolete()::')))
         if targetcat == 'divergent':
             # Pick one divergent per group of divergents
             revs = _dedupedivergents(repo, revs)
@@ -1534,22 +1526,24 @@
         _('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', 'any', False,
+        _('also consider troubled changesets unrelated to current working '
+          'directory')),
     ('r', 'rev', [], _('solves troubles of these revisions')),
     ('', 'bumped', False, _('solves only bumped changesets')),
     ('', 'divergent', False, _('solves only divergent changesets')),
     ('', 'unstable', False, _('solves only unstable changesets (default)')),
-    ('a', 'all', False, _('evolve all troubled changesets related to the current '
-                         'working directory and its descendants')),
+    ('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 troubled changesets in your repository
 
-    Modifying history can lead to various types of troubled changesets: unstable,
-    bumped, or divergent. The evolve command resolves your troubles by executing one
-    of the following actions:
+    Modifying history can lead to various types of troubled changesets:
+    unstable, bumped, or divergent. The evolve command resolves your troubles
+    by executing one of the following actions:
 
     - update working copy to a successor
     - rebase an unstable changeset
@@ -1557,53 +1551,57 @@
     - fuse divergent changesets back together
 
     If you pass no arguments, evolve works in automatic mode: it will execute a
-    single action to reduce instability related to your working copy. There are two
-    cases for this action. First, if the parent of your working copy is obsolete,
-    evolve updates to the parent's successor. Second, if the working copy parent is
-    not obsolete but has obsolete predecessors, then evolve determines if there is an
-    unstable changeset that can be rebased onto the working copy parent in order to
-    reduce instability. If so, evolve rebases that changeset. If not, evolve refuses
-    to guess your intention, and gives a hint about what you might want to do next.
+    single action to reduce instability related to your working copy. There are
+    two cases for this action. First, if the parent of your working copy is
+    obsolete, evolve updates to the parent's successor. Second, if the working
+    copy parent is not obsolete but has obsolete predecessors, then evolve
+    determines if there is an unstable changeset that can be rebased onto the
+    working copy parent in order to reduce instability.
+    If so, evolve rebases that changeset. If not, evolve refuses to guess your
+    intention, and gives a hint about what you might want to do next.
 
     Any time evolve creates a changeset, it updates the working copy to the new
-    changeset. (Currently, every successful evolve operation involves an update as
-    well; this may change in future.)
+    changeset. (Currently, every successful evolve operation involves an update
+    as well; this may change in future.)
 
     Automatic mode only handles common use cases. For example, it avoids taking
-    action in the case of ambiguity, and it ignores unstable changesets that are not
-    related to your working copy. It also refuses to solve bumped or divergent
-    changesets unless you explicity request such behavior (see below).
+    action in the case of ambiguity, and it ignores unstable changesets that
+    are not related to your working copy.
+    It also refuses to solve bumped or divergent changesets unless you explicity
+    request such behavior (see below).
 
     Eliminating all instability around your working copy may require multiple
-    invocations of :hg:`evolve`. Alternately, use ``--all`` to recursively select and
-    evolve all unstable changesets that can be rebased onto the working copy parent.
+    invocations of :hg:`evolve`. Alternately, use ``--all`` to recursively
+    select and evolve all unstable changesets that can be rebased onto the
+    working copy parent.
     This is more powerful than successive invocations, since ``--all`` handles
-    ambiguous cases (e.g. unstable changesets with multiple children) by evolving all
-    branches.
-
-    When your repository cannot be handled by automatic mode, you might need to use
-    ``--rev`` to specify a changeset to evolve. For example, if you have an unstable
-    changeset that is not related to the working copy parent, you could use ``--rev``
-    to evolve it. Or, if some changeset has multiple unstable children, evolve in
-    automatic mode refuses to guess which one to evolve; you have to use ``--rev``
-    in that case.
+    ambiguous cases (e.g. unstable changesets with multiple children) by
+    evolving all branches.
+
+    When your repository cannot be handled by automatic mode, you might need to
+    use ``--rev`` to specify a changeset to evolve. For example, if you have
+    an unstable changeset that is not related to the working copy parent,
+    you could use ``--rev`` to evolve it. Or, if some changeset has multiple
+    unstable children, evolve in automatic mode refuses to guess which one to
+    evolve; you have to use ``--rev`` in that case.
 
     Alternately, ``--any`` makes evolve search for the next evolvable changeset
     regardless of whether it is related to the working copy parent.
 
-    You can supply multiple revisions to evolve multiple troubled changesets in a
-    single invocation. In revset terms, ``--any`` is equivalent to ``--rev
+    You can supply multiple revisions to evolve multiple troubled changesets
+    in a single invocation. In revset terms, ``--any`` is equivalent to ``--rev
     first(unstable())``. ``--rev`` and ``--all`` are mutually exclusive, as are
     ``--rev`` and ``--any``.
 
     ``hg evolve --any --all`` is useful for cleaning up instability across all
     branches, letting evolve figure out the appropriate order and destination.
 
-    When you have troubled changesets that are not unstable, :hg:`evolve` refuses to
-    consider them unless you specify the category of trouble you wish to resolve,
-    with ``--bumped`` or ``--divergent``. These options are currently mutually
-    exclusive with each other and with ``--unstable`` (the default). You can combine
-    ``--bumped`` or ``--divergent`` with ``--rev``, ``--all``, or ``--any``.
+    When you have troubled changesets that are not unstable, :hg:`evolve`
+    refuses to consider them unless you specify the category of trouble you
+    wish to resolve, with ``--bumped`` or ``--divergent``. These options are
+    currently mutually exclusive with each other and with ``--unstable``
+    (the default). You can combine ``--bumped`` or ``--divergent`` with
+    ``--rev``, ``--all``, or ``--any``.
 
     """
 
@@ -1620,15 +1618,16 @@
     targetcat = 'unstable'
     if 1 < len(specifiedcategories):
         msg = _('cannot specify more than one trouble category to solve (yet)')
-        raise util.Abort(msg)
+        raise error.Abort(msg)
     elif len(specifiedcategories) == 1:
         targetcat = specifiedcategories[0]
     elif repo['.'].obsolete():
-        displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+        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:
+        except MultipleSuccessorsError as exc:
             repo.ui.write_err('parent is obsolete with multiple successors:\n')
             for ln in exc.successorssets:
                 for n in ln:
@@ -1657,23 +1656,23 @@
 
     def progresscb():
         if revopt or allopt:
-            ui.progress('evolve', seen, unit='changesets', total=count)
+            ui.progress(_('evolve'), seen, unit='changesets', total=count)
 
     # Continuation handling
     if contopt:
         if anyopt:
-            raise util.Abort('cannot specify both "--any" and "--continue"')
+            raise error.Abort('cannot specify both "--any" and "--continue"')
         if allopt:
-            raise util.Abort('cannot specify both "--all" and "--continue"')
+            raise error.Abort('cannot specify both "--all" and "--continue"')
         graftcmd = commands.table['graft'][0]
         return graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
     cmdutil.bailifchanged(repo)
 
 
     if revopt and allopt:
-        raise util.Abort('cannot specify both "--rev" and "--all"')
+        raise error.Abort('cannot specify both "--rev" and "--all"')
     if revopt and anyopt:
-        raise util.Abort('cannot specify both "--rev" and "--any"')
+        raise error.Abort('cannot specify both "--rev" and "--any"')
 
     revs = _selectrevs(repo, allopt, revopt, anyopt, targetcat)
 
@@ -1749,13 +1748,20 @@
 def _solveunstable(ui, repo, orig, dryrun=False, confirm=False,
                    progresscb=None):
     """Stabilize an unstable changeset"""
-    obs = orig.parents()[0]
-    if not obs.obsolete() and len(orig.parents()) == 2:
-        obs = orig.parents()[1] # second parent is obsolete ?
-
-    if not obs.obsolete():
-        ui.warn("cannot solve instability of %s, skipping\n" % orig)
+    pctx = orig.p1()
+    if len(orig.parents()) == 2:
+        if not pctx.obsolete():
+            pctx = orig.p2()  # second parent is obsolete ?
+        elif orig.p2().obsolete():
+            raise error.Abort(_("no support for evolving merge changesets "
+                                "with two obsolete parents yet"),
+                              hint=_("Redo the merge and use `hg prune <old> "
+                                   "--succ <new>` to obsolete the old one"))
+
+    if not pctx.obsolete():
+        ui.warn(_("cannot solve instability of %s, skipping\n") % orig)
         return False
+    obs = pctx
     newer = obsolete.successorssets(repo, obs.node())
     # search of a parent which is not killed
     while not newer or newer == [()]:
@@ -1765,7 +1771,8 @@
         obs = obs.parents()[0]
         newer = obsolete.successorssets(repo, obs.node())
     if len(newer) > 1:
-        msg = _("skipping %s: divergent rewriting. can't choose destination\n") % obs
+        msg = _("skipping %s: divergent rewriting. can't choose "
+                "destination\n") % obs
         ui.write_err(msg)
         return 2
     targets = newer[0]
@@ -1790,7 +1797,7 @@
         repo.ui.write(_('atop:'))
         displayer.show(target)
     if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
-            raise util.Abort(_('evolve aborted by user'))
+            raise error.Abort(_('evolve aborted by user'))
     if progresscb: progresscb()
     todo = 'hg rebase -r %s -d %s\n' % (orig, target)
     if dryrun:
@@ -1800,7 +1807,7 @@
         if progresscb: progresscb()
         keepbranch = orig.p1().branch() != orig.branch()
         try:
-            relocate(repo, orig, target, keepbranch)
+            relocate(repo, orig, target, pctx, keepbranch)
         except MergeFailure:
             repo.opener.write('graftstate', orig.hex() + '\n')
             repo.ui.write_err(_('evolve failed!\n'))
@@ -1822,7 +1829,8 @@
     prec = repo.set('last(allprecursors(%d) and public())', bumped).next()
     # For now we deny target merge
     if len(prec.parents()) > 1:
-        msg = _('skipping: %s: public version is a merge, this not handled yet\n') % prec
+        msg = _('skipping: %s: public version is a merge, '
+                'this is not handled yet\n') % prec
         ui.write_err(msg)
         return 2
 
@@ -1833,13 +1841,13 @@
         repo.ui.write(_('atop:'))
         displayer.show(prec)
     if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
-        raise util.Abort(_('evolve aborted by user'))
+        raise error.Abort(_('evolve aborted by user'))
     if dryrun:
         todo = 'hg rebase --rev %s --dest %s;\n' % (bumped, prec.p1())
         repo.ui.write(todo)
-        repo.ui.write('hg update %s;\n' % prec)
-        repo.ui.write('hg revert --all --rev %s;\n' % bumped)
-        repo.ui.write('hg commit --msg "bumped update to %s"')
+        repo.ui.write(('hg update %s;\n' % prec))
+        repo.ui.write(('hg revert --all --rev %s;\n' % bumped))
+        repo.ui.write(('hg commit --msg "bumped update to %s"'))
         return 0
     if progresscb: progresscb()
     newid = tmpctx = None
@@ -1927,7 +1935,8 @@
     base, others = divergentdata(divergent)
     if len(others) > 1:
         othersstr = "[%s]" % (','.join([str(i) for i in others]))
-        msg = _("skipping %d:divergent with a changeset that got splitted into multiple ones:\n"
+        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 "
@@ -1936,12 +1945,13 @@
                  "| - hg prune\n"
                  "| \n"
                  "| You should contact your local evolution Guru for help.\n"
-                 % (divergent, othersstr))
+                 ) % (divergent, othersstr)
         ui.write_err(msg)
         return 2
     other = others[0]
     if len(other.parents()) > 1:
-        msg = _("skipping %s: divergent changeset can't be a merge (yet)\n" % divergent)
+        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"
@@ -1949,7 +1959,8 @@
         ui.write_err(hint)
         return 2
     if other.p1() not in divergent.parents():
-        msg = _("skipping %s: have a different parent than %s (not handled yet)\n") % (divergent, other)
+        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"
@@ -1957,7 +1968,7 @@
                  "| 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})
+                 ) % {'d': divergent, 'o': other}
         ui.write_err(msg)
         ui.write_err(hint)
         return 2
@@ -1971,35 +1982,45 @@
         ui.write(_('base: '))
         displayer.show(base)
     if confirm and ui.prompt(_('perform evolve? [Ny]'), 'n') != 'y':
-        raise util.Abort(_('evolve aborted by user'))
+        raise error.Abort(_('evolve aborted by user'))
     if dryrun:
-        ui.write('hg update -c %s &&\n' % divergent)
-        ui.write('hg merge %s &&\n' % other)
-        ui.write('hg commit -m "auto merge resolving conflict between '
-                 '%s and %s"&&\n' % (divergent, other))
-        ui.write('hg up -C %s &&\n' % base)
-        ui.write('hg revert --all --rev tip &&\n')
-        ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n'
-                 % divergent)
+        ui.write(('hg update -c %s &&\n' % divergent))
+        ui.write(('hg merge %s &&\n' % other))
+        ui.write(('hg commit -m "auto merge resolving conflict between '
+                 '%s and %s"&&\n' % (divergent, other)))
+        ui.write(('hg up -C %s &&\n' % base))
+        ui.write(('hg revert --all --rev tip &&\n'))
+        ui.write(('hg commit -m "`hg log -r %s --template={desc}`";\n'
+                 % divergent))
         return
     if divergent not in repo[None].parents():
         repo.ui.status(_('updating to "local" conflict\n'))
         hg.update(repo, divergent.rev())
     repo.ui.note(_('merging divergent changeset\n'))
     if progresscb: progresscb()
-    stats = merge.update(repo,
-                         other.node(),
-                         branchmerge=True,
-                         force=False,
-                         partial=None,
-                         ancestor=base.node(),
-                         mergeancestor=True)
+    if 'partial' in merge.update.__doc__:
+        # Mercurial  < 43c00ca887d1 (3.7)
+        stats = merge.update(repo,
+                             other.node(),
+                             branchmerge=True,
+                             force=False,
+                             partial=None,
+                             ancestor=base.node(),
+                             mergeancestor=True)
+    else:
+        stats = merge.update(repo,
+                             other.node(),
+                             branchmerge=True,
+                             force=False,
+                             ancestor=base.node(),
+                             mergeancestor=True)
+
     hg._showstats(repo, stats)
     if stats[3]:
         repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
                          "or 'hg update -C .' to abandon\n"))
     if stats[3] > 0:
-        raise util.Abort('merge conflict between several amendments '
+        raise error.Abort('merge conflict between several amendments '
             '(this is not automated yet)',
             hint="""/!\ You can try:
 /!\ * manual merge + resolve => new cset X
@@ -2009,8 +2030,10 @@
 /!\ * hg kill -n Y W Z
 """)
     if progresscb: progresscb()
+    emtpycommitallowed = repo.ui.backupconfig('ui', 'allowemptycommit')
     tr = repo.transaction('stabilize-divergent')
     try:
+        repo.ui.setconfig('ui', 'allowemptycommit', True)
         repo.dirstate.beginparentchange()
         repo.dirstate.setparents(divergent.node(), node.nullid)
         repo.dirstate.endparentchange()
@@ -2025,6 +2048,7 @@
         phases.retractboundary(repo, tr, other.phase(), [new.node()])
         tr.close()
     finally:
+        repo.ui.restoreconfig(emtpycommitallowed)
         tr.release()
 
 def divergentdata(ctx):
@@ -2041,7 +2065,7 @@
         newer = [n for n in newer if n and ctx.node() not in n]
         if newer:
             return base, tuple(ctx._repo[o] for o in newer[0])
-    raise util.Abort("base of divergent changeset %s not found" % ctx,
+    raise error.Abort("base of divergent changeset %s not found" % ctx,
                      hint='this case is not yet handled')
 
 
@@ -2052,7 +2076,8 @@
          [('B', 'move-bookmark', False,
              _('move active bookmark after update')),
           ('', 'merge', False, _('bring uncommitted change along')),
-          ('n', 'dry-run', False, _('do not perform actions, just print what would be done'))],
+          ('n', 'dry-run', False,
+             _('do not perform actions, just print what would be done'))],
          '[OPTION]...')
 def cmdprevious(ui, repo, **opts):
     """update to parent revision
@@ -2062,11 +2087,11 @@
     wparents = wkctx.parents()
     dryrunopt = opts['dry_run']
     if len(wparents) != 1:
-        raise util.Abort('merge in progress')
+        raise error.Abort('merge in progress')
     if not opts['merge']:
         try:
             cmdutil.bailifchanged(repo)
-        except error.Abort, exc:
+        except error.Abort as exc:
             exc.hint = _('do you want --merge?')
             raise
 
@@ -2077,21 +2102,25 @@
         bm = bmactive(repo)
         shouldmove = opts.get('move_bookmark') and bm is not None
         if dryrunopt:
-            ui.write('hg update %s;\n' % p.rev())
+            ui.write(('hg update %s;\n' % p.rev()))
             if shouldmove:
-                ui.write('hg bookmark %s -r %s;\n' % (bm, p.rev()))
+                ui.write(('hg bookmark %s -r %s;\n' % (bm, p.rev())))
         else:
             ret = hg.update(repo, p.rev())
             if not ret:
+                tr = lock = None
                 wlock = repo.wlock()
                 try:
+                    lock = repo.lock()
+                    tr = repo.transaction('previous')
                     if shouldmove:
                         repo._bookmarks[bm] = p.node()
-                        repo._bookmarks.write()
+                        repo._bookmarks.recordchange(tr)
                     else:
                         bmdeactivate(repo)
+                    tr.close()
                 finally:
-                    wlock.release()
+                    lockmod.release(tr, lock, wlock)
         displayer.show(p)
         return 0
     else:
@@ -2105,8 +2134,9 @@
              _('move active bookmark after update')),
           ('', 'merge', False, _('bring uncommitted change along')),
           ('', 'evolve', False, _('evolve the next changeset if necessary')),
-          ('n', 'dry-run', False, _('do not perform actions, just print what would be done'))],
-         '[OPTION]...')
+          ('n', 'dry-run', False,
+              _('do not perform actions, just print what would be done'))],
+              '[OPTION]...')
 def cmdnext(ui, repo, **opts):
     """update to next child revision
 
@@ -2118,11 +2148,11 @@
     wparents = wkctx.parents()
     dryrunopt = opts['dry_run']
     if len(wparents) != 1:
-        raise util.Abort('merge in progress')
+        raise error.Abort('merge in progress')
     if not opts['merge']:
         try:
             cmdutil.bailifchanged(repo)
-        except error.Abort, exc:
+        except error.Abort as exc:
             exc.hint = _('do you want --merge?')
             raise
 
@@ -2133,25 +2163,29 @@
         bm = bmactive(repo)
         shouldmove = opts.get('move_bookmark') and bm is not None
         if dryrunopt:
-            ui.write('hg update %s;\n' % c.rev())
+            ui.write(('hg update %s;\n' % c.rev()))
             if shouldmove:
-                ui.write('hg bookmark %s -r %s;\n' % (bm, c.rev()))
+                ui.write(('hg bookmark %s -r %s;\n' % (bm, c.rev())))
         else:
             ret = hg.update(repo, c.rev())
             if not ret:
+                lock = tr = None
                 wlock = repo.wlock()
                 try:
+                    lock = repo.lock()
+                    tr = repo.transaction('next')
                     if shouldmove:
                         repo._bookmarks[bm] = c.node()
-                        repo._bookmarks.write()
+                        repo._bookmarks.recordchange(tr)
                     else:
                         bmdeactivate(repo)
+                    tr.close()
                 finally:
-                    wlock.release()
+                    lockmod.release(tr, lock, wlock)
         displayer.show(c)
         result = 0
     elif children:
-        ui.warn("ambigious next changeset:\n")
+        ui.warn(_("ambigious next changeset:\n"))
         for c in children:
             displayer.show(c)
         ui.warn(_('explicitly update to one of them\n'))
@@ -2181,46 +2215,48 @@
         return 1
     return result
 
-def _reachablefrombookmark(repo, revs, mark):
+def _reachablefrombookmark(repo, revs, bookmarks):
     """filter revisions and bookmarks reachable from the given bookmark
     yoinked from mq.py
     """
-    marks = repo._bookmarks
-    if mark not in marks:
-        raise util.Abort(_("bookmark '%s' not found") % mark)
+    repomarks = repo._bookmarks
+    if not bookmarks.issubset(repomarks):
+        raise error.Abort(_("bookmark '%s' not found") %
+            ','.join(sorted(bookmarks - set(repomarks.keys()))))
 
     # If the requested bookmark is not the only one pointing to a
     # a revision we have to only delete the bookmark and not strip
     # anything. revsets cannot detect that case.
-    uniquebm = True
-    for m, n in marks.iteritems():
-        if m != mark and n == repo[mark].node():
-            uniquebm = False
-            break
-    if uniquebm:
-        if util.safehasattr(repair, 'stripbmrevset'):
-            rsrevs = repair.stripbmrevset(repo, mark)
-        else:
-            rsrevs = repo.revs("ancestors(bookmark(%s)) - "
-                               "ancestors(head() and not bookmark(%s)) - "
-                               "ancestors(bookmark() and not bookmark(%s)) - "
-                               "obsolete()",
-                               mark, mark, mark)
-        revs = set(revs)
-        revs.update(set(rsrevs))
-        revs = sorted(revs)
-    return marks, revs
-
-def _deletebookmark(repo, marks, mark):
+    nodetobookmarks = {}
+    for mark, node in repomarks.iteritems():
+        nodetobookmarks.setdefault(node, []).append(mark)
+    for marks in nodetobookmarks.values():
+        if bookmarks.issuperset(marks):
+           if util.safehasattr(repair, 'stripbmrevset'):
+               rsrevs = repair.stripbmrevset(repo, marks[0])
+           else:
+               rsrevs = repo.revs("ancestors(bookmark(%s)) - "
+                                  "ancestors(head() and not bookmark(%s)) - "
+                                  "ancestors(bookmark() and not bookmark(%s)) - "
+                                  "obsolete()",
+                                  marks[0], marks[0], marks[0])
+           revs = set(revs)
+           revs.update(set(rsrevs))
+           revs = sorted(revs)
+    return repomarks, revs
+
+def _deletebookmark(repo, repomarks, bookmarks):
     wlock = lock = tr = None
     try:
         wlock = repo.wlock()
         lock = repo.lock()
         tr = repo.transaction('prune')
-        del marks[mark]
-        marks.recordchange(tr)
+        for bookmark in bookmarks:
+            del repomarks[bookmark]
+        repomarks.recordchange(tr)
         tr.close()
-        repo.ui.write(_("bookmark '%s' deleted\n") % mark)
+        for bookmark in sorted(bookmarks):
+            repo.ui.write(_("bookmark '%s' deleted\n") % bookmark)
     finally:
         lockmod.release(tr, lock, wlock)
 
@@ -2243,9 +2279,11 @@
      ('r', 'rev', [], _("revisions to prune")),
      ('k', 'keep', None, _("does not modify working copy during prune")),
      ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")),
-     ('', 'fold', False, _("record a fold (multiple precursors, one successors)")),
-     ('', 'split', False, _("record a split (on precursor, multiple successors)")),
-     ('B', 'bookmark', '', _("remove revs only reachable from given"
+     ('', 'fold', False,
+        _("record a fold (multiple precursors, one successors)")),
+     ('', 'split', False,
+        _("record a split (on precursor, multiple successors)")),
+     ('B', 'bookmark', [], _("remove revs only reachable from given"
                              " bookmark"))] + metadataopts,
     _('[OPTION] [-r] REV...'))
     # -U  --noupdate option to prevent wc update and or bookmarks update ?
@@ -2275,7 +2313,7 @@
     """
     revs = scmutil.revrange(repo, list(revs) + opts.get('rev'))
     succs = opts['new'] + opts['succ']
-    bookmark = opts.get('bookmark')
+    bookmarks = set(opts.get('bookmark'))
     metadata = _getmetadata(**opts)
     biject = opts.get('biject')
     fold = opts.get('fold')
@@ -2283,16 +2321,16 @@
 
     options = [o for o in ('biject', 'fold', 'split') if opts.get(o)]
     if 1 < len(options):
-        raise util.Abort(_("can only specify one of %s") % ', '.join(options))
-
-    if bookmark:
-        marks,revs = _reachablefrombookmark(repo, revs, bookmark)
+        raise error.Abort(_("can only specify one of %s") % ', '.join(options))
+
+    if bookmarks:
+        repomarks, revs = _reachablefrombookmark(repo, revs, bookmarks)
         if not revs:
             # no revisions to prune - delete bookmark immediately
-            _deletebookmark(repo, marks, bookmark)
+            _deletebookmark(repo, repomarks, bookmarks)
 
     if not revs:
-        raise util.Abort(_('nothing to prune'))
+        raise error.Abort(_('nothing to prune'))
 
     wlock = lock = tr = None
     try:
@@ -2306,15 +2344,15 @@
             cp = repo[p]
             if not cp.mutable():
                 # note: createmarkers() would have raised something anyway
-                raise util.Abort('cannot prune immutable changeset: %s' % cp,
+                raise error.Abort('cannot prune immutable changeset: %s' % cp,
                                  hint='see "hg help phases" for details')
             precs.append(cp)
         if not precs:
-            raise util.Abort('nothing to prune')
+            raise error.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"))
+                raise error.Abort(_("cannot prune in the middle of a stack"))
 
         # defines successors changesets
         sucs = scmutil.revrange(repo, succs)
@@ -2322,17 +2360,17 @@
         sucs = tuple(repo[n] for n in sucs)
         if not biject and len(sucs) > 1 and len(precs) > 1:
             msg = "Can't use multiple successors for multiple precursors"
-            raise util.Abort(msg)
+            raise error.Abort(msg)
         elif biject and len(sucs) != len(precs):
             msg = "Can't use %d successors for %d precursors" \
                 % (len(sucs), len(precs))
-            raise util.Abort(msg)
+            raise error.Abort(msg)
         elif (len(precs) == 1 and len(sucs) > 1) and not split:
             msg = "please add --split if you want to do a split"
-            raise util.Abort(msg)
+            raise error.Abort(msg)
         elif len(sucs) == 1 and len(precs) > 1 and not fold:
             msg = "please add --fold if you want to do a fold"
-            raise util.Abort(msg)
+            raise error.Abort(msg)
         elif biject:
             relations = [(p, (s,)) for p, s in zip(precs, sucs)]
         else:
@@ -2362,33 +2400,35 @@
                 descendantrevs = repo.revs("%d::." % newnode.rev())
                 changedfiles = []
                 for rev in descendantrevs:
-                    # blindly reset the files, regardless of what actually changed
+                    # 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.rebuild(newnode.node(), newnode.manifest(),
+                                      changedfiles)
                 writedirstate(dirstate, tr)
             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
+                movebookmark = bookactive and not bookmarks
                 if movebookmark:
                     bmdeactivate(repo)
                     repo._bookmarks[bookactive] = newnode.node()
-                    repo._bookmarks.write()
+                    repo._bookmarks.recordchange(tr)
                 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(repo, marks, bookmark)
+        if bookmarks:
+            _deletebookmark(repo, repomarks, bookmarks)
 
         # create markers
         obsolete.createmarkers(repo, relations, metadata=metadata)
@@ -2583,14 +2623,14 @@
         lock = repo.lock()
         wctx = repo[None]
         if len(wctx.parents()) <= 0:
-            raise util.Abort(_("cannot uncommit null changeset"))
+            raise error.Abort(_("cannot uncommit null changeset"))
         if len(wctx.parents()) > 1:
-            raise util.Abort(_("cannot uncommit while merging"))
+            raise error.Abort(_("cannot uncommit while merging"))
         old = repo['.']
         if old.phase() == phases.public:
-            raise util.Abort(_("cannot rewrite immutable changeset"))
+            raise error.Abort(_("cannot rewrite immutable changeset"))
         if len(old.parents()) > 1:
-            raise util.Abort(_("cannot uncommit merge changeset"))
+            raise error.Abort(_("cannot uncommit merge changeset"))
         oldphase = old.phase()
 
 
@@ -2599,12 +2639,13 @@
             rev = scmutil.revsingle(repo, opts.get('rev'))
             ctx = repo[None]
             if ctx.p1() == rev or ctx.p2() == rev:
-                raise util.Abort(_("cannot uncommit to parent changeset"))
+                raise error.Abort(_("cannot uncommit to parent changeset"))
 
         onahead = old.rev() in repo.changelog.headrevs()
-        disallowunstable = not obsolete.isenabled(repo, obsolete.allowunstableopt)
+        disallowunstable = not obsolete.isenabled(repo,
+                                                  obsolete.allowunstableopt)
         if disallowunstable and not onahead:
-            raise util.Abort(_("cannot uncommit in the middle of a stack"))
+            raise error.Abort(_("cannot uncommit in the middle of a stack"))
 
         # Recommit the filtered changeset
         tr = repo.transaction('uncommit')
@@ -2615,7 +2656,7 @@
             match = scmutil.match(old, pats, opts)
             newid = _commitfiltered(repo, old, match, target=rev)
         if newid is None:
-            raise util.Abort(_('nothing to uncommit'),
+            raise error.Abort(_('nothing to uncommit'),
                              hint=_("use --all to uncommit all files"))
         # Move local changes on filtered changeset
         obsolete.createmarkers(repo, [(old, (repo[newid],))])
@@ -2634,6 +2675,7 @@
 
 @eh.wrapcommand('commit')
 def commitwrapper(orig, ui, repo, *arg, **kwargs):
+    tr = None
     if kwargs.get('amend', False):
         wlock = lock = None
     else:
@@ -2656,17 +2698,23 @@
             for book in oldbookmarks:
                 repo._bookmarks[book] = new.node()
             if oldbookmarks:
-                repo._bookmarks.write()
+                if not wlock:
+                    wlock = repo.wlock()
+                if not lock:
+                    lock = repo.lock()
+                tr = repo.transaction('commit')
+                repo._bookmarks.recordchange(tr)
+                tr.close()
         return result
     finally:
-        lockmod.release(lock, wlock)
+        lockmod.release(tr, lock, wlock)
 
 @command('^split',
     [('r', 'rev', [], _("revision to fold")),
     ] + commitopts + commitopts2,
     _('hg split [OPTION]... [-r] REV'))
 def cmdsplit(ui, repo, *revs, **opts):
-    """split a changeset into smaller changesets (EXPERIMENTAL)
+    """split a changeset into smaller changesets
 
     By default, split the current revision by prompting for all its hunks to be
     redistributed into new changesets.
@@ -2680,7 +2728,7 @@
     if revopt:
         revs = scmutil.revrange(repo, revopt)
         if len(revs) != 1:
-            raise util.Abort(_("you can only specify one revision to split"))
+            raise error.Abort(_("you can only specify one revision to split"))
         else:
             rev = list(revs)[0]
     else:
@@ -2698,10 +2746,10 @@
         if disallowunstable:
             # XXX We should check head revs
             if repo.revs("(%d::) - %d", rev, rev):
-                raise util.Abort(_("cannot split commit: %s not a head") % ctx)
+                raise error.Abort(_("cannot split commit: %s not a head") % ctx)
 
         if len(ctx.parents()) > 1:
-            raise util.Abort(_("cannot split merge commits"))
+            raise error.Abort(_("cannot split merge commits"))
         prev = ctx.p1()
         bmupdate = _bookmarksupdater(repo, ctx.node(), tr)
         bookactive = bmactive(repo)
@@ -2714,6 +2762,10 @@
         def haschanges():
             modified, added, removed, deleted = repo.status()[:4]
             return modified or added or removed or deleted
+        msg = 'HG: Please, edit the original changeset description.\n\n'
+        msg += ctx.description()
+        opts['message'] = msg
+        opts['edit'] = True
         while haschanges():
             pats = ()
             cmdutil.dorecord(ui, repo, commands.commit, 'commit', False,
@@ -2727,7 +2779,7 @@
                     newcommits.append(repo['.'])
                     break
             else:
-                ui.status("no more change to split\n")
+                ui.status(_("no more change to split\n"))
 
         tip = repo[newcommits[-1]]
         bmupdate(tip.node())
@@ -2771,7 +2823,8 @@
     # allow to choose the seed ?
     _('[-r] revs'))
 def touch(ui, repo, *revs, **opts):
-    """create successors that are identical to their predecessors except for the changeset ID
+    """create successors that are identical to their predecessors except
+    for the changeset ID
 
     This is used to "resurrect" changesets
     """
@@ -2785,7 +2838,7 @@
         ui.write_err('no revision to touch\n')
         return 1
     if not duplicate and repo.revs('public() and %ld', revs):
-        raise util.Abort("can't touch public revision")
+        raise error.Abort("can't touch public revision")
     wlock = lock = tr = None
     try:
         wlock = repo.wlock()
@@ -2863,7 +2916,7 @@
     revs = list(revs)
     revs.extend(opts['rev'])
     if not revs:
-        raise util.Abort(_('no revisions specified'))
+        raise error.Abort(_('no revisions specified'))
 
     revs = scmutil.revrange(repo, revs)
 
@@ -2872,7 +2925,7 @@
         extrevs = repo.revs('(%ld::.) or (.::%ld)', revs, revs)
         discardedrevs = [r for r in revs if r not in extrevs]
         if discardedrevs:
-            raise util.Abort(_("cannot fold non-linear revisions"),
+            raise error.Abort(_("cannot fold non-linear revisions"),
                                hint=_("given revisions are unrelated to parent "
                                       "of working directory"))
         revs = extrevs
@@ -2883,20 +2936,20 @@
 
     roots = repo.revs('roots(%ld)', revs)
     if len(roots) > 1:
-        raise util.Abort(_("cannot fold non-linear revisions "
+        raise error.Abort(_("cannot fold non-linear revisions "
                            "(multiple roots given)"))
     root = repo[roots.first()]
     if root.phase() <= phases.public:
-        raise util.Abort(_("cannot fold public revisions"))
+        raise error.Abort(_("cannot fold public revisions"))
     heads = repo.revs('heads(%ld)', revs)
     if len(heads) > 1:
-        raise util.Abort(_("cannot fold non-linear revisions "
+        raise error.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 "\
+            raise error.Abort(_("cannot fold chain not ending with a head "\
                                "or with branching"))
     wlock = lock = None
     try:
@@ -2918,7 +2971,8 @@
                 commitopts['edit'] = True
 
             newid, unusedvariable = rewrite(repo, root, allctx, head,
-                                            [root.p1().node(), root.p2().node()],
+                                            [root.p1().node(),
+                                             root.p2().node()],
                                             commitopts=commitopts)
             phases.retractboundary(repo, tr, targetphase, [newid])
             obsolete.createmarkers(repo, [(ctx, (repo[newid],))
@@ -3026,7 +3080,8 @@
         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)
+        common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote,
+                                      commonrevs)
 
         revs = list(unfi.revs('%ld - (::%ln)', revs, common))
         nodes = [cl.node(r) for r in revs]
@@ -3058,8 +3113,9 @@
     wireproto.commands['evoext_obshash'] = (srv_obshash, 'nodes')
     wireproto.commands['evoext_obshash1'] = (srv_obshash1, 'nodes')
     if getattr(exchange, '_pushdiscoveryobsmarkers', None) is None:
-        ui.warn('evolve: your mercurial version is too old\n'
-                'evolve: (running in degraded mode, push will includes all markers)\n')
+        ui.warn(_('evolve: your mercurial version is too old\n'
+                  'evolve: (running in degraded mode, push will '
+                  'includes all markers)\n'))
     else:
         olddisco = exchange.pushdiscoverymapping['obsmarker']
         def newdisco(pushop):
@@ -3086,7 +3142,8 @@
     return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes)))
 
 def srv_obshash1(repo, proto, nodes):
-    return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes), version=1))
+    return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes),
+                                version=1))
 
 @eh.addattr(localrepo.localpeer, 'evoext_obshash')
 def local_obshash(peer, nodes):
@@ -3123,7 +3180,7 @@
     common = set()
     undecided = set(probeset)
     totalnb = len(undecided)
-    ui.progress("comparing with other", 0, total=totalnb)
+    ui.progress(_("comparing with other"), 0, total=totalnb)
     _takefullsample = setdiscovery._takefullsample
     if remote.capable('_evoext_obshash_1'):
         getremotehash = remote.evoext_obshash1
@@ -3141,7 +3198,7 @@
             sample = _takefullsample(dag, undecided, size=fullsamplesize)
 
         roundtrips += 1
-        ui.progress("comparing with other", totalnb - len(undecided),
+        ui.progress(_("comparing with other"), totalnb - len(undecided),
                     total=totalnb)
         ui.debug("query %i; still undecided: %i, sample size is: %i\n"
                  % (roundtrips, len(undecided), len(sample)))
@@ -3162,7 +3219,7 @@
         undecided.difference_update(common)
 
 
-    ui.progress("comparing with other", None, total=totalnb)
+    ui.progress(_("comparing with other"), None, total=totalnb)
     result = dag.headsetofconnecteds(common)
     ui.debug("%d total queries\n" % roundtrips)
 
@@ -3226,10 +3283,11 @@
         else:
             rslts = []
             remotedata = _pushkeyescape(markers).items()
-            totalbytes = sum(len(d) for k,d in remotedata)
+            totalbytes = sum(len(d) for k, d in remotedata)
             sentbytes = 0
-            obsexcmsg(repo.ui, "pushing %i obsolescence markers in %i pushkey payload (%i bytes)\n"
-                                % (len(markers), len(remotedata), totalbytes),
+            obsexcmsg(repo.ui, "pushing %i obsolescence markers in %i "
+                               "pushkey payload (%i bytes)\n"
+                               % (len(markers), len(remotedata), totalbytes),
                       True)
             for key, data in remotedata:
                 obsexcprg(repo.ui, sentbytes, item=key, unit="bytes",
@@ -3271,10 +3329,10 @@
             if l.strip():
                 self.ui.status(_('remote: '), l)
         return vals[0]
-    except socket.error, err:
+    except socket.error as err:
         if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
-            raise util.Abort(_('push failed: %s') % err.args[1])
-        raise util.Abort(err.args[1])
+            raise error.Abort(_('push failed: %s') % err.args[1])
+        raise error.Abort(err.args[1])
 
 @eh.wrapfunction(localrepo.localrepository, '_restrictcapabilities')
 def local_pushobsmarker_capabilities(orig, repo, caps):
@@ -3333,7 +3391,8 @@
 @eh.wrapfunction(exchange, '_pullbundle2extraprepare')
 def _addobscommontob2pull(orig, pullop, kwargs):
     ret = orig(pullop, kwargs)
-    if 'obsmarkers' in kwargs and pullop.remote.capable('_evoext_getbundle_obscommon'):
+    if ('obsmarkers' in kwargs and
+        pullop.remote.capable('_evoext_getbundle_obscommon')):
         boundaries = _buildpullobsmarkersboundaries(pullop)
         common = boundaries['common']
         if common != [nullid]:
@@ -3446,7 +3505,7 @@
     ui = self.ui
     obsexcprg(ui, current, unit="bytes", total=length)
     while current < length:
-        readsize = min(length-current, chunk)
+        readsize = min(length - current, chunk)
         data.write(f.read(readsize))
         current += readsize
         obsexcprg(ui, current, unit="bytes", total=length)
@@ -3481,7 +3540,7 @@
     cache = []
     unfi = repo.unfiltered()
     markercache = {}
-    repo.ui.progress("preparing locally", 0, total=len(unfi))
+    repo.ui.progress(_("preparing locally"), 0, total=len(unfi))
     for i in unfi:
         ctx = unfi[i]
         entry = 0
@@ -3511,14 +3570,13 @@
             cache.append((ctx.node(), sha.digest()))
         else:
             cache.append((ctx.node(), nullid))
-        repo.ui.progress("preparing locally", i, total=len(unfi))
-    repo.ui.progress("preparing locally", None)
+        repo.ui.progress(_("preparing locally"), i, total=len(unfi))
+    repo.ui.progress(_("preparing locally"), None)
     return cache
 
 @command('debugobsrelsethashtree',
         [('', 'v0', None, 'hash on marker format "0"'),
-         ('', 'v1', None, 'hash on marker format "1" (default)')
-         ,] , _(''))
+         ('', 'v1', None, 'hash on marker format "1" (default)')] , _(''))
 def debugobsrelsethashtree(ui, repo, v0=False, v1=False):
     """display Obsolete markers, Relevant Set, Hash Tree
     changeset-node obsrelsethashtree-node
@@ -3526,7 +3584,7 @@
     It computed form the "orsht" of its parent and markers
     relevant to the changeset itself."""
     if v0 and v1:
-        raise util.Abort('cannot only specify one format')
+        raise error.Abort('cannot only specify one format')
     elif v0:
         treefunc = _obsrelsethashtreefm0
     else:
@@ -3549,7 +3607,7 @@
         return
     for mark in markers:
         if node.nullid in mark[1]:
-            raise util.Abort(_('bad obsolescence marker detected: '
+            raise error.Abort(_('bad obsolescence marker detected: '
                                'invalid successors nullid'),
                              hint=_('You should run `hg debugobsconvert`'))
 
@@ -3561,7 +3619,7 @@
     origmarkers = repo.obsstore._all  # settle version
     if new_format == repo.obsstore._version:
         msg = _('New format is the same as the old format, not upgrading!')
-        raise util.Abort(msg)
+        raise error.Abort(msg)
     f = repo.svfs('obsstore', 'wb', atomictemp=True)
     known = set()
     markers = []
@@ -3621,3 +3679,42 @@
         help.helptable.append((["evolution"], _("Safely Rewriting History"),
                       _helploader))
         help.helptable.sort()
+
+def _relocatecommit(repo, orig, commitmsg):
+    if commitmsg is None:
+        commitmsg = orig.description()
+    extra = dict(orig.extra())
+    if 'branch' in extra:
+        del extra['branch']
+    extra['rebase_source'] = orig.hex()
+
+    backup = repo.ui.backupconfig('phases', 'new-commit')
+    try:
+        targetphase = max(orig.phase(), phases.draft)
+        repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
+        # Commit might fail if unresolved files exist
+        nodenew = repo.commit(text=commitmsg, user=orig.user(),
+                              date=orig.date(), extra=extra)
+    finally:
+        repo.ui.restoreconfig(backup)
+    return nodenew
+
+def _finalizerelocate(repo, orig, dest, nodenew, tr):
+    destbookmarks = repo.nodebookmarks(dest.node())
+    nodesrc = orig.node()
+    destphase = repo[nodesrc].phase()
+    oldbookmarks = repo.nodebookmarks(nodesrc)
+    if nodenew is not None:
+        phases.retractboundary(repo, tr, destphase, [nodenew])
+        obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))])
+        for book in oldbookmarks:
+            repo._bookmarks[book] = nodenew
+    else:
+        obsolete.createmarkers(repo, [(repo[nodesrc], ())])
+        # Behave like rebase, move bookmarks to dest
+        for book in oldbookmarks:
+            repo._bookmarks[book] = dest.node()
+    for book in destbookmarks: # restore bookmark that rebase move
+        repo._bookmarks[book] = dest.node()
+    if oldbookmarks or destbookmarks:
+        repo._bookmarks.recordchange(tr)
--- a/hgext/inhibit.py	Sun Jan 03 15:51:36 2016 +0100
+++ b/hgext/inhibit.py	Sun Jan 03 16:47:57 2016 +0100
@@ -9,9 +9,9 @@
 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 
+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
@@ -42,7 +42,7 @@
             obsinhibit = set()
             raw = self.svfs.tryread('obsinhibit')
             for i in xrange(0, len(raw), 20):
-                obsinhibit.add(raw[i:i+20])
+                obsinhibit.add(raw[i:i + 20])
             return obsinhibit
 
         def commit(self, *args, **kwargs):
@@ -61,7 +61,7 @@
     """
     wlock = None
     try:
-        # Evolve is running a hook on lock release to display a warning message 
+        # 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.
@@ -85,6 +85,11 @@
     haspruneopt = opts.get('prune', False)
     if not haspruneopt:
         return orig(ui, repo, *bookmarks, **opts)
+    elif opts.get('rename'):
+        raise error.Abort('Cannot use both -m and -D')
+    elif len(bookmarks) == 0:
+        hint = _('make sure to put a space between -D and your bookmark name')
+        raise error.Abort(_('Error, please check your command'), hint=hint)
 
     # Call prune -B
     evolve = extensions.find('evolve')
@@ -92,7 +97,7 @@
         'new': [],
         'succ': [],
         'rev': [],
-        'bookmark': bookmarks[0],
+        'bookmark': [bookmarks[0]],
         'keep': None,
         'biject': False,
     }
@@ -129,13 +134,22 @@
     if not _inhibitenabled(repo):
         return
 
-    newinhibit = repo.set('::%ln and obsolete()', nodes)
+    # we add (non public()) as a lower boundary to
+    # - use the C code in 3.6 (no ancestors in C as this is written)
+    # - restrict the search space. Otherwise, the ancestors can spend a lot of
+    #   time iterating if you have a check very low in the repo. We do not need
+    #   to iterate over tens of thousand of public revisions with higher
+    #   revision number
+    #
+    # In addition, the revset logic could be made significantly smarter here.
+    newinhibit = repo.revs('(not public())::%ln and obsolete()', nodes)
     if newinhibit:
+        node = repo.changelog.node
         lock = tr = None
         try:
             lock = repo.lock()
             tr = repo.transaction('obsinhibit')
-            repo._obsinhibit.update(c.node() for c in newinhibit)
+            repo._obsinhibit.update(node(r) for r in newinhibit)
             _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
             repo.invalidatevolatilesets()
             tr.close()
@@ -176,6 +190,16 @@
     finally:
         lockmod.release(tr, lock)
 
+def _computeobsoletenotrebasedwrap(orig, repo, rebasesetrevs, dest):
+    repo._notinhibited = rebasesetrevs
+    try:
+        repo.invalidatevolatilesets()
+        r = orig(repo, rebasesetrevs, dest)
+    finally:
+        del repo._notinhibited
+        repo.invalidatevolatilesets()
+    return r
+
 def transactioncallback(orig, repo, desc, *args, **kwargs):
     """ Wrap localrepo.transaction to inhibit new obsolete changes """
     def inhibitposttransaction(transaction):
@@ -188,7 +212,8 @@
             _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
     transaction = orig(repo, desc, *args, **kwargs)
     if desc != 'strip' and _inhibitenabled(repo):
-        transaction.addpostclose('inhibitposttransaction', inhibitposttransaction)
+        transaction.addpostclose('inhibitposttransaction',
+                                 inhibitposttransaction)
     return transaction
 
 def extsetup(ui):
@@ -202,8 +227,10 @@
         obs = obsfunc(repo)
         if _inhibitenabled(repo):
             getrev = repo.changelog.nodemap.get
+            blacklist = getattr(repo, '_notinhibited', set())
             for n in repo._obsinhibit:
-                obs.discard(getrev(n))
+                if getrev(n) not in blacklist:
+                    obs.discard(getrev(n))
         return obs
     try:
         extensions.find('directaccess')
@@ -228,6 +255,14 @@
     # wrap update to make sure that no obsolete commit is visible after an
     # update
     extensions.wrapcommand(commands.table, 'update', _update)
+    try:
+        rebase = extensions.find('rebase')
+        if rebase:
+            extensions.wrapfunction(rebase,
+                                    '_computeobsoletenotrebased',
+                                    _computeobsoletenotrebasedwrap)
+    except KeyError:
+        pass
     # There are two ways to save bookmark changes during a transation, we
     # wrap both to add inhibition markers.
     extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged)
--- a/hgext/obsolete.py	Sun Jan 03 15:51:36 2016 +0100
+++ b/hgext/obsolete.py	Sun Jan 03 16:47:57 2016 +0100
@@ -5,9 +5,12 @@
 # GNU General Public License version 2 or any later version.
 """Deprecated extension that formely introduces "Changeset Obsolescence".
 
-This concept is now partially in Mercurial core (starting with mercurial 2.3). The remaining logic have been grouped with the evolve extension.
+This concept is now partially in Mercurial core (starting with mercurial 2.3).
+The remaining logic have been grouped with the evolve extension.
 
-Some code cemains in this extensions to detect and convert prehistoric format of obsolete marker than early user may have create. Keep it enabled if you were such user.
+Some code cemains in this extensions to detect and convert prehistoric format
+of obsolete marker than early user may have create. Keep it enabled if you
+were such user.
 """
 
 from mercurial import util
@@ -15,13 +18,14 @@
 try:
     from mercurial import obsolete
 except ImportError:
-    raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)')
+    raise error.Abort('Obsolete extension requires Mercurial 2.3 (or later)')
 
 import sys
 import json
 
 from mercurial import cmdutil
 from mercurial import error
+from mercurial.i18n import _
 from mercurial.node import bin, nullid
 
 
@@ -50,8 +54,8 @@
         if not data:
             data = repo.svfs.tryread('obsoletemarkers')
         if data:
-            raise util.Abort('old format of obsolete marker detected!\n'
-                             'run `hg debugconvertobsolete` once.')
+            raise error.Abort('old format of obsolete marker detected!\n'
+                              'run `hg debugconvertobsolete` once.')
 
 def _obsdeserialise(flike):
     """read a file like object serialised with _obsserialise
@@ -65,7 +69,7 @@
         subnode = bin(subhex)
         if subnode == nullid:
             subnode = None
-        rels.setdefault( subnode, set()).add(bin(objhex))
+        rels.setdefault(subnode, set()).add(bin(objhex))
     return rels
 
 cmdtable = {}
@@ -153,7 +157,7 @@
         del repo._importoldobsolete
         l.release()
     if not some:
-            ui.warn('nothing to do\n')
+        ui.warn(_('nothing to do\n'))
     ui.status('%i obsolete marker converted\n' % cnt)
     if err:
         ui.write_err('%i conversion failed. check you graph!\n' % err)
--- a/hgext/pushexperiment.py	Sun Jan 03 15:51:36 2016 +0100
+++ b/hgext/pushexperiment.py	Sun Jan 03 16:47:57 2016 +0100
@@ -3,7 +3,8 @@
 - Add a new wire protocol command to exchange obsolescence markers. Sending the
   raw file as a binary instead of using pushkey hack.
 - Add a "push done" notification
-- Push obsolescence marker before anything else (This works around the lack of global transaction)
+- Push obsolescence marker before anything else (This works around the lack
+of global transaction)
 
 """
 
@@ -61,7 +62,8 @@
 
 def client_notifypushend(self):
     """wire peer  command to notify a push is done"""
-    self.requirecap('_push_experiment_notifypushend_0', _('hook once push is all done'))
+    self.requirecap('_push_experiment_notifypushend_0',
+                    _('hook once push is all done'))
     return self._call('push_experiment_notifypushend_0')
 
 
@@ -75,7 +77,7 @@
 def augmented_push(orig, repo, remote, *args, **kwargs):
     """push wrapped that call the wire protocol command"""
     if not remote.canpush():
-        raise util.Abort(_("destination does not support push"))
+        raise error.Abort(_("destination does not support push"))
     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.
@@ -104,8 +106,10 @@
 def extsetup(ui):
     wireproto.wirepeer.push_experiment_pushobsmarkers_0 = client_pushobsmarkers
     wireproto.wirepeer.push_experiment_notifypushend_0 = client_notifypushend
-    wireproto.commands['push_experiment_pushobsmarkers_0'] = (srv_pushobsmarkers, '')
-    wireproto.commands['push_experiment_notifypushend_0'] = (srv_notifypushend, '')
+    wireproto.commands['push_experiment_pushobsmarkers_0'] = \
+        (srv_pushobsmarkers, '')
+    wireproto.commands['push_experiment_notifypushend_0'] = \
+        (srv_notifypushend, '')
     extensions.wrapfunction(wireproto, 'capabilities', capabilities)
     extensions.wrapfunction(obsolete, 'syncpush', syncpush)
     extensions.wrapfunction(localrepo.localrepository, 'push', augmented_push)
--- a/hgext/simple4server.py	Sun Jan 03 15:51:36 2016 +0100
+++ b/hgext/simple4server.py	Sun Jan 03 16:47:57 2016 +0100
@@ -34,8 +34,8 @@
     gboptslist = getattr(wireproto, 'gboptslist', None)
     gboptsmap = getattr(wireproto, 'gboptsmap', None)
 except (ImportError, AttributeError):
-    raise util.Abort('Your Mercurial is too old for this version of Evolve\n'
-                     'requires version 3.0.1 or above')
+    raise error.Abort('Your Mercurial is too old for this version of Evolve\n'
+                      'requires version 3.0.1 or above')
 
 # Start of simple4server specific content
 
@@ -101,10 +101,11 @@
 if not util.safehasattr(obsolete.obsstore, 'relevantmarkers'):
     # from evolve extension: 1a23c7c52a43
     class pruneobsstore(obsolete.obsstore):
-        """And extended obsstore class that read parent information from v1 format
+        """And extended obsstore class that read parent information from v1
+        format
 
-        Evolve extension adds parent information in prune marker. We use it to make
-        markers relevant to pushed changeset."""
+        Evolve extension adds parent information in prune marker.
+        We use it to make markers relevant to pushed changeset."""
 
         def __init__(self, *args, **kwargs):
             self.prunedchildren = {}
@@ -237,7 +238,8 @@
 
 # from evolve extension: 3249814dabd1
 def srv_obshash1(repo, proto, nodes):
-    return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes), version=1))
+    return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes),
+                                version=1))
 
 # from evolve extension: 3249814dabd1
 def capabilities(orig, repo, proto):
--- a/tests/dummyssh	Sun Jan 03 15:51:36 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-import os
-
-os.chdir(os.getenv('TESTTMP'))
-
-if sys.argv[1] != "user@dummy":
-    sys.exit(-1)
-
-os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
-
-log = open("dummylog", "ab")
-log.write("Got arguments")
-for i, arg in enumerate(sys.argv[1:]):
-    log.write(" %d:%s" % (i + 1, arg))
-log.write("\n")
-log.close()
-hgcmd = sys.argv[2]
-if os.name == 'nt':
-    # hack to make simple unix single quote quoting work on windows
-    hgcmd = hgcmd.replace("'", '"')
-r = os.system(hgcmd)
-sys.exit(bool(r))
--- a/tests/killdaemons.py	Sun Jan 03 15:51:36 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-#!/usr/bin/env python
-
-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)
-
-    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:
-            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)
-            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
-
-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/run-tests.py	Sun Jan 03 15:51:36 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1128 +0,0 @@
-#!/usr/bin/env python
-#
-# run-tests.py - Run a set of tests on Mercurial
-#
-# Copyright 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-# Modifying this script is tricky because it has many modes:
-#   - serial (default) vs parallel (-jN, N > 1)
-#   - no coverage (default) vs coverage (-c, -C, -s)
-#   - temp install (default) vs specific hg script (--with-hg, --local)
-#   - tests are a mix of shell scripts and Python scripts
-#
-# If you change this script, it is recommended that you ensure you
-# haven't broken it by running it in various modes with a representative
-# sample of test scripts.  For example:
-#
-#  1) serial, no coverage, temp install:
-#      ./run-tests.py test-s*
-#  2) serial, no coverage, local hg:
-#      ./run-tests.py --local test-s*
-#  3) serial, coverage, temp install:
-#      ./run-tests.py -c test-s*
-#  4) serial, coverage, local hg:
-#      ./run-tests.py -c --local test-s*      # unsupported
-#  5) parallel, no coverage, temp install:
-#      ./run-tests.py -j2 test-s*
-#  6) parallel, no coverage, local hg:
-#      ./run-tests.py -j2 --local test-s*
-#  7) parallel, coverage, temp install:
-#      ./run-tests.py -j2 -c test-s*          # currently broken
-#  8) parallel, coverage, local install:
-#      ./run-tests.py -j2 -c --local test-s*  # unsupported (and broken)
-#  9) parallel, custom tmp dir:
-#      ./run-tests.py -j2 --tmpdir /tmp/myhgtests
-#
-# (You could use any subset of the tests: test-s* happens to match
-# enough that it's worth doing parallel runs, few enough that it
-# completes fairly quickly, includes both shell and Python scripts, and
-# includes some scripts that run daemon processes.)
-
-from distutils import version
-import difflib
-import errno
-import optparse
-import os
-import shutil
-import subprocess
-import signal
-import sys
-import tempfile
-import time
-import re
-
-closefds = os.name == 'posix'
-def Popen4(cmd, bufsize=-1):
-    p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
-                         close_fds=closefds,
-                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-                         stderr=subprocess.STDOUT)
-    p.fromchild = p.stdout
-    p.tochild = p.stdin
-    p.childerr = p.stderr
-    return p
-
-# reserved exit code to skip test (used by hghave)
-SKIPPED_STATUS = 80
-SKIPPED_PREFIX = 'skipped: '
-FAILED_PREFIX  = 'hghave check failed: '
-PYTHON = sys.executable
-IMPL_PATH = 'PYTHONPATH'
-if 'java' in sys.platform:
-    IMPL_PATH = 'JYTHONPATH'
-
-requiredtools = ["python", "diff", "grep", "sed"]
-
-defaults = {
-    'jobs': ('HGTEST_JOBS', 1),
-    'timeout': ('HGTEST_TIMEOUT', 180),
-    'port': ('HGTEST_PORT', 20059),
-}
-
-def parseargs():
-    parser = optparse.OptionParser("%prog [options] [tests]")
-
-    # keep these sorted
-    parser.add_option("--blacklist", action="append",
-        help="skip tests listed in the specified blacklist file")
-    parser.add_option("-C", "--annotate", action="store_true",
-        help="output files annotated with coverage")
-    parser.add_option("--child", type="int",
-        help="run as child process, summary to given fd")
-    parser.add_option("-c", "--cover", action="store_true",
-        help="print a test coverage report")
-    parser.add_option("-d", "--debug", action="store_true",
-        help="debug mode: write output of test scripts to console"
-             " rather than capturing and diff'ing it (disables timeout)")
-    parser.add_option("-f", "--first", action="store_true",
-        help="exit on the first test failure")
-    parser.add_option("--inotify", action="store_true",
-        help="enable inotify extension when running tests")
-    parser.add_option("-i", "--interactive", action="store_true",
-        help="prompt to accept changed output")
-    parser.add_option("-j", "--jobs", type="int",
-        help="number of jobs to run in parallel"
-             " (default: $%s or %d)" % defaults['jobs'])
-    parser.add_option("--keep-tmpdir", action="store_true",
-        help="keep temporary directory after running tests")
-    parser.add_option("-k", "--keywords",
-        help="run tests matching keywords")
-    parser.add_option("-l", "--local", action="store_true",
-        help="shortcut for --with-hg=<testdir>/../hg")
-    parser.add_option("-n", "--nodiff", action="store_true",
-        help="skip showing test changes")
-    parser.add_option("-p", "--port", type="int",
-        help="port on which servers should listen"
-             " (default: $%s or %d)" % defaults['port'])
-    parser.add_option("--pure", action="store_true",
-        help="use pure Python code instead of C extensions")
-    parser.add_option("-R", "--restart", action="store_true",
-        help="restart at last error")
-    parser.add_option("-r", "--retest", action="store_true",
-        help="retest failed tests")
-    parser.add_option("-S", "--noskips", action="store_true",
-        help="don't report skip tests verbosely")
-    parser.add_option("-t", "--timeout", type="int",
-        help="kill errant tests after TIMEOUT seconds"
-             " (default: $%s or %d)" % defaults['timeout'])
-    parser.add_option("--tmpdir", type="string",
-        help="run tests in the given temporary directory"
-             " (implies --keep-tmpdir)")
-    parser.add_option("-v", "--verbose", action="store_true",
-        help="output verbose messages")
-    parser.add_option("--view", type="string",
-        help="external diff viewer")
-    parser.add_option("--with-hg", type="string",
-        metavar="HG",
-        help="test using specified hg script rather than a "
-             "temporary installation")
-    parser.add_option("-3", "--py3k-warnings", action="store_true",
-        help="enable Py3k warnings on Python 2.6+")
-
-    for option, default in defaults.items():
-        defaults[option] = int(os.environ.get(*default))
-    parser.set_defaults(**defaults)
-    (options, args) = parser.parse_args()
-
-    # jython is always pure
-    if 'java' in sys.platform or '__pypy__' in sys.modules:
-        options.pure = True
-
-    if options.with_hg:
-        if not (os.path.isfile(options.with_hg) and
-                os.access(options.with_hg, os.X_OK)):
-            parser.error('--with-hg must specify an executable hg script')
-        if not os.path.basename(options.with_hg) == 'hg':
-            sys.stderr.write('warning: --with-hg should specify an hg script')
-    if options.local:
-        testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
-        hgbin = os.path.join(os.path.dirname(testdir), 'hg')
-        if not os.access(hgbin, os.X_OK):
-            parser.error('--local specified, but %r not found or not executable'
-                         % hgbin)
-        options.with_hg = hgbin
-
-    options.anycoverage = options.cover or options.annotate
-    if options.anycoverage:
-        try:
-            import coverage
-            covver = version.StrictVersion(coverage.__version__).version
-            if covver < (3, 3):
-                parser.error('coverage options require coverage 3.3 or later')
-        except ImportError:
-            parser.error('coverage options now require the coverage package')
-
-    if options.anycoverage and options.local:
-        # this needs some path mangling somewhere, I guess
-        parser.error("sorry, coverage options do not work when --local "
-                     "is specified")
-
-    global vlog
-    if options.verbose:
-        if options.jobs > 1 or options.child is not None:
-            pid = "[%d]" % os.getpid()
-        else:
-            pid = None
-        def vlog(*msg):
-            if pid:
-                print pid,
-            for m in msg:
-                print m,
-            print
-            sys.stdout.flush()
-    else:
-        vlog = lambda *msg: None
-
-    if options.tmpdir:
-        options.tmpdir = os.path.expanduser(options.tmpdir)
-
-    if options.jobs < 1:
-        parser.error('--jobs must be positive')
-    if options.interactive and options.jobs > 1:
-        print '(--interactive overrides --jobs)'
-        options.jobs = 1
-    if options.interactive and options.debug:
-        parser.error("-i/--interactive and -d/--debug are incompatible")
-    if options.debug:
-        if options.timeout != defaults['timeout']:
-            sys.stderr.write(
-                'warning: --timeout option ignored with --debug\n')
-        options.timeout = 0
-    if options.py3k_warnings:
-        if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
-            parser.error('--py3k-warnings can only be used on Python 2.6+')
-    if options.blacklist:
-        blacklist = dict()
-        for filename in options.blacklist:
-            try:
-                path = os.path.expanduser(os.path.expandvars(filename))
-                f = open(path, "r")
-            except IOError, err:
-                if err.errno != errno.ENOENT:
-                    raise
-                print "warning: no such blacklist file: %s" % filename
-                continue
-
-            for line in f.readlines():
-                line = line.strip()
-                if line and not line.startswith('#'):
-                    blacklist[line] = filename
-
-            f.close()
-
-        options.blacklist = blacklist
-
-    return (options, args)
-
-def rename(src, dst):
-    """Like os.rename(), trade atomicity and opened files friendliness
-    for existing destination support.
-    """
-    shutil.copy(src, dst)
-    os.remove(src)
-
-def splitnewlines(text):
-    '''like str.splitlines, but only split on newlines.
-    keep line endings.'''
-    i = 0
-    lines = []
-    while True:
-        n = text.find('\n', i)
-        if n == -1:
-            last = text[i:]
-            if last:
-                lines.append(last)
-            return lines
-        lines.append(text[i:n + 1])
-        i = n + 1
-
-def parsehghaveoutput(lines):
-    '''Parse hghave log lines.
-    Return tuple of lists (missing, failed):
-      * the missing/unknown features
-      * the features for which existence check failed'''
-    missing = []
-    failed = []
-    for line in lines:
-        if line.startswith(SKIPPED_PREFIX):
-            line = line.splitlines()[0]
-            missing.append(line[len(SKIPPED_PREFIX):])
-        elif line.startswith(FAILED_PREFIX):
-            line = line.splitlines()[0]
-            failed.append(line[len(FAILED_PREFIX):])
-
-    return missing, failed
-
-def showdiff(expected, output, ref, err):
-    try:
-        for line in difflib.unified_diff(expected, output, ref, err):
-            sys.stdout.write(line)
-    except IOError, ex:
-        print >>sys.stderr, 'BORKEN PIPE', ex.errno
-        pass
-
-def findprogram(program):
-    """Search PATH for a executable program"""
-    for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
-        name = os.path.join(p, program)
-        if os.access(name, os.X_OK):
-            return name
-    return None
-
-def checktools():
-    # Before we go any further, check for pre-requisite tools
-    # stuff from coreutils (cat, rm, etc) are not tested
-    for p in requiredtools:
-        if os.name == 'nt':
-            p += '.exe'
-        found = findprogram(p)
-        if found:
-            vlog("# Found prerequisite", p, "at", found)
-        else:
-            print "WARNING: Did not find prerequisite tool: "+p
-
-def killdaemons():
-    # Kill off any leftover daemon processes
-    try:
-        fp = open(DAEMON_PIDS)
-        for line in fp:
-            try:
-                pid = int(line)
-            except ValueError:
-                continue
-            try:
-                os.kill(pid, 0)
-                vlog('# Killing daemon process %d' % pid)
-                os.kill(pid, signal.SIGTERM)
-                time.sleep(0.25)
-                os.kill(pid, 0)
-                vlog('# 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()
-        os.unlink(DAEMON_PIDS)
-    except IOError:
-        pass
-
-def cleanup(options):
-    if not options.keep_tmpdir:
-        vlog("# Cleaning up HGTMP", HGTMP)
-        shutil.rmtree(HGTMP, True)
-
-def usecorrectpython():
-    # some tests run python interpreter. they must use same
-    # interpreter we use or bad things will happen.
-    exedir, exename = os.path.split(sys.executable)
-    if exename == 'python':
-        path = findprogram('python')
-        if os.path.dirname(path) == exedir:
-            return
-    vlog('# Making python executable in test path use correct Python')
-    mypython = os.path.join(BINDIR, 'python')
-    try:
-        os.symlink(sys.executable, mypython)
-    except AttributeError:
-        # windows fallback
-        shutil.copyfile(sys.executable, mypython)
-        shutil.copymode(sys.executable, mypython)
-
-def installhg(options):
-    vlog("# Performing temporary installation of HG")
-    installerrs = os.path.join("tests", "install.err")
-    pure = options.pure and "--pure" or ""
-
-    # Run installer in hg root
-    script = os.path.realpath(sys.argv[0])
-    hgroot = os.path.dirname(os.path.dirname(script))
-    os.chdir(hgroot)
-    nohome = '--home=""'
-    if os.name == 'nt':
-        # The --home="" trick works only on OS where os.sep == '/'
-        # because of a distutils convert_path() fast-path. Avoid it at
-        # least on Windows for now, deal with .pydistutils.cfg bugs
-        # when they happen.
-        nohome = ''
-    cmd = ('%s setup.py %s clean --all'
-           ' build --build-base="%s"'
-           ' install --force --prefix="%s" --install-lib="%s"'
-           ' --install-scripts="%s" %s >%s 2>&1'
-           % (sys.executable, pure, os.path.join(HGTMP, "build"),
-              INST, PYTHONDIR, BINDIR, nohome, installerrs))
-    vlog("# Running", cmd)
-    if os.system(cmd) == 0:
-        if not options.verbose:
-            os.remove(installerrs)
-    else:
-        f = open(installerrs)
-        for line in f:
-            print line,
-        f.close()
-        sys.exit(1)
-    os.chdir(TESTDIR)
-
-    usecorrectpython()
-
-    vlog("# Installing dummy diffstat")
-    f = open(os.path.join(BINDIR, 'diffstat'), 'w')
-    f.write('#!' + sys.executable + '\n'
-            'import sys\n'
-            'files = 0\n'
-            'for line in sys.stdin:\n'
-            '    if line.startswith("diff "):\n'
-            '        files += 1\n'
-            'sys.stdout.write("files patched: %d\\n" % files)\n')
-    f.close()
-    os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
-
-    if options.py3k_warnings and not options.anycoverage:
-        vlog("# Updating hg command to enable Py3k Warnings switch")
-        f = open(os.path.join(BINDIR, 'hg'), 'r')
-        lines = [line.rstrip() for line in f]
-        lines[0] += ' -3'
-        f.close()
-        f = open(os.path.join(BINDIR, 'hg'), 'w')
-        for line in lines:
-            f.write(line + '\n')
-        f.close()
-
-    if options.anycoverage:
-        custom = os.path.join(TESTDIR, 'sitecustomize.py')
-        target = os.path.join(PYTHONDIR, 'sitecustomize.py')
-        vlog('# Installing coverage trigger to %s' % target)
-        shutil.copyfile(custom, target)
-        rc = os.path.join(TESTDIR, '.coveragerc')
-        vlog('# Installing coverage rc to %s' % rc)
-        os.environ['COVERAGE_PROCESS_START'] = rc
-        fn = os.path.join(INST, '..', '.coverage')
-        os.environ['COVERAGE_FILE'] = fn
-
-def outputcoverage(options):
-
-    vlog('# Producing coverage report')
-    os.chdir(PYTHONDIR)
-
-    def covrun(*args):
-        cmd = 'coverage %s' % ' '.join(args)
-        vlog('# Running: %s' % cmd)
-        os.system(cmd)
-
-    if options.child:
-        return
-
-    covrun('-c')
-    omit = ','.join([BINDIR, TESTDIR])
-    covrun('-i', '-r', '"--omit=%s"' % omit) # report
-    if options.annotate:
-        adir = os.path.join(TESTDIR, 'annotated')
-        if not os.path.isdir(adir):
-            os.mkdir(adir)
-        covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
-
-class Timeout(Exception):
-    pass
-
-def alarmed(signum, frame):
-    raise Timeout
-
-def pytest(test, options, replacements):
-    py3kswitch = options.py3k_warnings and ' -3' or ''
-    cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
-    vlog("# Running", cmd)
-    return run(cmd, options, replacements)
-
-def shtest(test, options, replacements):
-    cmd = '"%s"' % test
-    vlog("# Running", cmd)
-    return run(cmd, options, replacements)
-
-needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
-escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
-escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
-escapemap.update({'\\': '\\\\', '\r': r'\r'})
-def escapef(m):
-    return escapemap[m.group(0)]
-def stringescape(s):
-    return escapesub(escapef, s)
-
-def tsttest(test, options, replacements):
-    t = open(test)
-    out = []
-    script = []
-    salt = "SALT" + str(time.time())
-
-    pos = prepos = -1
-    after = {}
-    expected = {}
-    for n, l in enumerate(t):
-        if not l.endswith('\n'):
-            l += '\n'
-        if l.startswith('  $ '): # commands
-            after.setdefault(pos, []).append(l)
-            prepos = pos
-            pos = n
-            script.append('echo %s %s $?\n' % (salt, n))
-            script.append(l[4:])
-        elif l.startswith('  > '): # continuations
-            after.setdefault(prepos, []).append(l)
-            script.append(l[4:])
-        elif l.startswith('  '): # results
-            # queue up a list of expected results
-            expected.setdefault(pos, []).append(l[2:])
-        else:
-            # non-command/result - queue up for merged output
-            after.setdefault(pos, []).append(l)
-
-    t.close()
-
-    script.append('echo %s %s $?\n' % (salt, n + 1))
-
-    fd, name = tempfile.mkstemp(suffix='hg-tst')
-
-    try:
-        for l in script:
-            os.write(fd, l)
-        os.close(fd)
-
-        cmd = '/bin/sh "%s"' % name
-        vlog("# Running", cmd)
-        exitcode, output = run(cmd, options, replacements)
-        # do not merge output if skipped, return hghave message instead
-        # similarly, with --debug, output is None
-        if exitcode == SKIPPED_STATUS or output is None:
-            return exitcode, output
-    finally:
-        os.remove(name)
-
-    def rematch(el, l):
-        try:
-            # ensure that the regex matches to the end of the string
-            return re.match(el + r'\Z', l)
-        except re.error:
-            # el is an invalid regex
-            return False
-
-    def globmatch(el, l):
-        # The only supported special characters are * and ?. Escaping is
-        # supported.
-        i, n = 0, len(el)
-        res = ''
-        while i < n:
-            c = el[i]
-            i += 1
-            if c == '\\' and el[i] in '*?\\':
-                res += el[i - 1:i + 1]
-                i += 1
-            elif c == '*':
-                res += '.*'
-            elif c == '?':
-                res += '.'
-            else:
-                res += re.escape(c)
-        return rematch(res, l)
-
-    pos = -1
-    postout = []
-    ret = 0
-    for n, l in enumerate(output):
-        lout, lcmd = l, None
-        if salt in l:
-            lout, lcmd = l.split(salt, 1)
-
-        if lout:
-            if lcmd:
-                lout += ' (no-eol)\n'
-
-            el = None
-            if pos in expected and expected[pos]:
-                el = expected[pos].pop(0)
-
-            if el == lout: # perfect match (fast)
-                postout.append("  " + lout)
-            elif (el and
-                  (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
-                   el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
-                   or el.endswith(" (esc)\n") and
-                      el.decode('string-escape') == l)):
-                postout.append("  " + el) # fallback regex/glob/esc match
-            else:
-                if needescape(lout):
-                    lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
-                postout.append("  " + lout) # let diff deal with it
-
-        if lcmd:
-            # add on last return code
-            ret = int(lcmd.split()[1])
-            if ret != 0:
-                postout.append("  [%s]\n" % ret)
-            if pos in after:
-                postout += after.pop(pos)
-            pos = int(lcmd.split()[0])
-
-    if pos in after:
-        postout += after.pop(pos)
-
-    return exitcode, postout
-
-wifexited = getattr(os, "WIFEXITED", lambda x: False)
-def run(cmd, options, replacements):
-    """Run command in a sub-process, capturing the output (stdout and stderr).
-    Return a tuple (exitcode, output).  output is None in debug mode."""
-    # TODO: Use subprocess.Popen if we're running on Python 2.4
-    if options.debug:
-        proc = subprocess.Popen(cmd, shell=True)
-        ret = proc.wait()
-        return (ret, None)
-
-    if os.name == 'nt' or sys.platform.startswith('java'):
-        tochild, fromchild = os.popen4(cmd)
-        tochild.close()
-        output = fromchild.read()
-        ret = fromchild.close()
-        if ret is None:
-            ret = 0
-    else:
-        proc = Popen4(cmd)
-        def cleanup():
-            os.kill(proc.pid, signal.SIGTERM)
-            ret = proc.wait()
-            if ret == 0:
-                ret = signal.SIGTERM << 8
-            killdaemons()
-            return ret
-
-        try:
-            output = ''
-            proc.tochild.close()
-            output = proc.fromchild.read()
-            ret = proc.wait()
-            if wifexited(ret):
-                ret = os.WEXITSTATUS(ret)
-        except Timeout:
-            vlog('# Process %d timed out - killing it' % proc.pid)
-            ret = cleanup()
-            output += ("\n### Abort: timeout after %d seconds.\n"
-                       % options.timeout)
-        except KeyboardInterrupt:
-            vlog('# Handling keyboard interrupt')
-            cleanup()
-            raise
-
-    for s, r in replacements:
-        output = re.sub(s, r, output)
-    return ret, splitnewlines(output)
-
-def runone(options, test, skips, fails):
-    '''tristate output:
-    None -> skipped
-    True -> passed
-    False -> failed'''
-
-    def skip(msg):
-        if not options.verbose:
-            skips.append((test, msg))
-        else:
-            print "\nSkipping %s: %s" % (testpath, msg)
-        return None
-
-    def fail(msg):
-        fails.append((test, msg))
-        if not options.nodiff:
-            print "\nERROR: %s %s" % (testpath, msg)
-        return None
-
-    vlog("# Test", test)
-
-    # create a fresh hgrc
-    hgrc = open(HGRCPATH, 'w+')
-    hgrc.write('[ui]\n')
-    hgrc.write('slash = True\n')
-    hgrc.write('[defaults]\n')
-    hgrc.write('backout = -d "0 0"\n')
-    hgrc.write('commit = -d "0 0"\n')
-    hgrc.write('tag = -d "0 0"\n')
-    if options.inotify:
-        hgrc.write('[extensions]\n')
-        hgrc.write('inotify=\n')
-        hgrc.write('[inotify]\n')
-        hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
-        hgrc.write('appendpid=True\n')
-    hgrc.close()
-
-    testpath = os.path.join(TESTDIR, test)
-    ref = os.path.join(TESTDIR, test+".out")
-    err = os.path.join(TESTDIR, test+".err")
-    if os.path.exists(err):
-        os.remove(err)       # Remove any previous output files
-    try:
-        tf = open(testpath)
-        firstline = tf.readline().rstrip()
-        tf.close()
-    except:
-        firstline = ''
-    lctest = test.lower()
-
-    if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
-        runner = pytest
-    elif lctest.endswith('.t'):
-        runner = tsttest
-        ref = testpath
-    else:
-        # do not try to run non-executable programs
-        if not os.access(testpath, os.X_OK):
-            return skip("not executable")
-        runner = shtest
-
-    # Make a tmp subdirectory to work in
-    testtmp = os.environ["TESTTMP"] = os.path.join(HGTMP, test)
-    os.mkdir(testtmp)
-    os.chdir(testtmp)
-
-    if options.timeout > 0:
-        signal.alarm(options.timeout)
-
-    ret, out = runner(testpath, options, [
-        (re.escape(testtmp), '$TESTTMP'),
-        (r':%s\b' % options.port, ':$HGPORT'),
-        (r':%s\b' % (options.port + 1), ':$HGPORT1'),
-        (r':%s\b' % (options.port + 2), ':$HGPORT2'),
-        ])
-    vlog("# Ret was:", ret)
-
-    if options.timeout > 0:
-        signal.alarm(0)
-
-    mark = '.'
-
-    skipped = (ret == SKIPPED_STATUS)
-
-    # If we're not in --debug mode and reference output file exists,
-    # check test output against it.
-    if options.debug:
-        refout = None                   # to match "out is None"
-    elif os.path.exists(ref):
-        f = open(ref, "r")
-        refout = splitnewlines(f.read())
-        f.close()
-    else:
-        refout = []
-
-    if (ret != 0 or out != refout) and not skipped and not options.debug:
-        # Save errors to a file for diagnosis
-        f = open(err, "wb")
-        for line in out:
-            f.write(line)
-        f.close()
-
-    if skipped:
-        mark = 's'
-        if out is None:                 # debug mode: nothing to parse
-            missing = ['unknown']
-            failed = None
-        else:
-            missing, failed = parsehghaveoutput(out)
-        if not missing:
-            missing = ['irrelevant']
-        if failed:
-            fail("hghave failed checking for %s" % failed[-1])
-            skipped = False
-        else:
-            skip(missing[-1])
-    elif out != refout:
-        mark = '!'
-        if ret:
-            fail("output changed and returned error code %d" % ret)
-        else:
-            fail("output changed")
-        if not options.nodiff:
-            if options.view:
-                os.system("%s %s %s" % (options.view, ref, err))
-            else:
-                showdiff(refout, out, ref, err)
-        ret = 1
-    elif ret:
-        mark = '!'
-        fail("returned error code %d" % ret)
-
-    if not options.verbose:
-        try:
-            sys.stdout.write(mark)
-            sys.stdout.flush()
-        except IOError, ex:
-            print >>sys.stderr, 'BORKEN PIPE', ex.errno
-            pass
-
-    killdaemons()
-
-    os.chdir(TESTDIR)
-    if not options.keep_tmpdir:
-        shutil.rmtree(testtmp, True)
-    if skipped:
-        return None
-    return ret == 0
-
-_hgpath = None
-
-def _gethgpath():
-    """Return the path to the mercurial package that is actually found by
-    the current Python interpreter."""
-    global _hgpath
-    if _hgpath is not None:
-        return _hgpath
-
-    cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
-    pipe = os.popen(cmd % PYTHON)
-    try:
-        _hgpath = pipe.read().strip()
-    finally:
-        pipe.close()
-    return _hgpath
-
-def _checkhglib(verb):
-    """Ensure that the 'mercurial' package imported by python is
-    the one we expect it to be.  If not, print a warning to stderr."""
-    expecthg = os.path.join(PYTHONDIR, 'mercurial')
-    actualhg = _gethgpath()
-    if actualhg != expecthg:
-        sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
-                         '         (expected %s)\n'
-                         % (verb, actualhg, expecthg))
-
-def runchildren(options, tests):
-    if INST:
-        installhg(options)
-        _checkhglib("Testing")
-
-    optcopy = dict(options.__dict__)
-    optcopy['jobs'] = 1
-    del optcopy['blacklist']
-    if optcopy['with_hg'] is None:
-        optcopy['with_hg'] = os.path.join(BINDIR, "hg")
-    optcopy.pop('anycoverage', None)
-
-    opts = []
-    for opt, value in optcopy.iteritems():
-        name = '--' + opt.replace('_', '-')
-        if value is True:
-            opts.append(name)
-        elif value is not None:
-            opts.append(name + '=' + str(value))
-
-    tests.reverse()
-    jobs = [[] for j in xrange(options.jobs)]
-    while tests:
-        for job in jobs:
-            if not tests:
-                break
-            job.append(tests.pop())
-    fps = {}
-
-    for j, job in enumerate(jobs):
-        if not job:
-            continue
-        rfd, wfd = os.pipe()
-        childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
-        childtmp = os.path.join(HGTMP, 'child%d' % j)
-        childopts += ['--tmpdir', childtmp]
-        cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
-        vlog(' '.join(cmdline))
-        fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
-        os.close(wfd)
-    signal.signal(signal.SIGINT, signal.SIG_IGN)
-    failures = 0
-    tested, skipped, failed = 0, 0, 0
-    skips = []
-    fails = []
-    while fps:
-        pid, status = os.wait()
-        fp = fps.pop(pid)
-        l = fp.read().splitlines()
-        try:
-            test, skip, fail = map(int, l[:3])
-        except ValueError:
-            test, skip, fail = 0, 0, 0
-        split = -fail or len(l)
-        for s in l[3:split]:
-            skips.append(s.split(" ", 1))
-        for s in l[split:]:
-            fails.append(s.split(" ", 1))
-        tested += test
-        skipped += skip
-        failed += fail
-        vlog('pid %d exited, status %d' % (pid, status))
-        failures |= status
-    print
-    if not options.noskips:
-        for s in skips:
-            print "Skipped %s: %s" % (s[0], s[1])
-    for s in fails:
-        print "Failed %s: %s" % (s[0], s[1])
-
-    _checkhglib("Tested")
-    print "# Ran %d tests, %d skipped, %d failed." % (
-        tested, skipped, failed)
-
-    if options.anycoverage:
-        outputcoverage(options)
-    sys.exit(failures != 0)
-
-def runtests(options, tests):
-    global DAEMON_PIDS, HGRCPATH
-    DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
-    HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
-
-    try:
-        if INST:
-            installhg(options)
-            _checkhglib("Testing")
-
-        if options.timeout > 0:
-            try:
-                signal.signal(signal.SIGALRM, alarmed)
-                vlog('# Running each test with %d second timeout' %
-                     options.timeout)
-            except AttributeError:
-                print 'WARNING: cannot run tests with timeouts'
-                options.timeout = 0
-
-        tested = 0
-        failed = 0
-        skipped = 0
-
-        if options.restart:
-            orig = list(tests)
-            while tests:
-                if os.path.exists(tests[0] + ".err"):
-                    break
-                tests.pop(0)
-            if not tests:
-                print "running all tests"
-                tests = orig
-
-        skips = []
-        fails = []
-
-        for test in tests:
-            if options.blacklist:
-                filename = options.blacklist.get(test)
-                if filename is not None:
-                    skips.append((test, "blacklisted (%s)" % filename))
-                    skipped += 1
-                    continue
-
-            if options.retest and not os.path.exists(test + ".err"):
-                skipped += 1
-                continue
-
-            if options.keywords:
-                fp = open(test)
-                t = fp.read().lower() + test.lower()
-                fp.close()
-                for k in options.keywords.lower().split():
-                    if k in t:
-                        break
-                else:
-                    skipped += 1
-                    continue
-
-            ret = runone(options, test, skips, fails)
-            if ret is None:
-                skipped += 1
-            elif not ret:
-                if options.interactive:
-                    print "Accept this change? [n] ",
-                    answer = sys.stdin.readline().strip()
-                    if answer.lower() in "y yes".split():
-                        if test.endswith(".t"):
-                            rename(test + ".err", test)
-                        else:
-                            rename(test + ".err", test + ".out")
-                        tested += 1
-                        fails.pop()
-                        continue
-                failed += 1
-                if options.first:
-                    break
-            tested += 1
-
-        if options.child:
-            fp = os.fdopen(options.child, 'w')
-            fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
-            for s in skips:
-                fp.write("%s %s\n" % s)
-            for s in fails:
-                fp.write("%s %s\n" % s)
-            fp.close()
-        else:
-            print
-            for s in skips:
-                print "Skipped %s: %s" % s
-            for s in fails:
-                print "Failed %s: %s" % s
-            _checkhglib("Tested")
-            print "# Ran %d tests, %d skipped, %d failed." % (
-                tested, skipped, failed)
-
-        if options.anycoverage:
-            outputcoverage(options)
-    except KeyboardInterrupt:
-        failed = True
-        print "\ninterrupted!"
-
-    if failed:
-        sys.exit(1)
-
-def main():
-    (options, args) = parseargs()
-    if not options.child:
-        os.umask(022)
-
-        checktools()
-
-    if len(args) == 0:
-        args = os.listdir(".")
-    args.sort()
-
-    tests = []
-    skipped = []
-    for test in args:
-        if (test.startswith("test-") and '~' not in test and
-            ('.' not in test or test.endswith('.py') or
-             test.endswith('.bat') or test.endswith('.t'))):
-            if not os.path.exists(test):
-                skipped.append(test)
-            else:
-                tests.append(test)
-    if not tests:
-        for test in skipped:
-            print 'Skipped %s: does not exist' % test
-        print "# Ran 0 tests, %d skipped, 0 failed." % len(skipped)
-        return
-    tests = tests + skipped
-
-    # Reset some environment variables to well-known values so that
-    # the tests produce repeatable output.
-    os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
-    os.environ['TZ'] = 'GMT'
-    os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
-    os.environ['CDPATH'] = ''
-    os.environ['COLUMNS'] = '80'
-    os.environ['GREP_OPTIONS'] = ''
-    os.environ['http_proxy'] = ''
-
-    # unset env related to hooks
-    for k in os.environ.keys():
-        if k.startswith('HG_'):
-            # can't remove on solaris
-            os.environ[k] = ''
-            del os.environ[k]
-
-    global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
-    TESTDIR = os.environ["TESTDIR"] = os.getcwd()
-    if options.tmpdir:
-        options.keep_tmpdir = True
-        tmpdir = options.tmpdir
-        if os.path.exists(tmpdir):
-            # Meaning of tmpdir has changed since 1.3: we used to create
-            # HGTMP inside tmpdir; now HGTMP is tmpdir.  So fail if
-            # tmpdir already exists.
-            sys.exit("error: temp dir %r already exists" % tmpdir)
-
-            # Automatically removing tmpdir sounds convenient, but could
-            # really annoy anyone in the habit of using "--tmpdir=/tmp"
-            # or "--tmpdir=$HOME".
-            #vlog("# Removing temp dir", tmpdir)
-            #shutil.rmtree(tmpdir)
-        os.makedirs(tmpdir)
-    else:
-        tmpdir = tempfile.mkdtemp('', 'hgtests.')
-    HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
-    DAEMON_PIDS = None
-    HGRCPATH = None
-
-    os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
-    os.environ["HGMERGE"] = "internal:merge"
-    os.environ["HGUSER"]   = "test"
-    os.environ["HGENCODING"] = "ascii"
-    os.environ["HGENCODINGMODE"] = "strict"
-    os.environ["HGPORT"] = str(options.port)
-    os.environ["HGPORT1"] = str(options.port + 1)
-    os.environ["HGPORT2"] = str(options.port + 2)
-
-    if options.with_hg:
-        INST = None
-        BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
-
-        # This looks redundant with how Python initializes sys.path from
-        # the location of the script being executed.  Needed because the
-        # "hg" specified by --with-hg is not the only Python script
-        # executed in the test suite that needs to import 'mercurial'
-        # ... which means it's not really redundant at all.
-        PYTHONDIR = BINDIR
-    else:
-        INST = os.path.join(HGTMP, "install")
-        BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
-        PYTHONDIR = os.path.join(INST, "lib", "python")
-
-    os.environ["BINDIR"] = BINDIR
-    os.environ["PYTHON"] = PYTHON
-
-    if not options.child:
-        path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
-        os.environ["PATH"] = os.pathsep.join(path)
-
-        # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
-        # can run .../tests/run-tests.py test-foo where test-foo
-        # adds an extension to HGRC
-        pypath = [PYTHONDIR, TESTDIR]
-        # We have to augment PYTHONPATH, rather than simply replacing
-        # it, in case external libraries are only available via current
-        # PYTHONPATH.  (In particular, the Subversion bindings on OS X
-        # are in /opt/subversion.)
-        oldpypath = os.environ.get(IMPL_PATH)
-        if oldpypath:
-            pypath.append(oldpypath)
-        os.environ[IMPL_PATH] = os.pathsep.join(pypath)
-
-    COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
-
-    vlog("# Using TESTDIR", TESTDIR)
-    vlog("# Using HGTMP", HGTMP)
-    vlog("# Using PATH", os.environ["PATH"])
-    vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
-
-    try:
-        if len(tests) > 1 and options.jobs > 1:
-            runchildren(options, tests)
-        else:
-            runtests(options, tests)
-    finally:
-        time.sleep(1)
-        cleanup(options)
-
-if __name__ == '__main__':
-    main()
--- a/tests/test-divergent.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-divergent.t	Sun Jan 03 16:47:57 2016 +0100
@@ -67,4 +67,45 @@
   |
   o  0:135f39f4bd78@default(draft) add _a []
   
+Test divergence resolution when it yields to an empty commit (issue4950)
+cdivergent2 contains the same content than cdivergent1 and they are divergent
+versions of the revision _c
+
+  $ hg up "desc(_a)"
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ mkcommit _c
+  created new head
+  $ hg up "desc(_a)"
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit cdivergent1
+  created new head
+  $ hg up "desc(_a)"
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo "cdivergent1" > cdivergent1
+  $ hg add cdivergent1
+  $ hg ci -m "cdivergent2"
+  created new head
+  $ hg prune -s "desc(cdivergent1)" "desc(_c)"
+  1 changesets pruned
+  $ hg prune -s "desc(cdivergent2)" "desc(_c)" --hidden
+  1 changesets pruned
+  2 new divergent changesets
+  $ hg log -G
+  @  8:0a768ef678d9@default(draft) cdivergent2 [divergent]
+  |
+  | o  7:26c7705fee96@default(draft) add cdivergent1 [divergent]
+  |/
+  | o  5:c26f1d3baed2@default(draft) add bdivergent1 []
+  |/
+  o  0:135f39f4bd78@default(draft) add _a []
+  
+  $ hg evolve --all --any --divergent
+  merge:[7] add cdivergent1
+  with: [8] cdivergent2
+  base: [6] add _c
+  updating to "local" conflict
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at 6602ff5a79dc
+ 
   $ cd ..  
--- a/tests/test-exchange-A3.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-exchange-A3.t	Sun Jan 03 16:47:57 2016 +0100
@@ -204,7 +204,7 @@
   adding changesets
   adding manifests
   adding file changes
-  added 1 changesets with 1 changes to 2 files (+1 heads)
+  added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   (run 'hg heads' to see heads, 'hg merge' to merge)
   1 new unstable changesets
--- a/tests/test-inhibit.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-inhibit.t	Sun Jan 03 16:47:57 2016 +0100
@@ -305,6 +305,16 @@
   |
   o  0:54ccbc537fc2 add cA
   
+Test edge cases of bookmark -D
+  $ hg book -D book2 -m hello
+  abort: Cannot use both -m and -D
+  [255]
+
+  $ hg book -Draster-fix
+  abort: Error, please check your command
+  (make sure to put a space between -D and your bookmark name)
+  [255]
+
 Test that direct access make changesets visible
 
   $ hg export 2db36d8066ff 02bcbc3f6e56
@@ -437,6 +447,7 @@
   |/
   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
@@ -525,8 +536,14 @@
   |/
   o  14:d66ccb8c5871 add cL
   |
-  $ hg strip -r 104eed5354c7
-  1 changesets pruned
+  $ hg strip -r 210589181b14
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory now at d66ccb8c5871
+  2 changesets pruned
+
+Using a hash prefix solely made of digits should work
+  $ hg update 210589181
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg rebase -d 18 -r 16 --keep
   rebasing 16:a438c045eb37 "add cN"
   $ hg log -r 14:: -G
@@ -651,7 +668,7 @@
   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)
+  (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)
@@ -700,6 +717,17 @@
   nothing changed
   [1]
 
+Check that the behavior of rebase with obsolescence markers is maintained
+despite inhibit
+
+  $ hg up a438c045eb37
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg rebase -r 15:: -d 21 --config experimental.rebaseskipobsolete=True
+  note: not rebasing 15:2d66e189f5b5 "add cM", already in destination as 21:721c3c279519 "add cM"
+  rebasing 16:a438c045eb37 "add cN"
+  $ hg up 21
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
 Directaccess should load after some extensions precised in the conf
 With no extension specified:
 
@@ -714,7 +742,7 @@
   > EOF
   $ hg id
   ['rebase', 'strip', 'evolve', 'directaccess', 'inhibit', 'testextension']
-  721c3c279519 tip
+  721c3c279519
 
 With test_extension specified:
   $ cat >> $HGRCPATH << EOF
@@ -723,7 +751,7 @@
   > EOF
   $ hg id
   ['rebase', 'strip', 'evolve', 'inhibit', 'testextension', 'directaccess']
-  721c3c279519 tip
+  721c3c279519
 
 Inhibit should not work without directaccess
   $ cat >> $HGRCPATH <<EOF
@@ -738,7 +766,6 @@
   $ 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
 
@@ -746,6 +773,7 @@
   $ pwd=$(pwd)
   $ cd inhibit
   $ mkcommit pk
+  created new head
   $ hg id
   003a4735afde tip
   $ echo "OO" > pk
@@ -767,7 +795,7 @@
   adding changesets
   adding manifests
   adding file changes
-  added 1 changesets with 1 changes to 1 files
+  added 1 changesets with 1 changes to 1 files (+1 heads)
   2 new obsolescence markers
 
 Pulling from a inhibit repo to a non-inhibit repo should work
--- a/tests/test-prune.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-prune.t	Sun Jan 03 16:47:57 2016 +0100
@@ -279,11 +279,15 @@
   $ cd ..
   $ hg init bookmarks
   $ cd bookmarks
-  $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b'
+  $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b<m+2:d<2.:e<m+1:f'
   $ hg bookmark -r 'a' 'todelete'
   $ hg bookmark -r 'b' 'B'
   $ hg bookmark -r 'b' 'nostrip'
   $ hg bookmark -r 'c' 'delete'
+  $ hg bookmark -r 'd' 'multipledelete1'
+  $ hg bookmark -r 'e' 'multipledelete2'
+  $ hg bookmark -r 'f' 'singlenode1'
+  $ hg bookmark -r 'f' 'singlenode2'
   $ hg up -C todelete
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark todelete)
@@ -307,6 +311,27 @@
   $ hg bookmarks
      B                         10:ff43616e5d0f
      delete                    6:2702dd0c91e7
+     multipledelete1           12:e46a4836065c
+     multipledelete2           13:b4594d867745
+     singlenode1               14:43227190fef8
+     singlenode2               14:43227190fef8
+  $ hg prune -B multipledelete1 -B multipledelete2
+  bookmark 'multipledelete1' deleted
+  bookmark 'multipledelete2' deleted
+  2 changesets pruned
+  $ hg prune -B singlenode1 -B singlenode2
+  bookmark 'singlenode1' deleted
+  bookmark 'singlenode2' deleted
+  1 changesets pruned
+  $ hg prune -B unknownbookmark
+  abort: bookmark 'unknownbookmark' not found
+  [255]
+  $ hg prune -B unknownbookmark1 -B unknownbookmark2
+  abort: bookmark 'unknownbookmark1,unknownbookmark2' not found
+  [255]
+  $ hg prune -B delete -B unknownbookmark
+  abort: bookmark 'unknownbookmark' not found
+  [255]
   $ hg prune -B delete
   bookmark 'delete' deleted
   3 changesets pruned
@@ -317,23 +342,23 @@
   [255]
 
   $ hg debugobsstorestat
-  markers total:                      4
-      for known precursors:           4
+  markers total:                      7
+      for known precursors:           7
       with parents data:              [04] (re)
-  markers with no successors:         4
+  markers with no successors:         7
                 1 successors:         0
                 2 successors:         0
       more than 2 successors:         0
       available  keys:
-                 user:                4
-  disconnected clusters:              4
-          any known node:             4
+                 user:                7
+  disconnected clusters:              7
+          any known node:             7
           smallest length:            1
           longer length:              1
           median length:              1
           mean length:                1
-      using parents data:             4
-          any known node:             4
+      using parents data:             7
+          any known node:             7
           smallest length:            1
           longer length:              1
           median length:              1
@@ -347,14 +372,22 @@
   (leaving bookmark rg)
   $ hg bookmark r10
   $ hg log -G
-  o  11:cd0038e05e1b[rg] (stable/draft) add rg
+  o  15:cd0038e05e1b[rg] (stable/draft) add rg
   |
-  | @  10:ff43616e5d0f[B r10] (stable/draft) r10
+  | x  14:43227190fef8[] (extinct/draft) r14
+  | |
+  | | x  13:b4594d867745[] (extinct/draft) r13
+  | | |
+  | | | x  12:e46a4836065c[] (extinct/draft) r12
+  | | |/
+  | | o  11:bab5d5bf48bd[] (stable/draft) r11
+  | |/
+  +---@  10:ff43616e5d0f[B r10] (stable/draft) r10
+  | |
+  o |  8:d62d843c9a01[] (stable/draft) r8
+  | |
+  o |  7:e7d9710d9fc6[] (stable/draft) r7
   |/
-  o  8:d62d843c9a01[] (stable/draft) r8
-  |
-  o  7:e7d9710d9fc6[] (stable/draft) r7
-  |
   o    3:2b6d669947cd[] (stable/draft) r3
   |\
   | o  2:fa942426a6fd[] (stable/draft) r2
@@ -366,12 +399,22 @@
   $ hg prune 11
   1 changesets pruned
   $ hg log -G
-  @  10:ff43616e5d0f[B r10] (stable/draft) r10
+  o  15:cd0038e05e1b[rg] (stable/draft) add rg
   |
-  o  8:d62d843c9a01[rg] (stable/draft) r8
-  |
-  o  7:e7d9710d9fc6[] (stable/draft) r7
-  |
+  | x  14:43227190fef8[] (extinct/draft) r14
+  | |
+  | | x  13:b4594d867745[] (extinct/draft) r13
+  | | |
+  | | | x  12:e46a4836065c[] (extinct/draft) r12
+  | | |/
+  | | x  11:bab5d5bf48bd[] (extinct/draft) r11
+  | |/
+  +---@  10:ff43616e5d0f[B r10] (stable/draft) r10
+  | |
+  o |  8:d62d843c9a01[] (stable/draft) r8
+  | |
+  o |  7:e7d9710d9fc6[] (stable/draft) r7
+  |/
   o    3:2b6d669947cd[] (stable/draft) r3
   |\
   | o  2:fa942426a6fd[] (stable/draft) r2
@@ -387,5 +430,5 @@
      B                         8:d62d843c9a01
    * CELESTE                   8:d62d843c9a01
      r10                       8:d62d843c9a01
-     rg                        8:d62d843c9a01
+     rg                        15:cd0038e05e1b
 
--- a/tests/test-sharing.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-sharing.t	Sun Jan 03 16:47:57 2016 +0100
@@ -387,7 +387,7 @@
 
 Figure SG08: review and public changesets after Alice pushes.
   $ hg --hidden -R ../review shortlog -G -r 1::
-  o  7:a06ec1bf97bd  public  fix bug 15 (v2)
+  o  7:754cbfea9b13  public  fix bug 15 (v2)
   |
   o  6:540ba8f317e6  public  implement feature X (v3)
   |
@@ -402,7 +402,7 @@
   @  1:de6151c48e1c  public  fix bug 37
   |
   $ hg --hidden -R ../public shortlog -G -r 1::
-  o  3:a06ec1bf97bd  public  fix bug 15 (v2)
+  o  3:754cbfea9b13  public  fix bug 15 (v2)
   |
   o  2:540ba8f317e6  public  implement feature X (v3)
   |
@@ -431,7 +431,7 @@
   $ echo 'pretty good fix' >> file1
   $ hg commit -u bob -m 'fix bug 24 (v1)'
   $ hg shortlog -r .
-  4:2fe6c4bd32d0  draft  fix bug 24 (v1)
+  4:6d407e4bc7f4  draft  fix bug 24 (v1)
 
 Since Alice and Bob are now in cowboy mode, Alice pulls Bob's draft
 changeset and amends it herself. ::
@@ -454,13 +454,13 @@
   $ echo 'better fix (bob)' >> file1
   $ hg amend -u bob -m 'fix bug 24 (v2 by bob)'
   $ hg --hidden shortlog -G -r 3::
-  @  6:a360947f6faf  draft  fix bug 24 (v2 by bob)
+  @  6:059a6be5b63a  draft  fix bug 24 (v2 by bob)
   |
-  | x  5:3466c7f5a149  draft  temporary amend commit for 2fe6c4bd32d0
+  | x  5:a2493482aafe  draft  temporary amend commit for 6d407e4bc7f4
   | |
-  | x  4:2fe6c4bd32d0  draft  fix bug 24 (v1)
+  | x  4:6d407e4bc7f4  draft  fix bug 24 (v1)
   |/
-  o  3:a06ec1bf97bd  public  fix bug 15 (v2)
+  o  3:754cbfea9b13  public  fix bug 15 (v2)
   |
 
 Bob discovers the divergence.
@@ -477,19 +477,19 @@
 
 Figure SG09: multiple heads! divergence! oh my!
   $ hg --hidden shortlog -G -r 3::
-  o  7:e3f99ce9d9cd  draft  fix bug 24 (v2 by alice)
+  o  7:cdcc922d8d15  draft  fix bug 24 (v2 by alice)
   |
-  | @  6:a360947f6faf  draft  fix bug 24 (v2 by bob)
+  | @  6:059a6be5b63a  draft  fix bug 24 (v2 by bob)
   |/
-  | x  5:3466c7f5a149  draft  temporary amend commit for 2fe6c4bd32d0
+  | x  5:a2493482aafe  draft  temporary amend commit for 6d407e4bc7f4
   | |
-  | x  4:2fe6c4bd32d0  draft  fix bug 24 (v1)
+  | x  4:6d407e4bc7f4  draft  fix bug 24 (v1)
   |/
-  o  3:a06ec1bf97bd  public  fix bug 15 (v2)
+  o  3:754cbfea9b13  public  fix bug 15 (v2)
   |
-  $ hg --hidden shortlog -r 'successors(2fe6)'
-  6:a360947f6faf  draft  fix bug 24 (v2 by bob)
-  7:e3f99ce9d9cd  draft  fix bug 24 (v2 by alice)
+  $ hg --hidden shortlog -r 'successors(6d407)'
+  6:059a6be5b63a  draft  fix bug 24 (v2 by bob)
+  7:cdcc922d8d15  draft  fix bug 24 (v2 by alice)
 
 Use evolve to fix the divergence.
   $ HGMERGE=internal:other hg evolve --divergent
@@ -497,28 +497,28 @@
   with: [7] fix bug 24 (v2 by alice)
   base: [4] fix bug 24 (v1)
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
-  working directory is now at 5ad6037c046c
+  working directory is now at 70e807c6e67f
   $ hg log -q -r 'divergent()'
 
 Figure SG10: Bob's repository after fixing divergence.
   $ hg --hidden shortlog -G -r 3::
-  @  9:5ad6037c046c  draft  fix bug 24 (v2 by bob)
+  @  9:70e807c6e67f  draft  fix bug 24 (v2 by bob)
   |
-  | x  8:bcfc9a755ac3  draft  temporary amend commit for a360947f6faf
+  | x  8:d8d2e15ab73d  draft  temporary amend commit for 059a6be5b63a
   | |
-  +---x  7:e3f99ce9d9cd  draft  fix bug 24 (v2 by alice)
+  +---x  7:cdcc922d8d15  draft  fix bug 24 (v2 by alice)
   | |
-  | x  6:a360947f6faf  draft  fix bug 24 (v2 by bob)
+  | x  6:059a6be5b63a  draft  fix bug 24 (v2 by bob)
   |/
-  | x  5:3466c7f5a149  draft  temporary amend commit for 2fe6c4bd32d0
+  | x  5:a2493482aafe  draft  temporary amend commit for 6d407e4bc7f4
   | |
-  | x  4:2fe6c4bd32d0  draft  fix bug 24 (v1)
+  | x  4:6d407e4bc7f4  draft  fix bug 24 (v1)
   |/
-  o  3:a06ec1bf97bd  public  fix bug 15 (v2)
+  o  3:754cbfea9b13  public  fix bug 15 (v2)
   |
   $ hg --hidden shortlog -r 'precursors(9)'
-  6:a360947f6faf  draft  fix bug 24 (v2 by bob)
-  7:e3f99ce9d9cd  draft  fix bug 24 (v2 by alice)
+  6:059a6be5b63a  draft  fix bug 24 (v2 by bob)
+  7:cdcc922d8d15  draft  fix bug 24 (v2 by alice)
   $ cat file1
   Do stuff.
   pretty good fix
--- a/tests/test-simple4server-bundle2.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-simple4server-bundle2.t	Sun Jan 03 16:47:57 2016 +0100
@@ -140,7 +140,7 @@
 
   $ echo '[__temporary__]' >> server/.hg/hgrc
   $ echo 'advertiseobsolete=False' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
+  $ $RUNTESTDIR/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
 
@@ -150,7 +150,7 @@
   phases	
 
   $ echo 'advertiseobsolete=True' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
+  $ $RUNTESTDIR/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
 
--- a/tests/test-simple4server.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-simple4server.t	Sun Jan 03 16:47:57 2016 +0100
@@ -143,7 +143,7 @@
 
   $ echo '[__temporary__]' >> server/.hg/hgrc
   $ echo 'advertiseobsolete=False' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
+  $ $RUNTESTDIR/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
 
@@ -157,7 +157,7 @@
   [1]
 
   $ echo 'advertiseobsolete=True' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
+  $ $RUNTESTDIR/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
 
--- a/tests/test-stabilize-conflict.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-stabilize-conflict.t	Sun Jan 03 16:47:57 2016 +0100
@@ -169,7 +169,7 @@
   grafting 5:71c18f70c34f "babar count up to fifteen"
   $ hg resolve -l
   $ hg log -G
-  @  changeset:   8:1836b91c6c1d
+  @  changeset:   8:7f3d9db50b5d
   |  tag:         tip
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
@@ -232,7 +232,7 @@
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     babar count up to ten
   |
-  | o  changeset:   8:1836b91c6c1d
+  | o  changeset:   8:7f3d9db50b5d
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
   | |  summary:     babar count up to fifteen
--- a/tests/test-tutorial.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-tutorial.t	Sun Jan 03 16:47:57 2016 +0100
@@ -251,7 +251,7 @@
 My local work is now rebased on the remote one.
 
   $ hg log -G
-  @  41aff6a42b75 (draft): adding fruit
+  @  f49d36e87e8a (draft): adding fruit
   |
   o  dfd3a2d7691e (draft): adding condiment
   |
@@ -273,9 +273,9 @@
   > EOF
   $ hg ci -m 'transport'
   $ hg log -G
-  @  1125e39fbf21 (draft): transport
+  @  207804be803b (draft): transport
   |
-  o  41aff6a42b75 (draft): adding fruit
+  o  f49d36e87e8a (draft): adding fruit
   |
   o  dfd3a2d7691e (draft): adding condiment
   |
@@ -289,13 +289,13 @@
 
   $ hg prune . # "." is for working directory parent
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  working directory now at 41aff6a42b75
+  working directory now at f49d36e87e8a
   1 changesets pruned
 
 The silly changeset is gone.
 
   $ hg log -G
-  @  41aff6a42b75 (draft): adding fruit
+  @  f49d36e87e8a (draft): adding fruit
   |
   o  dfd3a2d7691e (draft): adding condiment
   |
@@ -323,11 +323,11 @@
   $ sed -i'' -e 's/Spam/Spam Spam Spam/g' shopping
   $ hg ci -m 'SPAM SPAM'
   $ hg log -G
-  @  fac207dec9f5 (draft): SPAM SPAM
+  @  0a0104b5de2f (draft): SPAM SPAM
   |
-  o  10b8aeaa8cc8 (draft): bathroom stuff
+  o  0dbf8779b56f (draft): bathroom stuff
   |
-  o  41aff6a42b75 (draft): adding fruit
+  o  f49d36e87e8a (draft): adding fruit
   |
   o  dfd3a2d7691e (draft): adding condiment
   |
@@ -346,18 +346,18 @@
 
 .. note: grab is an alias for `hg rebase --dest . --rev <target>; hg up <there>`
 
-  $ hg up 'p1(10b8aeaa8cc8)' # going on "bathroom stuff" parent
+  $ hg up 'p1(0dbf8779b56f)' # going on "bathroom stuff" parent
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg grab fac207dec9f5 # moving "SPAM SPAM" to the working directory parent
-  rebasing 10:fac207dec9f5 "SPAM SPAM" (tip)
+  $ hg grab 0a0104b5de2f # moving "SPAM SPAM" to the working directory parent
+  rebasing 10:0a0104b5de2f "SPAM SPAM" (tip)
   merging shopping
   ? files updated, 0 files merged, 0 files removed, 0 files unresolved (glob)
   $ hg log -G
-  @  a224f2a4fb9f (draft): SPAM SPAM
+  @  21ddb8bb7ace (draft): SPAM SPAM
   |
-  | o  10b8aeaa8cc8 (draft): bathroom stuff
+  | o  0dbf8779b56f (draft): bathroom stuff
   |/
-  o  41aff6a42b75 (draft): adding fruit
+  o  f49d36e87e8a (draft): adding fruit
   |
   o  dfd3a2d7691e (draft): adding condiment
   |
@@ -377,8 +377,8 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID a224f2a4fb9f9f828f608959912229d7b38b26de
-  # Parent  41aff6a42b7578ec7ec3cb2041633f1ca43cca96
+  # Node ID 21ddb8bb7acea9d1c46bf8a3965590c595aecb99
+  # Parent  f49d36e87e8ac78cba19b4aa94421cf13c18bcb5
   SPAM SPAM
   
   diff --git a/shopping b/shopping
@@ -394,7 +394,7 @@
 To make sure I do not push unready changeset by mistake I set the "bathroom
 stuff" changeset in the secret phase.
 
-  $ hg phase --force --secret 10b8aeaa8cc8
+  $ hg phase --force --secret 0dbf8779b56f
 
 we can now push our change:
 
@@ -409,17 +409,17 @@
 
 for simplicity sake we get the bathroom change in line again
 
-  $ hg grab 10b8aeaa8cc8
-  rebasing 9:10b8aeaa8cc8 "bathroom stuff"
+  $ hg grab 0dbf8779b56f
+  rebasing 9:0dbf8779b56f "bathroom stuff"
   merging shopping
   ? files updated, 0 files merged, 0 files removed, 0 files unresolved (glob)
   $ hg phase --draft .
   $ hg log -G
-  @  75954b8cd933 (draft): bathroom stuff
+  @  1820dfc355a0 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  21ddb8bb7ace (public): SPAM SPAM
   |
-  o  41aff6a42b75 (public): adding fruit
+  o  f49d36e87e8a (public): adding fruit
   |
   o  dfd3a2d7691e (public): adding condiment
   |
@@ -528,11 +528,11 @@
   1 new obsolescence markers
   (run 'hg update' to get a working copy)
   $ hg log -G
-  o  75954b8cd933 (public): bathroom stuff
+  o  1820dfc355a0 (public): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  21ddb8bb7ace (public): SPAM SPAM
   |
-  o  41aff6a42b75 (public): adding fruit
+  o  f49d36e87e8a (public): adding fruit
   |
   o  dfd3a2d7691e (public): adding condiment
   |
@@ -550,9 +550,9 @@
   $ hg rollback
   repository tip rolled back to revision 4 (undo pull)
   $ hg log -G
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  21ddb8bb7ace (public): SPAM SPAM
   |
-  o  41aff6a42b75 (public): adding fruit
+  o  f49d36e87e8a (public): adding fruit
   |
   o  dfd3a2d7691e (public): adding condiment
   |
@@ -585,11 +585,11 @@
   1 new obsolescence markers
   (run 'hg update' to get a working copy)
   $ hg log -G
-  o  75954b8cd933 (draft): bathroom stuff
+  o  1820dfc355a0 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  21ddb8bb7ace (public): SPAM SPAM
   |
-  o  41aff6a42b75 (public): adding fruit
+  o  f49d36e87e8a (public): adding fruit
   |
   o  dfd3a2d7691e (public): adding condiment
   |
@@ -603,7 +603,7 @@
 
 Remotely someone add a new changeset on top of the mutable "bathroom" on.
 
-  $ hg up 75954b8cd933 -q
+  $ hg up 1820dfc355a0 -q
   $ cat >> shopping << EOF
   > Giraffe
   > Rhino
@@ -615,15 +615,15 @@
 But at the same time, locally, this same "bathroom changeset" was updated.
 
   $ cd ../local
-  $ hg up 75954b8cd933 -q
+  $ hg up 1820dfc355a0 -q
   $ sed -i'' -e 's/... More bathroom stuff to come/Bath Robe/' shopping
   $ hg commit --amend
   $ hg log -G
-  @  a44c85f957d3 (draft): bathroom stuff
+  @  adef71cd3160 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  21ddb8bb7ace (public): SPAM SPAM
   |
-  o  41aff6a42b75 (public): adding fruit
+  o  f49d36e87e8a (public): adding fruit
   |
   o  dfd3a2d7691e (public): adding condiment
   |
@@ -650,15 +650,15 @@
 see both version showing up in the log.
 
   $ hg log -G
-  o  bf1b0d202029 (draft): animals
+  o  f9d416ff3ea5 (draft): animals
   |
-  | @  a44c85f957d3 (draft): bathroom stuff
+  | @  adef71cd3160 (draft): bathroom stuff
   | |
-  x |  75954b8cd933 (draft): bathroom stuff
+  x |  1820dfc355a0 (draft): bathroom stuff
   |/
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  21ddb8bb7ace (public): SPAM SPAM
   |
-  o  41aff6a42b75 (public): adding fruit
+  o  f49d36e87e8a (public): adding fruit
   |
   o  dfd3a2d7691e (public): adding condiment
   |
@@ -678,7 +678,7 @@
   $ hg push other
   pushing to $TESTTMP/other (glob)
   searching for changes
-  abort: push includes unstable changeset: bf1b0d202029!
+  abort: push includes unstable changeset: f9d416ff3ea5!
   (use 'hg evolve' to get a stable history or --force to ignore warnings)
   [255]
  
@@ -693,7 +693,7 @@
   $ hg evolve --dry-run
   move:[15] animals
   atop:[14] bathroom stuff
-  hg rebase -r bf1b0d202029 -d a44c85f957d3
+  hg rebase -r f9d416ff3ea5 -d adef71cd3160
 
 Let's do it
 
@@ -701,18 +701,18 @@
   move:[15] animals
   atop:[14] bathroom stuff
   merging shopping
-  working directory is now at ee942144f952
+  working directory is now at 0325a6c93ec2
 
 The old version of bathroom is hidden again.
 
   $ hg log -G
-  @  ee942144f952 (draft): animals
+  @  0325a6c93ec2 (draft): animals
   |
-  o  a44c85f957d3 (draft): bathroom stuff
+  o  adef71cd3160 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  21ddb8bb7ace (public): SPAM SPAM
   |
-  o  41aff6a42b75 (public): adding fruit
+  o  f49d36e87e8a (public): adding fruit
   |
   o  dfd3a2d7691e (public): adding condiment
   |
@@ -746,13 +746,13 @@
 now let's see where we are, and update to the successor
 
   $ hg parents
-  bf1b0d202029 (draft): animals
+  f9d416ff3ea5 (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
-  working directory is now at ee942144f952
+  working directory is now at 0325a6c93ec2
 
 Relocating unstable change after prune
 ----------------------------------------------
@@ -774,15 +774,15 @@
   added 1 changesets with 1 changes to 1 files
   (run 'hg update' to get a working copy)
   $ hg log -G
-  o  99f039c5ec9e (draft): SPAM SPAM SPAM
+  o  e8373c1af7dc (draft): SPAM SPAM SPAM
   |
-  @  ee942144f952 (draft): animals
+  @  0325a6c93ec2 (draft): animals
   |
-  o  a44c85f957d3 (draft): bathroom stuff
+  o  adef71cd3160 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  21ddb8bb7ace (public): SPAM SPAM
   |
-  o  41aff6a42b75 (public): adding fruit
+  o  f49d36e87e8a (public): adding fruit
   |
   o  dfd3a2d7691e (public): adding condiment
   |
@@ -793,9 +793,9 @@
 
 In the mean time I noticed you can't buy animals in a super market and I prune the animal changeset:
 
-  $ hg prune ee942144f952
+  $ hg prune 0325a6c93ec2
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  working directory now at a44c85f957d3
+  working directory now at adef71cd3160
   1 changesets pruned
   1 new unstable changesets
 
@@ -804,15 +804,15 @@
 is neither dead or obsolete.  My repository is in an unstable state again.
 
   $ hg log -G
-  o  99f039c5ec9e (draft): SPAM SPAM SPAM
+  o  e8373c1af7dc (draft): SPAM SPAM SPAM
   |
-  x  ee942144f952 (draft): animals
+  x  0325a6c93ec2 (draft): animals
   |
-  @  a44c85f957d3 (draft): bathroom stuff
+  @  adef71cd3160 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  21ddb8bb7ace (public): SPAM SPAM
   |
-  o  41aff6a42b75 (public): adding fruit
+  o  f49d36e87e8a (public): adding fruit
   |
   o  dfd3a2d7691e (public): adding condiment
   |
@@ -822,22 +822,22 @@
   
 
   $ hg log -r 'unstable()'
-  99f039c5ec9e (draft): SPAM SPAM SPAM
+  e8373c1af7dc (draft): SPAM SPAM SPAM
 
   $ hg evolve
   move:[17] SPAM SPAM SPAM
   atop:[14] bathroom stuff
   merging shopping
-  working directory is now at 40aa40daeefb
+  working directory is now at 5b9223df71d2
 
   $ hg log -G
-  @  40aa40daeefb (draft): SPAM SPAM SPAM
+  @  5b9223df71d2 (draft): SPAM SPAM SPAM
   |
-  o  a44c85f957d3 (draft): bathroom stuff
+  o  adef71cd3160 (draft): bathroom stuff
   |
-  o  a224f2a4fb9f (public): SPAM SPAM
+  o  21ddb8bb7ace (public): SPAM SPAM
   |
-  o  41aff6a42b75 (public): adding fruit
+  o  f49d36e87e8a (public): adding fruit
   |
   o  dfd3a2d7691e (public): adding condiment
   |
--- a/tests/test-unstable.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-unstable.t	Sun Jan 03 16:47:57 2016 +0100
@@ -103,17 +103,13 @@
   $ hg evo --all --any --unstable
   move:[3] merge
   atop:[4] aprime
-  abort: no support for evolving merge changesets yet
-  (Redo the merge and use `hg prune <old> --succ <new>` to obsolete the old one)
-  [255]
+  working directory is now at 0bf3f3a59c8c
   $ hg log -G
-  @  4:47127ea62e5f@default(draft) aprime
-  |
-  | o    3:6b4280e33286@default(draft) merge
-  | |\
-  +---o  2:474da87dd33b@default(draft) add _c
+  @    5:0bf3f3a59c8c@default(draft) merge
+  |\
+  | o  4:47127ea62e5f@default(draft) aprime
   | |
-  | x  1:b3264cec9506@default(draft) add _a
+  o |  2:474da87dd33b@default(draft) add _c
   |/
   o  0:b4952fcf48cf@default(draft) add base
   
@@ -158,9 +154,7 @@
   
 
   $ hg evo --all --any --unstable
-  move:[3] merge
-  atop:[5] cprime
-  abort: no support for evolving merge changesets yet
+  abort: no support for evolving merge changesets with two obsolete parents yet
   (Redo the merge and use `hg prune <old> --succ <new>` to obsolete the old one)
   [255]
   $ hg log -G
--- a/tests/test-wireproto-bundle1.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-wireproto-bundle1.t	Sun Jan 03 16:47:57 2016 +0100
@@ -3,7 +3,7 @@
   > [defaults]
   > amend=-d "0 0"
   > [ui]
-  > ssh=python "$TESTDIR/dummyssh"
+  > ssh=python "$RUNTESTDIR/dummyssh"
   > [phases]
   > publish = False
   > [extensions]
--- a/tests/test-wireproto.t	Sun Jan 03 15:51:36 2016 +0100
+++ b/tests/test-wireproto.t	Sun Jan 03 16:47:57 2016 +0100
@@ -6,7 +6,7 @@
   > obsmarkers-exchange-debug=true
   > bundle2-exp=true
   > [ui]
-  > ssh=python "$TESTDIR/dummyssh"
+  > ssh=python "$RUNTESTDIR/dummyssh"
   > [phases]
   > publish = False
   > [extensions]
@@ -144,7 +144,7 @@
   adding changesets
   adding manifests
   adding file changes
-  added 1 changesets with 0 changes to 3 files (+1 heads)
+  added 1 changesets with 0 changes to 1 files (+1 heads)
   obsmarker-exchange: 208 bytes received
   1 new obsolescence markers
   (run 'hg heads' to see heads, 'hg merge' to merge)