changeset 1602:f932853783a5 stable

merge with stable (Mercurial 3.7 is out)
author Pierre-Yves David <pierre-yves.david@fb.com>
date Thu, 04 Feb 2016 11:17:09 +0000
parents 190e4e526c66 (current diff) 2a08ef812b84 (diff)
children 6482497d859b
files tests/dummyssh tests/killdaemons.py tests/run-tests.py
diffstat 26 files changed, 1025 insertions(+), 1663 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Thu Jan 14 14:02:05 2016 -0800
+++ b/Makefile	Thu Feb 04 11:17:09 2016 +0000
@@ -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	Thu Jan 14 14:02:05 2016 -0800
+++ b/README	Thu Feb 04 11:17:09 2016 +0000
@@ -58,7 +58,14 @@
 
 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.
+- evolve: prevent added file to be marked as unknown if evolve fails (issue4966)
+- evolve: stop relying on graftstate file for save evolve state
+          (for `hg evolve --continue`)
 
 5.2.2 --
 
--- a/debian/rules	Thu Jan 14 14:02:05 2016 -0800
+++ b/debian/rules	Thu Feb 04 11:17:09 2016 +0000
@@ -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/debian/test-blacklist	Thu Jan 14 14:02:05 2016 -0800
+++ b/debian/test-blacklist	Thu Feb 04 11:17:09 2016 +0000
@@ -1,3 +1,4 @@
 test-drop.t
+test-inhibit.t
 test-simple4server.t
 tests/test-simple4server-bundle2.t
--- a/hgext/directaccess.py	Thu Jan 14 14:02:05 2016 -0800
+++ b/hgext/directaccess.py	Thu Feb 04 11:17:09 2016 +0000
@@ -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	Thu Jan 14 14:02:05 2016 -0800
+++ b/hgext/drophack.py	Thu Feb 04 11:17:09 2016 +0000
@@ -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	Thu Jan 14 14:02:05 2016 -0800
+++ b/hgext/evolve.py	Thu Feb 04 11:17:09 2016 +0000
@@ -67,6 +67,7 @@
 import collections
 import socket
 import errno
+import struct
 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
 
 import mercurial
@@ -86,7 +87,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
@@ -116,6 +117,7 @@
 command = cmdutil.command(cmdtable)
 
 _pack = struct.pack
+_unpack = struct.unpack
 
 if gboptsmap is not None:
     memfilectx = context.memfilectx
@@ -124,7 +126,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 +418,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 +750,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
@@ -764,12 +767,17 @@
         else:
             ui.note(s)
 
-    nbunstable = len(getrevs(repo, 'unstable'))
-    nbbumped = len(getrevs(repo, 'bumped'))
-    nbdivergent = len(getrevs(repo, 'divergent'))
-    write('unstable: %i changesets\n', nbunstable)
-    write('bumped: %i changesets\n', nbbumped)
-    write('divergent: %i changesets\n', nbdivergent)
+    # util.versiontuple was introduced in 3.6.2
+    if not util.safehasattr(util, 'versiontuple'):
+        nbunstable = len(getrevs(repo, 'unstable'))
+        nbbumped = len(getrevs(repo, 'bumped'))
+        nbdivergent = len(getrevs(repo, 'divergent'))
+        write('unstable: %i changesets\n', nbunstable)
+        write('bumped: %i changesets\n', nbbumped)
+        write('divergent: %i changesets\n', nbdivergent)
+    else:
+        # In 3.6.2, summary in core gained this feature, no need to display it
+        pass
 
 @eh.extsetup
 def obssummarysetup(ui):
@@ -789,13 +797,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 +898,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()
@@ -937,37 +948,16 @@
             repo.ui.note(_('The stale commit message reference to %s could '
                            'not be updated\n') % sha1)
 
-    tr = repo.transaction('relocate')
+    tr = repo.currenttransaction()
+    assert tr is not None
     try:
         try:
-            if repo['.'].rev() != dest.rev():
-                merge.update(repo, dest, False, True, False)
-            if bmactive(repo):
-                repo.ui.status(_("(leaving bookmark %s)\n") % bmactive(repo))
-            bmdeactivate(repo)
-            if keepbranch:
-                repo.dirstate.setbranch(orig.branch())
-            r = merge.graft(repo, orig, orig.p1(), ['local', 'graft'])
+            r = _evolvemerge(repo, orig, dest, pctx, keepbranch)
             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)
@@ -977,25 +967,12 @@
             class LocalMergeFailure(MergeFailure, exc.__class__):
                 pass
             exc.__class__ = LocalMergeFailure
+            tr.close() # to keep changes in this transaction (e.g. dirstate)
             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)
-        tr.close()
+        _finalizerelocate(repo, orig, dest, nodenew, tr)
     finally:
-        tr.release()
+        pass # TODO: remove this redundant try/finally block
     return nodenew
 
 def _bookmarksupdater(repo, oldid, tr):
@@ -1016,14 +993,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 +1133,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 +1163,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 +1172,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 +1205,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 +1347,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 +1369,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 +1422,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 +1456,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 +1515,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 +1540,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 +1607,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 +1645,34 @@
 
     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"')
-        graftcmd = commands.table['graft'][0]
-        return graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
+            raise error.Abort('cannot specify both "--all" and "--continue"')
+        state = _evolvestateread(repo)
+        if state is None:
+            raise error.Abort('no evolve to continue')
+        orig = repo[state['current']]
+        # XXX This is a terrible terrible hack, please get rid of it.
+        repo.opener.write('graftstate', orig.hex() + '\n')
+        try:
+            graftcmd = commands.table['graft'][0]
+            ret = graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
+            _evolvestatedelete(repo)
+            return ret
+        finally:
+            util.unlinkpath(repo.join('graftstate'), ignoremissing=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,9 +1807,9 @@
         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')
+            _evolvestatewrite(repo, {'current': orig.node()})
             repo.ui.write_err(_('evolve failed!\n'))
             repo.ui.write_err(
                 _('fix conflict and run "hg evolve --continue"'
@@ -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,19 +1841,20 @@
         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
     tmpctx = bumped
     # Basic check for common parent. Far too complicated and fragile
-    tr = repo.transaction('bumped-stabilize')
+    tr = repo.currenttransaction()
+    assert tr is not None
     bmupdate = _bookmarksupdater(repo, bumped.node(), tr)
     try:
         if not list(repo.set('parents(%d) and parents(%d)', bumped, prec)):
@@ -1911,10 +1920,9 @@
             obsolete.createmarkers(repo, [(tmpctx, (repo[newid],))],
                                    flag=obsolete.bumpedfix)
         bmupdate(newid)
-        tr.close()
         repo.ui.status(_('committed as %s\n') % node.short(newid))
     finally:
-        tr.release()
+        pass # TODO: remove this redundant try/finally block
     # reroute the working copy parent to the new changeset
     repo.dirstate.beginparentchange()
     repo.dirstate.setparents(newid, node.nullid)
@@ -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,11 @@
 /!\ * hg kill -n Y W Z
 """)
     if progresscb: progresscb()
-    tr = repo.transaction('stabilize-divergent')
+    emtpycommitallowed = repo.ui.backupconfig('ui', 'allowemptycommit')
+    tr = repo.currenttransaction()
+    assert tr is not None
     try:
+        repo.ui.setconfig('ui', 'allowemptycommit', True)
         repo.dirstate.beginparentchange()
         repo.dirstate.setparents(divergent.node(), node.nullid)
         repo.dirstate.endparentchange()
@@ -2023,9 +2047,8 @@
             new = repo['.']
         obsolete.createmarkers(repo, [(other, (new,))])
         phases.retractboundary(repo, tr, other.phase(), [new.node()])
-        tr.close()
     finally:
-        tr.release()
+        repo.ui.restoreconfig(emtpycommitallowed)
 
 def divergentdata(ctx):
     """return base, other part of a conflict
@@ -2041,7 +2064,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 +2075,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 +2086,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 +2101,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 +2133,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 +2147,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 +2162,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 +2214,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 +2278,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 +2312,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 +2320,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 +2343,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 +2359,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 +2399,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 +2622,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 +2638,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 +2655,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 +2674,7 @@
 
 @eh.wrapcommand('commit')
 def commitwrapper(orig, ui, repo, *arg, **kwargs):
+    tr = None
     if kwargs.get('amend', False):
         wlock = lock = None
     else:
@@ -2656,17 +2697,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.
@@ -2676,16 +2723,13 @@
     tr = wlock = lock = None
     newcommits = []
 
-    revopt = opts.get('rev')
-    if revopt:
-        revs = scmutil.revrange(repo, revopt)
-        if len(revs) != 1:
-            raise util.Abort(_("you can only specify one revision to split"))
-        else:
-            rev = list(revs)[0]
-    else:
-        rev = '.'
-
+    revarg = (list(revs) + opts.get('rev')) or ['.']
+    if len(revarg) != 1:
+        msg = _("more than one revset is given")
+        hnt = _("use either `hg split <rs>` or `hg split --rev <rs>`, not both")
+        raise error.Abort(msg, hint=hnt)
+
+    rev = scmutil.revsingle(repo, revarg[0])
     try:
         wlock = repo.wlock()
         lock = repo.lock()
@@ -2698,10 +2742,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 +2758,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 +2775,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())
@@ -2767,15 +2815,20 @@
 @command('^touch',
     [('r', 'rev', [], 'revision to update'),
      ('D', 'duplicate', False,
-      'do not mark the new revision as successor of the old one')],
+      'do not mark the new revision as successor of the old one'),
+     ('A', 'allowdivergence', False,
+      'mark the new revision as successor of the old one potentially creating '
+      'divergence')],
     # allow to choose the seed ?
     _('[-r] revs'))
 def touch(ui, repo, *revs, **opts):
-    """create successors that are identical to their predecessors except for the changeset ID
+    """create successors that are identical to their predecessors except
+    for the changeset ID
 
     This is used to "resurrect" changesets
     """
     duplicate = opts['duplicate']
+    allowdivergence = opts['allowdivergence']
     revs = list(revs)
     revs.extend(opts['rev'])
     if not revs:
@@ -2785,7 +2838,8 @@
         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")
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
     wlock = lock = tr = None
     try:
         wlock = repo.wlock()
@@ -2802,11 +2856,37 @@
             p2 = ctx.p2().node()
             p1 = newmapping.get(p1, p1)
             p2 = newmapping.get(p2, p2)
+
+            if not (duplicate or allowdivergence):
+                # The user hasn't yet decided what to do with the revived
+                # cset, let's ask
+                sset = obsolete.successorssets(repo, ctx.node())
+                nodivergencerisk = len(sset) == 0 or (
+                                    len(sset) == 1 and
+                                    len(sset[0]) == 1 and
+                                    repo[sset[0][0]].rev() == ctx.rev()
+                                   )
+                if nodivergencerisk:
+                    duplicate = False
+                else:
+                    displayer.show(ctx)
+                    index = ui.promptchoice(
+                        _("reviving this changeset will create divergence"
+                        " unless you make a duplicate.\n(a)llow divergence or"
+                        " (d)uplicate the changeset? $$ &Allowdivergence $$ "
+                        "&Duplicate"), 0)
+                    choice = ['allowdivergence', 'duplicate'][index]
+                    if choice == 'allowdivergence':
+                        duplicate = False
+                    else:
+                        duplicate = True
+
             new, unusedvariable = rewrite(repo, ctx, [], ctx,
                                           [p1, p2],
                                           commitopts={'extra': extra})
             # store touched version to help potential children
             newmapping[ctx.node()] = new
+
             if not duplicate:
                 obsolete.createmarkers(repo, [(ctx, (repo[new],))])
             phases.retractboundary(repo, tr, ctx.phase(), [new])
@@ -2863,7 +2943,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 +2952,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 +2963,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 +2998,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 +3107,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 +3140,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 +3169,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 +3207,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 +3225,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 +3246,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 +3310,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 +3356,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 +3418,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 +3532,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 +3567,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 +3597,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 +3611,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 +3634,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 +3646,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 +3706,144 @@
         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)
+
+evolvestateversion = 0
+
+@eh.uisetup
+def setupevolveunfinished(ui):
+    data = ('evolvestate', True, False, _('evolve in progress'),
+           _("use 'hg evolve --continue' or 'hg update' to abort"))
+    cmdutil.unfinishedstates.append(data)
+
+@eh.wrapfunction(hg, 'clean')
+def clean(orig, repo, *args, **kwargs):
+    ret = orig(repo, *args, **kwargs)
+    util.unlinkpath(repo.join('evolvestate'), ignoremissing=True)
+    return ret
+
+def _evolvestatewrite(repo, state):
+    # [version]
+    # [type][length][content]
+    #
+    # `version` is a 4 bytes integer (handled at higher level)
+    # `type` is a single character, `length` is a 4 byte integer, and
+    # `content` is an arbitrary byte sequence of length `length`.
+    f = repo.vfs('evolvestate', 'w')
+    try:
+        f.write(_pack('>I', evolvestateversion))
+        current = state['current']
+        key = 'C' # as in 'current'
+        format = '>sI%is' % len(current)
+        f.write(_pack(format, key, len(current), current))
+    finally:
+        f.close()
+
+def _evolvestateread(repo):
+    try:
+        f = repo.vfs('evolvestate')
+    except IOError, err:
+        if err.errno != errno.ENOENT:
+            raise
+        return None
+    try:
+        versionblob = f.read(4)
+        if len(versionblob) < 4:
+            repo.ui.debug('ignoring corrupted evolvestte (file contains %i bits)'
+                          % len(versionblob))
+            return None
+        version = _unpack('>I', versionblob)[0]
+        if version != evolvestateversion:
+            raise error.Abort(_('unknown evolvestate version %i')
+                                % version, hint=_('upgrade your evolve'))
+        records = []
+        data = f.read()
+        off = 0
+        end = len(data)
+        while off < end:
+            rtype = data[off]
+            off += 1
+            length = _unpack('>I', data[off:(off + 4)])[0]
+            off += 4
+            record = data[off:(off + length)]
+            off += length
+            if rtype == 't':
+                rtype, record = record[0], record[1:]
+            records.append((rtype, record))
+        state = {}
+        for rtype, rdata in records:
+            if rtype == 'C':
+                state['current'] = rdata
+            elif rtype.lower():
+                repo.ui.debug('ignore evolve state record type %s' % rtype)
+            else:
+                raise error.Abort(_('unknown evolvestate field type %r')
+                                  % rtype, hint=_('upgrade your evolve'))
+        return state
+    finally:
+        f.close()
+
+def _evolvestatedelete(repo):
+    util.unlinkpath(repo.join('evolvestate'), ignoremissing=True)
+
+def _evolvemerge(repo, orig, dest, pctx, keepbranch):
+    """Used by the evolve function to merge dest on top of pctx.
+    return the same tuple as merge.graft"""
+    if repo['.'].rev() != dest.rev():
+       merge.update(repo, dest, False, True, False)
+    if bmactive(repo):
+       repo.ui.status(_("(leaving bookmark %s)\n") % bmactive(repo))
+    bmdeactivate(repo)
+    if keepbranch:
+       repo.dirstate.setbranch(orig.branch())
+
+    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'])
+    return r
--- a/hgext/inhibit.py	Thu Jan 14 14:02:05 2016 -0800
+++ b/hgext/inhibit.py	Thu Feb 04 11:17:09 2016 +0000
@@ -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,
         'keep': None,
         'biject': False,
     }
@@ -117,7 +122,7 @@
     Public changesets are already immune to obsolescence"""
     getrev = repo.changelog.nodemap.get
     getphase = repo._phasecache.phase
-    return (n for n in repo._obsinhibit
+    return (n for n in nodes
             if getrev(n) is not None and getphase(repo, getrev(n)))
 
 def _inhibitmarkers(repo, nodes):
@@ -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 _filterobsoleterevswrap(orig, repo, rebasesetrevs, *args, **kwargs):
+    repo._notinhibited = rebasesetrevs
+    try:
+        repo.invalidatevolatilesets()
+        r = orig(repo, rebasesetrevs, *args, **kwargs)
+    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):
@@ -183,14 +207,38 @@
         # obsolete commit to inhibit them
         visibleobsolete = repo.revs('obsolete() - hidden()')
         ignoreset = set(getattr(repo, '_rebaseset', []))
+        ignoreset |= set(getattr(repo, '_obsoletenotrebased', []))
         visibleobsolete = list(r for r in visibleobsolete if r not in ignoreset)
         if visibleobsolete:
             _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
     transaction = orig(repo, desc, *args, **kwargs)
     if desc != 'strip' and _inhibitenabled(repo):
-        transaction.addpostclose('inhibitposttransaction', inhibitposttransaction)
+        transaction.addpostclose('inhibitposttransaction',
+                                 inhibitposttransaction)
     return transaction
 
+
+# We wrap these two functions to address the following scenario:
+# - Assuming that we have markers between commits in the rebase set and
+#   destination and that these markers are inhibited
+# - At the end of the rebase the nodes are still visible because rebase operate
+#   without inhibition and skip these nodes
+# We keep track in repo._obsoletenotrebased of the obsolete commits skipped by
+# the rebase and lift the inhibition in the end of the rebase.
+
+def _computeobsoletenotrebased(orig, repo, *args, **kwargs):
+    r = orig(repo, *args, **kwargs)
+    repo._obsoletenotrebased = r.keys()
+    return r
+
+def _clearrebased(orig, ui, repo, *args, **kwargs):
+    r = orig(ui, repo, *args, **kwargs)
+    tonode = repo.changelog.node
+    if util.safehasattr(repo, '_obsoletenotrebased'):
+        _deinhibitmarkers(repo, [tonode(k) for k in repo._obsoletenotrebased])
+    return r
+
+
 def extsetup(ui):
     # lets wrap the computation of the obsolete set
     # We apply inhibition there
@@ -202,8 +250,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 +278,21 @@
     # 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:
+            if util.safehasattr(rebase, '_filterobsoleterevs'):
+                extensions.wrapfunction(rebase,
+                                        '_filterobsoleterevs',
+                                        _filterobsoleterevswrap)
+            extensions.wrapfunction(rebase, 'clearrebased', _clearrebased)
+            if util.safehasattr(rebase, '_computeobsoletenotrebased'):
+                extensions.wrapfunction(rebase,
+                                        '_computeobsoletenotrebased',
+                                        _computeobsoletenotrebased)
+
+    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	Thu Jan 14 14:02:05 2016 -0800
+++ b/hgext/obsolete.py	Thu Feb 04 11:17:09 2016 +0000
@@ -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	Thu Jan 14 14:02:05 2016 -0800
+++ b/hgext/pushexperiment.py	Thu Feb 04 11:17:09 2016 +0000
@@ -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	Thu Jan 14 14:02:05 2016 -0800
+++ b/hgext/simple4server.py	Thu Feb 04 11:17:09 2016 +0000
@@ -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	Thu Jan 14 14:02:05 2016 -0800
+++ /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	Thu Jan 14 14:02:05 2016 -0800
+++ /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	Thu Jan 14 14:02:05 2016 -0800
+++ /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	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-divergent.t	Thu Feb 04 11:17:09 2016 +0000
@@ -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-evolve.t	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-evolve.t	Thu Feb 04 11:17:09 2016 +0000
@@ -601,6 +601,7 @@
   $ echo 3 > 1
   $ hg resolve -m 1
   (no more unresolved files)
+  continue: hg graft --continue
   $ hg graft --continue -O
   grafting 7:a5bfd90a2f29 "conflict" (tip)
   $ glog --hidden
@@ -1397,3 +1398,54 @@
   working directory is now at 43c3f5ef149f
 
 
+Check that dirstate changes are kept at failure for conflicts (issue4966)
+----------------------------------------
+
+  $ echo "will be amended" > newfile
+  $ hg commit -m "will be amended"
+  $ hg parents
+  37	: will be amended - test
+
+  $ echo "will be evolved safely" >> a
+  $ hg commit -m "will be evolved safely"
+
+  $ echo "will cause conflict at evolve" > newfile
+  $ echo "newly added" > newlyadded
+  $ hg add newlyadded
+  $ hg commit -m "will cause conflict at evolve"
+
+  $ hg update -q 37
+  $ echo "amended" > newfile
+  $ hg amend -m "amended"
+  2 new unstable changesets
+
+  $ hg evolve --rev "37::"
+  move:[38] will be evolved safely
+  atop:[41] amended
+  move:[39] will cause conflict at evolve
+  atop:[42] will be evolved safely
+  merging newfile
+  warning: conflicts while merging newfile! (edit, then use 'hg resolve --mark')
+  evolve failed!
+  fix conflict and run "hg evolve --continue" or use "hg update -C" to abort
+  abort: unresolved merge conflicts (see hg help resolve)
+  [255]
+
+  $ glog -r "36::" --hidden
+  @  42:c904da5245b0@default(draft) will be evolved safely
+  |
+  o  41:34ae045ec400@default(draft) amended
+  |
+  | x  40:e88bee38ffc2@default(draft) temporary amend commit for 36030b147271
+  | |
+  | | o  39:02e943732647@default(draft) will cause conflict at evolve
+  | | |
+  | | x  38:f8e30e9317aa@default(draft) will be evolved safely
+  | |/
+  | x  37:36030b147271@default(draft) will be amended
+  |/
+  o  36:43c3f5ef149f@default(draft) add uu
+  |
+
+  $ hg status newlyadded
+  A newlyadded
--- a/tests/test-exchange-A3.t	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-exchange-A3.t	Thu Feb 04 11:17:09 2016 +0000
@@ -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	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-inhibit.t	Thu Feb 04 11:17:09 2016 +0000
@@ -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 -q 2d66e189f5b5 # To inhibit it as the rest of test depends on it
+  $ hg up -q 21
+
 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,9 +795,86 @@
   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
 
+Create a stack (obsolete with successor in dest) -> (not obsolete) and rebase
+it. We expect to not see the stack at the end of the rebase.
+  $ hg log -G  -r "25::"
+  @  25:71eb4f100663 add pk
+  |
+  $ hg up -C 22
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit Dk
+  $ hg prune 22 -s 25
+  1 changesets pruned
+  $ hg rebase -s 22 -d 25 --config experimental.rebaseskipobsolete=True
+  note: not rebasing 22:46cb6daad392 "add cN", already in destination as 25:71eb4f100663 "add pk"
+  rebasing 26:7ad60e760c7b "add Dk" (tip)
+  $ hg log -G  -r "25::"
+  @  27:1192fa9fbc68 add Dk
+  |
+  o  25:71eb4f100663 add pk
+  |
+
+Create a stack (obsolete with succ in dest) -> (not obsolete) -> (not obsolete).
+Rebase the first two revs of the stack onto dest, we expect to see one new
+revision on the destination and everything visible.
+  $ hg up 25
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit Dl
+  created new head
+  $ mkcommit Dp
+  $ mkcommit Do
+  $ hg log -G -r "25::"
+  @  30:b517facce1ef add Do
+  |
+  o  29:c5a47ab27c2e add Dp
+  |
+  o  28:8c1c2edbaf1b add Dl
+  |
+  | o  27:1192fa9fbc68 add Dk
+  |/
+  o  25:71eb4f100663 add pk
+  |
+  $ hg prune 28 -s 27
+  1 changesets pruned
+  $ hg up 25
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg rebase -r "28 + 29" --keep -d 27 --config experimental.rebaseskipobsolete=True
+  note: not rebasing 28:8c1c2edbaf1b "add Dl", already in destination as 27:1192fa9fbc68 "add Dk"
+  rebasing 29:c5a47ab27c2e "add Dp"
+  $ hg log -G  -r "25::"
+  o  31:7d8affb1f604 add Dp
+  |
+  | o  30:b517facce1ef add Do
+  | |
+  | o  29:c5a47ab27c2e add Dp
+  | |
+  | o  28:8c1c2edbaf1b add Dl
+  | |
+  o |  27:1192fa9fbc68 add Dk
+  |/
+  @  25:71eb4f100663 add pk
+  |
+
+Rebase the same stack in full on the destination, we expect it to disappear
+and only see the top revision added to destination. We don\'t expect 29 to be
+skipped as we used --keep before.
+  $ hg rebase -s 28 -d 27 --config experimental.rebaseskipobsolete=True
+  note: not rebasing 28:8c1c2edbaf1b "add Dl", already in destination as 27:1192fa9fbc68 "add Dk"
+  rebasing 29:c5a47ab27c2e "add Dp"
+  rebasing 30:b517facce1ef "add Do"
+  $ hg log -G  -r "25::"
+  o  32:1d43fff9e26f add Do
+  |
+  o  31:7d8affb1f604 add Dp
+  |
+  o  27:1192fa9fbc68 add Dk
+  |
+  @  25:71eb4f100663 add pk
+  |
+
 Pulling from a inhibit repo to a non-inhibit repo should work
 
   $ cd ..
@@ -787,3 +892,16 @@
   searching for changes
   no changes found
   adding remote bookmark foo
+
+Test that bookmark -D can take multiple branch names
+  $ cd ../inhibit
+  $ hg bookmark book2 book1 book3
+  $ touch foo && hg add foo && hg ci -m "add foo"
+  created new head
+  $ hg up book1
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (activating bookmark book1)
+  $ hg bookmark -D book2 book3
+  bookmark 'book2' deleted
+  bookmark 'book3' deleted
+  1 changesets pruned
--- a/tests/test-prune.t	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-prune.t	Thu Feb 04 11:17:09 2016 +0000
@@ -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-simple4server-bundle2.t	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-simple4server-bundle2.t	Thu Feb 04 11:17:09 2016 +0000
@@ -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	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-simple4server.t	Thu Feb 04 11:17:09 2016 +0000
@@ -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-split.t	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-split.t	Thu Feb 04 11:17:09 2016 +0000
@@ -320,9 +320,14 @@
      bookA                     19:a2b5c9d9b362
    * bookB                     19:a2b5c9d9b362
  
-Cannot specify multiple revisions with -r
+Lastest revision is selected if multiple are given to -r
   $ hg split -r "desc(_a)::"
-  abort: you can only specify one revision to split
+  (leaving bookmark bookB)
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  adding _d
+  diff --git a/_d b/_d
+  new file mode 100644
+  examine changes to '_d'? [Ynesfdaq?] abort: response expected
   [255]
 
 Cannot split a commit that is not a head if instability is not allowed
@@ -335,4 +340,41 @@
   abort: cannot split commit: ced8fbcce3a7 not a head
   [255]
 
+Changing evolution level to createmarkers
+  $ echo "[experimental]" >> $HGRCPATH
+  $ echo "evolution=createmarkers" >> $HGRCPATH
 
+Running split without any revision operates on the parent of the working copy
+  $ hg split << EOF
+  > q
+  > EOF
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  adding _d
+  diff --git a/_d b/_d
+  new file mode 100644
+  examine changes to '_d'? [Ynesfdaq?] q
+  
+  abort: user quit
+  [255]
+
+Running split with tip revision, specified as unnamed argument
+  $ hg split . << EOF
+  > q
+  > EOF
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  adding _d
+  diff --git a/_d b/_d
+  new file mode 100644
+  examine changes to '_d'? [Ynesfdaq?] q
+  
+  abort: user quit
+  [255]
+
+Running split with both unnamed and named revision arguments shows an error msg
+  $ hg split . --rev .^ << EOF
+  > q
+  > EOF
+  abort: more than one revset is given
+  (use either `hg split <rs>` or `hg split --rev <rs>`, not both)
+  [255]
+
--- a/tests/test-touch.t	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-touch.t	Thu Feb 04 11:17:09 2016 +0000
@@ -41,6 +41,9 @@
   @  1:[0-9a-f]{12} a (re)
   
   $ hg touch .
+  [1] a
+  reviving this changeset will create divergence unless you make a duplicate.
+  (a)llow divergence or (d)uplicate the changeset?  a
   2 new divergent changesets
   $ hg log -G
   @  4:[0-9a-f]{12} a (re)
@@ -110,3 +113,15 @@
   A gna2
     gna1
   R gna1
+
+check that the --duplicate option does not create divergence
+
+  $ hg touch --duplicate 11 --hidden
+  1 new unstable changesets
+
+check that reviving a changeset with no successor does not show the prompt
+
+  $ hg prune 14
+  1 changesets pruned
+  $ hg touch 14 --hidden
+  1 new unstable changesets
--- a/tests/test-unstable.t	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-unstable.t	Thu Feb 04 11:17:09 2016 +0000
@@ -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	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-wireproto-bundle1.t	Thu Feb 04 11:17:09 2016 +0000
@@ -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	Thu Jan 14 14:02:05 2016 -0800
+++ b/tests/test-wireproto.t	Thu Feb 04 11:17:09 2016 +0000
@@ -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)