changeset 2799:1627153804dc mercurial-4.1

test-compat: merge with mercurial-4.2
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Tue, 25 Jul 2017 03:59:22 +0200
parents 2280461343e5 (diff) 52d5c5ce27e4 (current diff)
children 13c4d518327a f25c79365f5d
files tests/test-discovery-obshashrange.t tests/test-evolve-obshistory.t tests/test-evolve-serveronly-bundle2.t tests/test-evolve-templates.t tests/test-evolve.t tests/test-fold.t tests/test-inhibit.t tests/test-obsolete.t tests/test-prev-next.t tests/test-prune.t tests/test-topic-stack.t tests/test-userguide.t tests/test-wireproto-bundle1.t tests/test-wireproto.t
diffstat 41 files changed, 2357 insertions(+), 1301 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Sun Jul 02 18:22:39 2017 +0200
+++ b/.hgtags	Tue Jul 25 03:59:22 2017 +0200
@@ -54,3 +54,4 @@
 6da4ca7b3e4fc214a363a5cf723554f114b7f51e 6.3.0
 e358c0263e4629746a7c925429c951f67d4b02b1 6.3.1
 e60248f26f923f4460682252f7863ff86f7b86b0 6.4.0
+734c0bc066cdc0121a20a9cb44c8cc30c653be94 6.5.0
--- a/README	Sun Jul 02 18:22:39 2017 +0200
+++ b/README	Tue Jul 25 03:59:22 2017 +0200
@@ -121,26 +121,52 @@
 Changelog
 =========
 
-6.5.0 - in progress
+6.6.0 - in progress
 -------------------
 
+  - amend: add a --extract flag to move change back to the working copy
+    (same as uncommit, but accessible through the amend commit)
+  - split: now properly refuse to split public changeset
+  - commands: unify and improve the pre-rewrite validation and error message
+  - uncommit: add support for --current-date and --current-user option
+  - fold: add support for --current-date and --current-user option
+  - metaedit: add support for --current-date and --current-user option
+  - split add support for --current-date and --current-user option
+
+  - topic: add --age option to sort topic by the most recently touched,
+  - topic: add a 't0' to access the root of a topic while keeping it active,
+  - topic: allow 'hg prev' to me move to 't0',
+  - topic: add a config option to enforce topic on new commit
+    (experimental.enforce-topic)
+
+6.5.0 -- 2017-07-02
+-------------------
+
+features:
+
  - obslog: gain a --patch flag to display changes introduced by the evolution
   (Currently limited to in simple case only)
+ - log: display obsolescence fate by default, (future 4.3 only)
+ - doc: various minor improvement.
 
- - stack: also show the unstable status for the current changeset (issue5553)
+bugfixes:
+
+ - evolve: fix branch preservation for merge,
+ - obsfate: improve support for advanced template reformating,
+ - split: preserve author of the splitted changeset.
+ - grab: properly fix hg executable on windows.
+
+topic (0.1.0):
+
+ - stack: also show the unstable status for the current changeset, (issue5553)
  - stack: properly abort when and unknown topic is requested,
- - stack: add basic and raw support for named branches
- - topic: changing topic on revs no longer adds extra instability (issue5441)
+ - stack: add basic and raw support for named branches,
+ - topic: changing topic on revs no longer adds extra instability, (issue5441)
  - topic: topics: rename '--change' flag to '--rev' flag,
  - topic: multiple large performance improvements,
  - topic: various small output improvement,
-
-6.4.1 - in progress
--------------------
+ - topic: improved topic preservation for various commands.
 
- - evolve: fix branch preservation for merge
- - obsfate: improve support for advanced template reformating
- - split: preserve author of the splitted changeset,
 
 6.4.0 -- 2017-06-16
 -------------------
--- a/debian/changelog	Sun Jul 02 18:22:39 2017 +0200
+++ b/debian/changelog	Tue Jul 25 03:59:22 2017 +0200
@@ -1,3 +1,9 @@
+mercurial-evolve (6.5.0-1) UNRELEASED; urgency=medium
+
+  * new upstream release
+
+ -- Pierre-Yves David <marmoute@nodosa.octopoid.net>  Sun, 02 Jul 2017 19:35:17 +0200
+
 mercurial-evolve (6.4.0-1) unstable; urgency=medium
 
   * new upstream release
--- a/hgext3rd/evolve/__init__.py	Sun Jul 02 18:22:39 2017 +0200
+++ b/hgext3rd/evolve/__init__.py	Tue Jul 25 03:59:22 2017 +0200
@@ -77,6 +77,11 @@
   # (recommended 'yes' for server (default))
   obshashrange.warm-cache = no
 
+The initial cache warming is currently a bit slow. To make sure it is build you
+can run the following commands in your repository::
+
+    $ hg debugobshashrange --rev 'head()
+
 It is recommended to enable the blackbox extension. It gathers useful data about
 the experiment. It is shipped with Mercurial so no extra install is needed::
 
@@ -96,6 +101,13 @@
     # ensuring no large repository will get affected.
     obshashrange.max-revs = 100000 # default is None
 
+For very large repositories. it is currently recommended to disable obsmarkers
+discovery (Make sure you follow release announcement to know when you can turn
+it back on).
+
+    [experimental]
+    evolution.obsdiscovery = no
+
 Effect Flag Experiment
 ======================
 
@@ -202,12 +214,45 @@
 
 Obsolescence markers will be exchanged between repositories that explicitly
 assert support for the obsolescence feature (this can currently only be done
-via an extension).""".strip()
+via an extension).
+
+Instability
+==========
+
+(note: the vocabulary is in the process of being updated)
+
+Rewriting changesets might introduce instability (currently 'trouble').
+
+There are two main kinds of instability: orphaning and diverging.
+
+Orphans are changesets left behind when their ancestors are rewritten, (currently: 'unstable').
+Divergence has two variants:
+
+* Content-divergence occurs when independent rewrites of the same changesets
+  lead to different results. (currently: 'divergent')
+
+* Phase-divergence occurs when the old (obsolete) version of a changeset
+  becomes public. (currently: 'bumped')
+
+If it possible to prevent local creation of orphans by using the following config::
+
+    [experimental]
+    evolution=createmarkers,allnewcommands,exchange
+
+You can also enable that option explicitly::
+
+    [experimental]
+    evolution=createmarkers,allnewcommands,allowunstable,exchange
+
+or simply::
+
+    [experimental]
+    evolution=all
+""".strip()
 
 
 import os
 import sys
-import random
 import re
 import collections
 import errno
@@ -231,7 +276,6 @@
 
 import mercurial
 from mercurial import util
-from mercurial import repair
 
 from mercurial import obsolete
 if not obsolete._enabled:
@@ -258,7 +302,7 @@
     scmutil,
 )
 
-from mercurial.commands import walkopts, commitopts, commitopts2, mergetoolopts
+from mercurial.commands import mergetoolopts
 from mercurial.i18n import _
 from mercurial.node import nullid
 
@@ -266,11 +310,13 @@
     checkheads,
     compat,
     debugcmd,
+    cmdrewrite,
     exthelper,
     metadata,
     obscache,
     obsexchange,
     obshistory,
+    rewriteutil,
     safeguard,
     templatekw,
     utility,
@@ -287,6 +333,7 @@
 commandopt = 'allnewcommands'
 
 obsexcmsg = utility.obsexcmsg
+shorttemplate = utility.shorttemplate
 
 colortable = {'evolve.node': 'yellow',
               'evolve.user': 'green',
@@ -301,7 +348,11 @@
 _unpack = struct.unpack
 
 aliases, entry = cmdutil.findcmd('commit', commands.table)
-interactiveopt = [['i', 'interactive', None, _('use interactive mode')]]
+commitopts3 = cmdrewrite.commitopts3
+interactiveopt = cmdrewrite.interactiveopt
+_bookmarksupdater = rewriteutil.bookmarksupdater
+rewrite = rewriteutil.rewrite
+
 # This extension contains the following code
 #
 # - Extension Helper code
@@ -318,6 +369,7 @@
 eh.merge(obshistory.eh)
 eh.merge(templatekw.eh)
 eh.merge(compat.eh)
+eh.merge(cmdrewrite.eh)
 uisetup = eh.final_uisetup
 extsetup = eh.final_extsetup
 reposetup = eh.final_reposetup
@@ -388,25 +440,6 @@
 ### experimental behavior                                         ###
 #####################################################################
 
-commitopts3 = [
-    ('D', 'current-date', None,
-     _('record the current date as commit date')),
-    ('U', 'current-user', None,
-     _('record the current user as committer')),
-]
-
-def _resolveoptions(ui, opts):
-    """modify commit options dict to handle related options
-
-    For now, all it does is figure out the commit date: respect -D unless
-    -d was supplied.
-    """
-    # N.B. this is extremely similar to setupheaderopts() in mq.py
-    if not opts.get('date') and opts.get('current_date'):
-        opts['date'] = '%d %d' % util.makedate()
-    if not opts.get('user') and opts.get('current_user'):
-        opts['user'] = ui.username()
-
 getrevs = obsolete.getrevs
 
 #####################################################################
@@ -808,92 +841,6 @@
 ### changeset rewriting logic
 #############################
 
-def rewrite(repo, old, updates, head, newbases, commitopts):
-    """Return (nodeid, created) where nodeid is the identifier of the
-    changeset generated by the rewrite process, and created is True if
-    nodeid was actually created. If created is False, nodeid
-    references a changeset existing before the rewrite call.
-    """
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction('rewrite')
-        if len(old.parents()) > 1: # XXX remove this unnecessary limitation.
-            raise error.Abort(_('cannot amend merge changesets'))
-        base = old.p1()
-        updatebookmarks = _bookmarksupdater(repo, old.node(), tr)
-
-        # commit a new version of the old changeset, including the update
-        # collect all files which might be affected
-        files = set(old.files())
-        for u in updates:
-            files.update(u.files())
-
-        # Recompute copies (avoid recording a -> b -> a)
-        copied = copies.pathcopies(base, head)
-
-        # prune files which were reverted by the updates
-        def samefile(f):
-            if f in head.manifest():
-                a = head.filectx(f)
-                if f in base.manifest():
-                    b = base.filectx(f)
-                    return (a.data() == b.data()
-                            and a.flags() == b.flags())
-                else:
-                    return False
-            else:
-                return f not in base.manifest()
-        files = [f for f in files if not samefile(f)]
-        # commit version of these files as defined by head
-        headmf = head.manifest()
-
-        def filectxfn(repo, ctx, path):
-            if path in headmf:
-                fctx = head[path]
-                flags = fctx.flags()
-                mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
-                                          islink='l' in flags,
-                                          isexec='x' in flags,
-                                          copied=copied.get(path))
-                return mctx
-            return None
-
-        message = cmdutil.logmessage(repo.ui, commitopts)
-        if not message:
-            message = old.description()
-
-        user = commitopts.get('user') or old.user()
-        # TODO: In case not date is given, we should take the old commit date
-        # if we are working one one changeset or mimic the fold behavior about
-        # date
-        date = commitopts.get('date') or None
-        extra = dict(commitopts.get('extra', old.extra()))
-        extra['branch'] = head.branch()
-
-        new = context.memctx(repo,
-                             parents=newbases,
-                             text=message,
-                             files=files,
-                             filectxfn=filectxfn,
-                             user=user,
-                             date=date,
-                             extra=extra)
-
-        if commitopts.get('edit'):
-            new._text = cmdutil.commitforceeditor(repo, new, [])
-        revcount = len(repo)
-        newid = repo.commitctx(new)
-        new = repo[newid]
-        created = len(repo) != revcount
-        updatebookmarks(newid)
-
-        tr.close()
-        return newid, created
-    finally:
-        lockmod.release(tr, lock, wlock)
-
 class MergeFailure(error.Abort):
     pass
 
@@ -962,29 +909,8 @@
     _finalizerelocate(repo, orig, dest, nodenew, tr)
     return nodenew
 
-def _bookmarksupdater(repo, oldid, tr):
-    """Return a callable update(newid) updating the current bookmark
-    and bookmarks bound to oldid to newid.
-    """
-    def updatebookmarks(newid):
-        dirty = False
-        oldbookmarks = repo.nodebookmarks(oldid)
-        if oldbookmarks:
-            for b in oldbookmarks:
-                repo._bookmarks[b] = newid
-            dirty = True
-        if dirty:
-            repo._bookmarks.recordchange(tr)
-    return updatebookmarks
-
 ### new command
 #############################
-metadataopts = [
-    ('d', 'date', '',
-     _('record the specified date in metadata'), _('DATE')),
-    ('u', 'user', '',
-     _('record the specified user in metadata'), _('USER')),
-]
 
 @eh.uisetup
 def _installimportobsolete(ui):
@@ -1966,7 +1892,7 @@
         with repo.dirstate.parentchange():
             repo.dirstate.setparents(divergent.node(), node.nullid)
         oldlen = len(repo)
-        amend(ui, repo, message='', logfile='')
+        cmdrewrite.amend(ui, repo, message='', logfile='')
         if oldlen == len(repo):
             new = divergent
             # no changes
@@ -1994,7 +1920,68 @@
     raise error.Abort("base of divergent changeset %s not found" % ctx,
                       hint='this case is not yet handled')
 
-shorttemplate = "[{label('evolve.rev', rev)}] {desc|firstline}\n"
+def _gettopic(ctx):
+    """handle topic fetching with or without the extension"""
+    return getattr(ctx, 'topic', lambda: '')()
+
+def _gettopicidx(ctx):
+    """handle topic fetching with or without the extension"""
+    return getattr(ctx, 'topicidx', lambda: None)()
+
+def _getcurrenttopic(repo):
+    return getattr(repo, 'currenttopic', '')
+
+def _prevupdate(repo, displayer, target, bookmark, dryrun):
+    if dryrun:
+        repo.ui.write(('hg update %s;\n' % target.rev()))
+        if bookmark is not None:
+            repo.ui.write(('hg bookmark %s -r %s;\n'
+                           % (bookmark, target.rev())))
+    else:
+        ret = hg.update(repo, target.rev())
+        if not ret:
+            tr = lock = None
+            try:
+                lock = repo.lock()
+                tr = repo.transaction('previous')
+                if bookmark is not None:
+                    bmchanges = [(bookmark, target.node())]
+                    compat.bookmarkapplychanges(repo, tr, bmchanges)
+                else:
+                    bookmarksmod.deactivate(repo)
+                tr.close()
+            finally:
+                lockmod.release(tr, lock)
+
+    displayer.show(target)
+
+def _findprevtarget(repo, displayer, movebookmark=False, topic=True):
+    target = bookmark = None
+    wkctx = repo[None]
+    p1 = wkctx.parents()[0]
+    parents = p1.parents()
+    currenttopic = _getcurrenttopic(repo)
+
+    # we do not filter in the 1 case to allow prev to t0
+    if currenttopic and topic and _gettopicidx(p1) != 1:
+        parents = [ctx for ctx in parents if ctx.topic() == currenttopic]
+
+    # issue message for the various case
+    if p1.node() == node.nullid:
+        repo.ui.warn(_('already at repository root\n'))
+    elif not parents and currenttopic:
+        repo.ui.warn(_('no parent in topic "%s"\n') % currenttopic)
+        repo.ui.warn(_('(do you want --no-topic)\n'))
+    elif len(parents) == 1:
+        target = parents[0]
+        bookmark = None
+        if movebookmark:
+            bookmark = repo._activebookmark
+    else:
+        for p in parents:
+            displayer.show(p)
+        repo.ui.warn(_('multiple parents, explicitly update to one\n'))
+    return target, bookmark
 
 @eh.command(
     '^previous',
@@ -2025,44 +2012,22 @@
                 exc.hint = _('do you want --merge?')
                 raise
 
-        parents = wparents[0].parents()
-        topic = getattr(repo, 'currenttopic', '')
-        if topic and not opts.get("no_topic", False):
-            parents = [ctx for ctx in parents if ctx.topic() == topic]
         displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-        if not parents:
-            ui.warn(_('no parent in topic "%s"\n') % topic)
-            ui.warn(_('(do you want --no-topic)\n'))
-        elif len(parents) == 1:
-            p = parents[0]
-            bm = repo._activebookmark
-            shouldmove = opts.get('move_bookmark') and bm is not None
-            if dryrunopt:
-                ui.write(('hg update %s;\n' % p.rev()))
-                if shouldmove:
-                    ui.write(('hg bookmark %s -r %s;\n' % (bm, p.rev())))
-            else:
-                ret = hg.update(repo, p.rev())
-                if not ret:
-                    tr = lock = None
-                    try:
-                        lock = repo.lock()
-                        tr = repo.transaction('previous')
-                        if shouldmove:
-                            repo._bookmarks[bm] = p.node()
-                            repo._bookmarks.recordchange(tr)
-                        else:
-                            bookmarksmod.deactivate(repo)
-                        tr.close()
-                    finally:
-                        lockmod.release(tr, lock)
+        topic = not opts.get("no_topic", False)
 
-            displayer.show(p)
+        target, bookmark = _findprevtarget(repo, displayer,
+                                           opts.get('move_bookmark'), topic)
+        if target is not None:
+            backup = repo.ui.backupconfig('_internal', 'keep-topic')
+            try:
+                if topic and _getcurrenttopic(repo) != _gettopic(target):
+                    repo.ui.setconfig('_internal', 'keep-topic', 'yes',
+                                      source='topic-extension')
+                _prevupdate(repo, displayer, target, bookmark, dryrunopt)
+            finally:
+                repo.ui.restoreconfig(backup)
             return 0
         else:
-            for p in parents:
-                displayer.show(p)
-            ui.warn(_('multiple parents, explicitly update to one\n'))
             return 1
     finally:
         lockmod.release(wlock)
@@ -2124,8 +2089,8 @@
                         lock = repo.lock()
                         tr = repo.transaction('next')
                         if shouldmove:
-                            repo._bookmarks[bm] = c.node()
-                            repo._bookmarks.recordchange(tr)
+                            bmchanges = [(bm, c.node())]
+                            compat.bookmarkapplychanges(repo, tr, bmchanges)
                         else:
                             bookmarksmod.deactivate(repo)
                         tr.close()
@@ -2176,458 +2141,6 @@
     finally:
         lockmod.release(wlock)
 
-def _reachablefrombookmark(repo, revs, bookmarks):
-    """filter revisions and bookmarks reachable from the given bookmark
-    yoinked from mq.py
-    """
-    repomarks = repo._bookmarks
-    if not bookmarks.issubset(repomarks):
-        raise error.Abort(_("bookmark '%s' not found") %
-                          ','.join(sorted(bookmarks - set(repomarks.keys()))))
-
-    # If the requested bookmark is not the only one pointing to a
-    # a revision we have to only delete the bookmark and not strip
-    # anything. revsets cannot detect that case.
-    nodetobookmarks = {}
-    for mark, bnode in repomarks.iteritems():
-        nodetobookmarks.setdefault(bnode, []).append(mark)
-    for marks in nodetobookmarks.values():
-        if bookmarks.issuperset(marks):
-            rsrevs = repair.stripbmrevset(repo, marks[0])
-            revs = set(revs)
-            revs.update(set(rsrevs))
-            revs = sorted(revs)
-    return repomarks, revs
-
-def _deletebookmark(repo, repomarks, bookmarks):
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction('prune')
-        for bookmark in bookmarks:
-            del repomarks[bookmark]
-        repomarks.recordchange(tr)
-        tr.close()
-        for bookmark in sorted(bookmarks):
-            repo.ui.write(_("bookmark '%s' deleted\n") % bookmark)
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-def _getmetadata(**opts):
-    metadata = {}
-    date = opts.get('date')
-    user = opts.get('user')
-    if date:
-        metadata['date'] = '%i %i' % util.parsedate(date)
-    if user:
-        metadata['user'] = user
-    return metadata
-
-@eh.command(
-    '^prune|obsolete',
-    [('n', 'new', [], _("successor changeset (DEPRECATED)")),
-     ('s', 'succ', [], _("successor changeset")),
-     ('r', 'rev', [], _("revisions to prune")),
-     ('k', 'keep', None, _("does not modify working copy during prune")),
-     ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")),
-     ('', 'fold', False,
-      _("record a fold (multiple precursors, one successors)")),
-     ('', 'split', False,
-      _("record a split (on precursor, multiple successors)")),
-     ('B', 'bookmark', [], _("remove revs only reachable from given"
-                             " bookmark"))] + metadataopts,
-    _('[OPTION] [-r] REV...'))
-# XXX -U  --noupdate option to prevent wc update and or bookmarks update ?
-def cmdprune(ui, repo, *revs, **opts):
-    """hide changesets by marking them obsolete
-
-    Pruned changesets are obsolete with no successors. If they also have no
-    descendants, they are hidden (invisible to all commands).
-
-    Non-obsolete descendants of pruned changesets become "unstable". Use :hg:`evolve`
-    to handle this situation.
-
-    When you prune the parent of your working copy, Mercurial updates the working
-    copy to a non-obsolete parent.
-
-    You can use ``--succ`` to tell Mercurial that a newer version (successor) of the
-    pruned changeset exists. Mercurial records successor revisions in obsolescence
-    markers.
-
-    You can use the ``--biject`` option to specify a 1-1 mapping (bijection) between
-    revisions to pruned (precursor) and successor changesets. This option may be
-    removed in a future release (with the functionality provided automatically).
-
-    If you specify multiple revisions in ``--succ``, you are recording a "split" and
-    must acknowledge it by passing ``--split``. Similarly, when you prune multiple
-    changesets with a single successor, you must pass the ``--fold`` option.
-    """
-    revs = scmutil.revrange(repo, list(revs) + opts.get('rev'))
-    succs = opts['new'] + opts['succ']
-    bookmarks = set(opts.get('bookmark'))
-    metadata = _getmetadata(**opts)
-    biject = opts.get('biject')
-    fold = opts.get('fold')
-    split = opts.get('split')
-
-    options = [o for o in ('biject', 'fold', 'split') if opts.get(o)]
-    if 1 < len(options):
-        raise error.Abort(_("can only specify one of %s") % ', '.join(options))
-
-    if bookmarks:
-        repomarks, revs = _reachablefrombookmark(repo, revs, bookmarks)
-        if not revs:
-            # no revisions to prune - delete bookmark immediately
-            _deletebookmark(repo, repomarks, bookmarks)
-
-    if not revs:
-        raise error.Abort(_('nothing to prune'))
-
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction('prune')
-        # defines pruned changesets
-        precs = []
-        revs.sort()
-        for p in revs:
-            cp = repo[p]
-            if not cp.mutable():
-                # note: createmarkers() would have raised something anyway
-                raise error.Abort('cannot prune immutable changeset: %s' % cp,
-                                  hint="see 'hg help phases' for details")
-            precs.append(cp)
-        if not precs:
-            raise error.Abort('nothing to prune')
-
-        if _disallowednewunstable(repo, revs):
-            raise error.Abort(_("cannot prune in the middle of a stack"),
-                              hint=_("new unstable changesets are not allowed"))
-
-        # defines successors changesets
-        sucs = scmutil.revrange(repo, succs)
-        sucs.sort()
-        sucs = tuple(repo[n] for n in sucs)
-        if not biject and len(sucs) > 1 and len(precs) > 1:
-            msg = "Can't use multiple successors for multiple precursors"
-            hint = _("use --biject to mark a series as a replacement"
-                     " for another")
-            raise error.Abort(msg, hint=hint)
-        elif biject and len(sucs) != len(precs):
-            msg = "Can't use %d successors for %d precursors" \
-                % (len(sucs), len(precs))
-            raise error.Abort(msg)
-        elif (len(precs) == 1 and len(sucs) > 1) and not split:
-            msg = "please add --split if you want to do a split"
-            raise error.Abort(msg)
-        elif len(sucs) == 1 and len(precs) > 1 and not fold:
-            msg = "please add --fold if you want to do a fold"
-            raise error.Abort(msg)
-        elif biject:
-            relations = [(p, (s,)) for p, s in zip(precs, sucs)]
-        else:
-            relations = [(p, sucs) for p in precs]
-
-        wdp = repo['.']
-
-        if len(sucs) == 1 and len(precs) == 1 and wdp in precs:
-            # '.' killed, so update to the successor
-            newnode = sucs[0]
-        else:
-            # update to an unkilled parent
-            newnode = wdp
-
-            while newnode in precs or newnode.obsolete():
-                newnode = newnode.parents()[0]
-
-        if newnode.node() != wdp.node():
-            if opts.get('keep', False):
-                # This is largely the same as the implementation in
-                # strip.stripcmd(). We might want to refactor this somewhere
-                # common at some point.
-
-                # only reset the dirstate for files that would actually change
-                # between the working context and uctx
-                descendantrevs = repo.revs("%d::." % newnode.rev())
-                changedfiles = []
-                for rev in descendantrevs:
-                    # blindly reset the files, regardless of what actually
-                    # changed
-                    changedfiles.extend(repo[rev].files())
-
-                # reset files that only changed in the dirstate too
-                dirstate = repo.dirstate
-                dirchanges = [f for f in dirstate if dirstate[f] != 'n']
-                changedfiles.extend(dirchanges)
-                repo.dirstate.rebuild(newnode.node(), newnode.manifest(),
-                                      changedfiles)
-                dirstate.write(tr)
-            else:
-                bookactive = repo._activebookmark
-                # Active bookmark that we don't want to delete (with -B option)
-                # we deactivate and move it before the update and reactivate it
-                # after
-                movebookmark = bookactive and not bookmarks
-                if movebookmark:
-                    bookmarksmod.deactivate(repo)
-                    repo._bookmarks[bookactive] = newnode.node()
-                    repo._bookmarks.recordchange(tr)
-                commands.update(ui, repo, newnode.rev())
-                ui.status(_('working directory now at %s\n')
-                          % ui.label(str(newnode), 'evolve.node'))
-                if movebookmark:
-                    bookmarksmod.activate(repo, bookactive)
-
-        # update bookmarks
-        if bookmarks:
-            _deletebookmark(repo, repomarks, bookmarks)
-
-        # create markers
-        obsolete.createmarkers(repo, relations, metadata=metadata)
-
-        # informs that changeset have been pruned
-        ui.status(_('%i changesets pruned\n') % len(precs))
-
-        for ctx in repo.unfiltered().set('bookmark() and %ld', precs):
-            # used to be:
-            #
-            #   ldest = list(repo.set('max((::%d) - obsolete())', ctx))
-            #   if ldest:
-            #      c = ldest[0]
-            #
-            # but then revset took a lazy arrow in the knee and became much
-            # slower. The new forms makes as much sense and a much faster.
-            for dest in ctx.ancestors():
-                if not dest.obsolete():
-                    updatebookmarks = _bookmarksupdater(repo, ctx.node(), tr)
-                    updatebookmarks(dest.node())
-                    break
-
-        tr.close()
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-@eh.command(
-    'amend|refresh',
-    [('A', 'addremove', None,
-      _('mark new/missing files as added/removed before committing')),
-     ('e', 'edit', False, _('invoke editor on commit messages')),
-     ('', 'close-branch', None,
-      _('mark a branch as closed, hiding it from the branch list')),
-     ('s', 'secret', None, _('use the secret phase for committing')),
-    ] + walkopts + commitopts + commitopts2 + commitopts3 + interactiveopt,
-    _('[OPTION]... [FILE]...'))
-def amend(ui, repo, *pats, **opts):
-    """combine a changeset with updates and replace it with a new one
-
-    Commits a new changeset incorporating both the changes to the given files
-    and all the changes from the current parent changeset into the repository.
-
-    See :hg:`commit` for details about committing changes.
-
-    If you don't specify -m, the parent's message will be reused.
-
-    Behind the scenes, Mercurial first commits the update as a regular child
-    of the current parent. Then it creates a new commit on the parent's parents
-    with the updated contents. Then it changes the working copy parent to this
-    new combined changeset. Finally, the old changeset and its update are hidden
-    from :hg:`log` (unless you use --hidden with log).
-
-    Returns 0 on success, 1 if nothing changed.
-    """
-    opts = opts.copy()
-    edit = opts.pop('edit', False)
-    log = opts.get('logfile')
-    opts['amend'] = True
-    if not (edit or opts['message'] or log):
-        opts['message'] = repo['.'].description()
-    _resolveoptions(ui, opts)
-    _alias, commitcmd = cmdutil.findcmd('commit', commands.table)
-    return commitcmd[0](ui, repo, *pats, **opts)
-
-
-def _touchedbetween(repo, source, dest, match=None):
-    touched = set()
-    for files in repo.status(source, dest, match=match)[:3]:
-        touched.update(files)
-    return touched
-
-def _commitfiltered(repo, ctx, match, target=None):
-    """Recommit ctx with changed files not in match. Return the new
-    node identifier, or None if nothing changed.
-    """
-    base = ctx.p1()
-    if target is None:
-        target = base
-    # ctx
-    initialfiles = _touchedbetween(repo, base, ctx)
-    if base == target:
-        affected = set(f for f in initialfiles if match(f))
-        newcontent = set()
-    else:
-        affected = _touchedbetween(repo, target, ctx, match=match)
-        newcontent = _touchedbetween(repo, target, base, match=match)
-    # The commit touchs all existing files
-    # + all file that needs a new content
-    # - the file affected bny uncommit with the same content than base.
-    files = (initialfiles - affected) | newcontent
-    if not newcontent and files == initialfiles:
-        return None
-
-    # Filter copies
-    copied = copies.pathcopies(target, ctx)
-    copied = dict((dst, src) for dst, src in copied.iteritems()
-                  if dst in files)
-
-    def filectxfn(repo, memctx, path, contentctx=ctx, redirect=newcontent):
-        if path in redirect:
-            return filectxfn(repo, memctx, path, contentctx=target, redirect=())
-        if path not in contentctx:
-            return None
-        fctx = contentctx[path]
-        flags = fctx.flags()
-        mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
-                                  islink='l' in flags,
-                                  isexec='x' in flags,
-                                  copied=copied.get(path))
-        return mctx
-
-    new = context.memctx(repo,
-                         parents=[base.node(), node.nullid],
-                         text=ctx.description(),
-                         files=files,
-                         filectxfn=filectxfn,
-                         user=ctx.user(),
-                         date=ctx.date(),
-                         extra=ctx.extra())
-    # commitctx always create a new revision, no need to check
-    newid = repo.commitctx(new)
-    return newid
-
-def _uncommitdirstate(repo, oldctx, match):
-    """Fix the dirstate after switching the working directory from
-    oldctx to a copy of oldctx not containing changed files matched by
-    match.
-    """
-    ctx = repo['.']
-    ds = repo.dirstate
-    copies = dict(ds.copies())
-    m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
-    for f in m:
-        if ds[f] == 'r':
-            # modified + removed -> removed
-            continue
-        ds.normallookup(f)
-
-    for f in a:
-        if ds[f] == 'r':
-            # added + removed -> unknown
-            ds.drop(f)
-        elif ds[f] != 'a':
-            ds.add(f)
-
-    for f in r:
-        if ds[f] == 'a':
-            # removed + added -> normal
-            ds.normallookup(f)
-        elif ds[f] != 'r':
-            ds.remove(f)
-
-    # Merge old parent and old working dir copies
-    oldcopies = {}
-    for f in (m + a):
-        src = oldctx[f].renamed()
-        if src:
-            oldcopies[f] = src[0]
-    oldcopies.update(copies)
-    copies = dict((dst, oldcopies.get(src, src))
-                  for dst, src in oldcopies.iteritems())
-    # Adjust the dirstate copies
-    for dst, src in copies.iteritems():
-        if (src not in ctx or dst in ctx or ds[dst] != 'a'):
-            src = None
-        ds.copy(src, dst)
-
-@eh.command(
-    '^uncommit',
-    [('a', 'all', None, _('uncommit all changes when no arguments given')),
-     ('r', 'rev', '', _('revert commit content to REV instead')),
-     ] + commands.walkopts,
-    _('[OPTION]... [NAME]'))
-def uncommit(ui, repo, *pats, **opts):
-    """move changes from parent revision to working directory
-
-    Changes to selected files in the checked out revision appear again as
-    uncommitted changed in the working directory. A new revision
-    without the selected changes is created, becomes the checked out
-    revision, and obsoletes the previous one.
-
-    The --include option specifies patterns to uncommit.
-    The --exclude option specifies patterns to keep in the commit.
-
-    The --rev argument let you change the commit file to a content of another
-    revision. It still does not change the content of your file in the working
-    directory.
-
-    Return 0 if changed files are uncommitted.
-    """
-
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        wctx = repo[None]
-        if len(wctx.parents()) <= 0:
-            raise error.Abort(_("cannot uncommit null changeset"))
-        if len(wctx.parents()) > 1:
-            raise error.Abort(_("cannot uncommit while merging"))
-        old = repo['.']
-        if old.phase() == phases.public:
-            raise error.Abort(_("cannot rewrite immutable changeset"))
-        if len(old.parents()) > 1:
-            raise error.Abort(_("cannot uncommit merge changeset"))
-        oldphase = old.phase()
-
-        rev = None
-        if opts.get('rev'):
-            rev = scmutil.revsingle(repo, opts.get('rev'))
-            ctx = repo[None]
-            if ctx.p1() == rev or ctx.p2() == rev:
-                raise error.Abort(_("cannot uncommit to parent changeset"))
-
-        onahead = old.rev() in repo.changelog.headrevs()
-        disallowunstable = not obsolete.isenabled(repo,
-                                                  obsolete.allowunstableopt)
-        if disallowunstable and not onahead:
-            raise error.Abort(_("cannot uncommit in the middle of a stack"))
-
-        # Recommit the filtered changeset
-        tr = repo.transaction('uncommit')
-        updatebookmarks = _bookmarksupdater(repo, old.node(), tr)
-        newid = None
-        includeorexclude = opts.get('include') or opts.get('exclude')
-        if (pats or includeorexclude or opts.get('all')):
-            match = scmutil.match(old, pats, opts)
-            newid = _commitfiltered(repo, old, match, target=rev)
-        if newid is None:
-            raise error.Abort(_('nothing to uncommit'),
-                              hint=_("use --all to uncommit all files"))
-        # Move local changes on filtered changeset
-        obsolete.createmarkers(repo, [(old, (repo[newid],))])
-        phases.retractboundary(repo, tr, oldphase, [newid])
-        with repo.dirstate.parentchange():
-            repo.dirstate.setparents(newid, node.nullid)
-            _uncommitdirstate(repo, old, match)
-        updatebookmarks(newid)
-        if not repo[newid].files():
-            ui.warn(_("new changeset is empty\n"))
-            ui.status(_("(use 'hg prune .' to remove it)\n"))
-        tr.close()
-    finally:
-        lockmod.release(tr, lock, wlock)
-
 @eh.wrapcommand('commit')
 def commitwrapper(orig, ui, repo, *arg, **kwargs):
     tr = None
@@ -2650,110 +2163,21 @@
                 markers.append((old, (new,)))
             if markers:
                 obsolete.createmarkers(repo, markers)
+            bmchanges = []
             for book in oldbookmarks:
-                repo._bookmarks[book] = new.node()
+                bmchanges.append((book, new.node()))
             if oldbookmarks:
                 if not wlock:
                     wlock = repo.wlock()
                 if not lock:
                     lock = repo.lock()
                 tr = repo.transaction('commit')
-                repo._bookmarks.recordchange(tr)
+                compat.bookmarkapplychanges(repo, tr, bmchanges)
                 tr.close()
         return result
     finally:
         lockmod.release(tr, lock, wlock)
 
-def presplitupdate(repo, ui, prev, ctx):
-    """prepare the working directory for a split (for topic hooking)
-    """
-    hg.update(repo, prev)
-    commands.revert(ui, repo, rev=ctx.rev(), all=True)
-
-@eh.command(
-    '^split',
-    [('r', 'rev', [], _("revision to split")),
-    ] + commitopts + commitopts2,
-    _('hg split [OPTION]... [-r] REV'))
-def cmdsplit(ui, repo, *revs, **opts):
-    """split a changeset into smaller changesets
-
-    By default, split the current revision by prompting for all its hunks to be
-    redistributed into new changesets.
-
-    Use --rev to split a given changeset instead.
-    """
-    tr = wlock = lock = None
-    newcommits = []
-
-    revarg = (list(revs) + opts.get('rev')) or ['.']
-    if len(revarg) != 1:
-        msg = _("more than one revset is given")
-        hnt = _("use either `hg split <rs>` or `hg split --rev <rs>`, not both")
-        raise error.Abort(msg, hint=hnt)
-
-    rev = scmutil.revsingle(repo, revarg[0])
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        cmdutil.bailifchanged(repo)
-        tr = repo.transaction('split')
-        ctx = repo[rev]
-        r = ctx.rev()
-        disallowunstable = not obsolete.isenabled(repo,
-                                                  obsolete.allowunstableopt)
-        if disallowunstable:
-            # XXX We should check head revs
-            if repo.revs("(%d::) - %d", rev, rev):
-                raise error.Abort(_("cannot split commit: %s not a head") % ctx)
-
-        if len(ctx.parents()) > 1:
-            raise error.Abort(_("cannot split merge commits"))
-        prev = ctx.p1()
-        bmupdate = _bookmarksupdater(repo, ctx.node(), tr)
-        bookactive = repo._activebookmark
-        if bookactive is not None:
-            repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark)
-        bookmarksmod.deactivate(repo)
-
-        # Prepare the working directory
-        presplitupdate(repo, ui, prev, ctx)
-
-        def haschanges():
-            modified, added, removed, deleted = repo.status()[:4]
-            return modified or added or removed or deleted
-        msg = ("HG: This is the original pre-split commit message. "
-               "Edit it as appropriate.\n\n")
-        msg += ctx.description()
-        opts['message'] = msg
-        opts['edit'] = True
-        opts['user'] = ctx.user()
-        while haschanges():
-            pats = ()
-            cmdutil.dorecord(ui, repo, commands.commit, 'commit', False,
-                             cmdutil.recordfilter, *pats, **opts)
-            # TODO: Does no seem like the best way to do this
-            # We should make dorecord return the newly created commit
-            newcommits.append(repo['.'])
-            if haschanges():
-                if ui.prompt('Done splitting? [yN]', default='n') == 'y':
-                    commands.commit(ui, repo, **opts)
-                    newcommits.append(repo['.'])
-                    break
-            else:
-                ui.status(_("no more change to split\n"))
-
-        if newcommits:
-            tip = repo[newcommits[-1]]
-            bmupdate(tip.node())
-            if bookactive is not None:
-                bookmarksmod.activate(repo, bookactive)
-            obsolete.createmarkers(repo, [(repo[r], newcommits)])
-        tr.close()
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-
 @eh.wrapcommand('strip', extension='strip', opts=[
     ('', 'bundle', None, _("delete the commit entirely and move it to a "
                            "backup bundle")),
@@ -2777,354 +2201,7 @@
     kwargs['new'] = []
     kwargs['succ'] = []
     kwargs['biject'] = False
-    return cmdprune(ui, repo, *revs, **kwargs)
-
-@eh.command(
-    '^touch',
-    [('r', 'rev', [], 'revision to update'),
-     ('D', 'duplicate', False,
-      'do not mark the new revision as successor of the old one'),
-     ('A', 'allowdivergence', False,
-      'mark the new revision as successor of the old one potentially creating '
-      'divergence')],
-    # allow to choose the seed ?
-    _('[-r] revs'))
-def touch(ui, repo, *revs, **opts):
-    """create successors that are identical to their predecessors except
-    for the changeset ID
-
-    This is used to "resurrect" changesets
-    """
-    duplicate = opts['duplicate']
-    allowdivergence = opts['allowdivergence']
-    revs = list(revs)
-    revs.extend(opts['rev'])
-    if not revs:
-        revs = ['.']
-    revs = scmutil.revrange(repo, revs)
-    if not revs:
-        ui.write_err('no revision to touch\n')
-        return 1
-    if not duplicate and repo.revs('public() and %ld', revs):
-        raise error.Abort("can't touch public revision")
-    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction('touch')
-        revs.sort() # ensure parent are run first
-        newmapping = {}
-        for r in revs:
-            ctx = repo[r]
-            extra = ctx.extra().copy()
-            extra['__touch-noise__'] = random.randint(0, 0xffffffff)
-            # search for touched parent
-            p1 = ctx.p1().node()
-            p2 = ctx.p2().node()
-            p1 = newmapping.get(p1, p1)
-            p2 = newmapping.get(p2, p2)
-
-            if not (duplicate or allowdivergence):
-                # The user hasn't yet decided what to do with the revived
-                # cset, let's ask
-                sset = compat.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])
-            if ctx in repo[None].parents():
-                with repo.dirstate.parentchange():
-                    repo.dirstate.setparents(new, node.nullid)
-        tr.close()
-    finally:
-        lockmod.release(tr, lock, wlock)
-
-@eh.command(
-    '^fold|squash',
-    [('r', 'rev', [], _("revision to fold")),
-     ('', 'exact', None, _("only fold specified revisions")),
-     ('', 'from', None, _("fold revisions linearly to working copy parent"))
-    ] + commitopts + commitopts2,
-    _('hg fold [OPTION]... [-r] REV'))
-def fold(ui, repo, *revs, **opts):
-    """fold multiple revisions into a single one
-
-    With --from, folds all the revisions linearly between the given revisions
-    and the parent of the working directory.
-
-    With --exact, folds only the specified revisions while ignoring the
-    parent of the working directory. In this case, the given revisions must
-    form a linear unbroken chain.
-
-    .. container:: verbose
-
-     Some examples:
-
-     - Fold the current revision with its parent::
-
-         hg fold --from .^
-
-     - Fold all draft revisions with working directory parent::
-
-         hg fold --from 'draft()'
-
-       See :hg:`help phases` for more about draft revisions and
-       :hg:`help revsets` for more about the `draft()` keyword
-
-     - Fold revisions between 3 and 6 with the working directory parent::
-
-         hg fold --from 3::6
-
-     - Fold revisions 3 and 4:
-
-        hg fold "3 + 4" --exact
-
-     - Only fold revisions linearly between foo and @::
-
-         hg fold foo::@ --exact
-    """
-    revs = list(revs)
-    revs.extend(opts['rev'])
-    if not revs:
-        raise error.Abort(_('no revisions specified'))
-
-    revs = scmutil.revrange(repo, revs)
-
-    if opts['from'] and opts['exact']:
-        raise error.Abort(_('cannot use both --from and --exact'))
-    elif opts['from']:
-        # Try to extend given revision starting from the working directory
-        extrevs = repo.revs('(%ld::.) or (.::%ld)', revs, revs)
-        discardedrevs = [r for r in revs if r not in extrevs]
-        if discardedrevs:
-            msg = _("cannot fold non-linear revisions")
-            hint = _("given revisions are unrelated to parent of working"
-                     " directory")
-            raise error.Abort(msg, hint=hint)
-        revs = extrevs
-    elif opts['exact']:
-        # Nothing to do; "revs" is already set correctly
-        pass
-    else:
-        raise error.Abort(_('must specify either --from or --exact'))
-
-    if not revs:
-        raise error.Abort(_('specified revisions evaluate to an empty set'),
-                          hint=_('use different revision arguments'))
-    elif len(revs) == 1:
-        ui.write_err(_('single revision specified, nothing to fold\n'))
-        return 1
-
-    wlock = lock = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-
-        root, head = _foldcheck(repo, revs)
-
-        tr = repo.transaction('fold')
-        try:
-            commitopts = opts.copy()
-            allctx = [repo[r] for r in revs]
-            targetphase = max(c.phase() for c in allctx)
-
-            if commitopts.get('message') or commitopts.get('logfile'):
-                commitopts['edit'] = False
-            else:
-                msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
-                msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
-                         (c.rev(), c.description()) for c in allctx]
-                commitopts['message'] = "\n".join(msgs)
-                commitopts['edit'] = True
-
-            newid, unusedvariable = rewrite(repo, root, allctx, head,
-                                            [root.p1().node(),
-                                             root.p2().node()],
-                                            commitopts=commitopts)
-            phases.retractboundary(repo, tr, targetphase, [newid])
-            obsolete.createmarkers(repo, [(ctx, (repo[newid],))
-                                   for ctx in allctx])
-            tr.close()
-        finally:
-            tr.release()
-        ui.status('%i changesets folded\n' % len(revs))
-        if repo['.'].rev() in revs:
-            hg.update(repo, newid)
-    finally:
-        lockmod.release(lock, wlock)
-
-@eh.command(
-    '^metaedit',
-    [('r', 'rev', [], _("revision to edit")),
-     ('', 'fold', None, _("also fold specified revisions into one")),
-    ] + commitopts + commitopts2,
-    _('hg metaedit [OPTION]... [-r] [REV]'))
-def metaedit(ui, repo, *revs, **opts):
-    """edit commit information
-
-    Edits the commit information for the specified revisions. By default, edits
-    commit information for the working directory parent.
-
-    With --fold, also folds multiple revisions into one if necessary. In this
-    case, the given revisions must form a linear unbroken chain.
-
-    .. container:: verbose
-
-     Some examples:
-
-     - Edit the commit message for the working directory parent::
-
-         hg metaedit
-
-     - Change the username for the working directory parent::
-
-         hg metaedit --user 'New User <new-email@example.com>'
-
-     - Combine all draft revisions that are ancestors of foo but not of @ into
-       one::
-
-         hg metaedit --fold 'draft() and only(foo,@)'
-
-       See :hg:`help phases` for more about draft revisions, and
-       :hg:`help revsets` for more about the `draft()` and `only()` keywords.
-    """
-    revs = list(revs)
-    revs.extend(opts['rev'])
-    if not revs:
-        if opts['fold']:
-            raise error.Abort(_('revisions must be specified with --fold'))
-        revs = ['.']
-
-    wlock = lock = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-
-        revs = scmutil.revrange(repo, revs)
-        if not opts['fold'] and len(revs) > 1:
-            # TODO: handle multiple revisions. This is somewhat tricky because
-            # if we want to edit a series of commits:
-            #
-            #   a ---- b ---- c
-            #
-            # we need to rewrite a first, then directly rewrite b on top of the
-            # new a, then rewrite c on top of the new b. So we need to handle
-            # revisions in topological order.
-            raise error.Abort(_('editing multiple revisions without --fold is '
-                                'not currently supported'))
-
-        if opts['fold']:
-            root, head = _foldcheck(repo, revs)
-        else:
-            if repo.revs("%ld and public()", revs):
-                raise error.Abort(_('cannot edit commit information for public '
-                                    'revisions'))
-            newunstable = _disallowednewunstable(repo, revs)
-            if newunstable:
-                msg = _('cannot edit commit information in the middle'
-                        ' of a stack')
-                hint = _('%s will become unstable and new unstable changes'
-                         ' are not allowed')
-                hint %= repo[newunstable.first()]
-                raise error.Abort(msg, hint=hint)
-            root = head = repo[revs.first()]
-
-        wctx = repo[None]
-        p1 = wctx.p1()
-        tr = repo.transaction('metaedit')
-        newp1 = None
-        try:
-            commitopts = opts.copy()
-            allctx = [repo[r] for r in revs]
-            targetphase = max(c.phase() for c in allctx)
-
-            if commitopts.get('message') or commitopts.get('logfile'):
-                commitopts['edit'] = False
-            else:
-                if opts['fold']:
-                    msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
-                    msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
-                             (c.rev(), c.description()) for c in allctx]
-                else:
-                    msgs = [head.description()]
-                commitopts['message'] = "\n".join(msgs)
-                commitopts['edit'] = True
-
-            # TODO: if the author and message are the same, don't create a new
-            # hash. Right now we create a new hash because the date can be
-            # different.
-            newid, created = rewrite(repo, root, allctx, head,
-                                     [root.p1().node(), root.p2().node()],
-                                     commitopts=commitopts)
-            if created:
-                if p1.rev() in revs:
-                    newp1 = newid
-                phases.retractboundary(repo, tr, targetphase, [newid])
-                obsolete.createmarkers(repo, [(ctx, (repo[newid],))
-                                              for ctx in allctx])
-            else:
-                ui.status(_("nothing changed\n"))
-            tr.close()
-        finally:
-            tr.release()
-
-        if opts['fold']:
-            ui.status('%i changesets folded\n' % len(revs))
-        if newp1 is not None:
-            hg.update(repo, newp1)
-    finally:
-        lockmod.release(lock, wlock)
-
-def _foldcheck(repo, revs):
-    roots = repo.revs('roots(%ld)', revs)
-    if len(roots) > 1:
-        raise error.Abort(_("cannot fold non-linear revisions "
-                            "(multiple roots given)"))
-    root = repo[roots.first()]
-    if root.phase() <= phases.public:
-        raise error.Abort(_("cannot fold public revisions"))
-    heads = repo.revs('heads(%ld)', revs)
-    if len(heads) > 1:
-        raise error.Abort(_("cannot fold non-linear revisions "
-                            "(multiple heads given)"))
-    head = repo[heads.first()]
-    if _disallowednewunstable(repo, revs):
-        msg = _("cannot fold chain not ending with a head or with branching")
-        hint = _("new unstable changesets are not allowed")
-        raise error.Abort(msg, hint=hint)
-    return root, head
-
-def _disallowednewunstable(repo, revs):
-    allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
-    if allowunstable:
-        return revset.baseset()
-    return repo.revs("(%ld::) - %ld", revs, revs)
+    return cmdrewrite.cmdprune(ui, repo, *revs, **kwargs)
 
 @eh.wrapcommand('graft')
 def graftwrapper(orig, ui, repo, *revs, **kwargs):
@@ -3196,23 +2273,24 @@
     if new_format == repo.obsstore._version:
         msg = _('New format is the same as the old format, not upgrading!')
         raise error.Abort(msg)
-    f = repo.svfs('obsstore', 'wb', atomictemp=True)
-    known = set()
-    markers = []
-    for m in origmarkers:
-        # filter out invalid markers
-        if nullid in m[1]:
-            m = list(m)
-            m[1] = tuple(s for s in m[1] if s != nullid)
-            m = tuple(m)
-        if m in known:
-            continue
-        known.add(m)
-        markers.append(m)
-    ui.write(_('Old store is version %d, will rewrite in version %d\n') % (
-        repo.obsstore._version, new_format))
-    map(f.write, obsolete.encodemarkers(markers, True, new_format))
-    f.close()
+    with repo.lock():
+        f = repo.svfs('obsstore', 'wb', atomictemp=True)
+        known = set()
+        markers = []
+        for m in origmarkers:
+            # filter out invalid markers
+            if nullid in m[1]:
+                m = list(m)
+                m[1] = tuple(s for s in m[1] if s != nullid)
+                m = tuple(m)
+            if m in known:
+                continue
+            known.add(m)
+            markers.append(m)
+        ui.write(_('Old store is version %d, will rewrite in version %d\n') % (
+            repo.obsstore._version, new_format))
+        map(f.write, obsolete.encodemarkers(markers, True, new_format))
+        f.close()
     ui.write(_('Done!\n'))
 
 
@@ -3253,20 +2331,22 @@
     nodesrc = orig.node()
     destphase = repo[nodesrc].phase()
     oldbookmarks = repo.nodebookmarks(nodesrc)
+    bmchanges = []
+
     if nodenew is not None:
         phases.retractboundary(repo, tr, destphase, [nodenew])
         obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))])
         for book in oldbookmarks:
-            repo._bookmarks[book] = nodenew
+            bmchanges.append((book, nodenew))
     else:
         obsolete.createmarkers(repo, [(repo[nodesrc], ())])
         # Behave like rebase, move bookmarks to dest
         for book in oldbookmarks:
-            repo._bookmarks[book] = dest.node()
+            bmchanges.append((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)
+        bmchanges.append((book, dest.node()))
+    if bmchanges:
+        compat.bookmarkapplychanges(repo, tr, bmchanges)
 
 evolvestateversion = 0
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/cmdrewrite.py	Tue Jul 25 03:59:22 2017 +0200
@@ -0,0 +1,919 @@
+# Module dedicated to host history rewriting commands
+#
+# Copyright 2017 Octobus <contact@octobus.net>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+# Status: Stabilization of the API in progress
+#
+#   The final set of command should go into core.
+
+from __future__ import absolute_import
+
+import random
+
+from mercurial import (
+    bookmarks as bookmarksmod,
+    cmdutil,
+    commands,
+    context,
+    copies,
+    error,
+    hg,
+    lock as lockmod,
+    node,
+    obsolete,
+    phases,
+    scmutil,
+    util,
+)
+
+from mercurial.i18n import _
+
+from . import (
+    compat,
+    exthelper,
+    rewriteutil,
+    utility,
+)
+
+eh = exthelper.exthelper()
+
+walkopts = commands.walkopts
+commitopts = commands.commitopts
+commitopts2 = commands.commitopts2
+mergetoolopts = commands.mergetoolopts
+
+# option added by evolve
+
+def _resolveoptions(ui, opts):
+    """modify commit options dict to handle related options
+
+    For now, all it does is figure out the commit date: respect -D unless
+    -d was supplied.
+    """
+    # N.B. this is extremely similar to setupheaderopts() in mq.py
+    if not opts.get('date') and opts.get('current_date'):
+        opts['date'] = '%d %d' % util.makedate()
+    if not opts.get('user') and opts.get('current_user'):
+        opts['user'] = ui.username()
+
+commitopts3 = [
+    ('D', 'current-date', None,
+     _('record the current date as commit date')),
+    ('U', 'current-user', None,
+     _('record the current user as committer')),
+]
+
+interactiveopt = [['i', 'interactive', None, _('use interactive mode')]]
+
+@eh.command(
+    'amend|refresh',
+    [('A', 'addremove', None,
+      _('mark new/missing files as added/removed before committing')),
+     ('a', 'all', False, _("match all files")),
+     ('e', 'edit', False, _('invoke editor on commit messages')),
+     ('', 'extract', False, _('extract changes from the commit to the working copy')),
+     ('', 'close-branch', None,
+      _('mark a branch as closed, hiding it from the branch list')),
+     ('s', 'secret', None, _('use the secret phase for committing')),
+    ] + walkopts + commitopts + commitopts2 + commitopts3 + interactiveopt,
+    _('[OPTION]... [FILE]...'))
+def amend(ui, repo, *pats, **opts):
+    """combine a changeset with updates and replace it with a new one
+
+    Commits a new changeset incorporating both the changes to the given files
+    and all the changes from the current parent changeset into the repository.
+
+    See :hg:`commit` for details about committing changes.
+
+    If you don't specify -m, the parent's message will be reused.
+
+    If --extra is specified, the behavior of `hg amend` is reversed: Changes
+    to selected files in the checked out revision appear again as uncommitted
+    changed in the working directory.
+
+    Returns 0 on success, 1 if nothing changed.
+    """
+    opts = opts.copy()
+    if opts.get('extract'):
+        if opts.pop('interactive', False):
+            msg = _('not support for --interactive with --extract yet')
+            raise error.Abort(msg)
+        return uncommit(ui, repo, *pats, **opts)
+    else:
+        if opts.pop('all', False):
+            # add an include for all
+            include = list(opts.get('include'))
+            include.append('re:.*')
+        edit = opts.pop('edit', False)
+        log = opts.get('logfile')
+        opts['amend'] = True
+        if not (edit or opts['message'] or log):
+            opts['message'] = repo['.'].description()
+        _resolveoptions(ui, opts)
+        _alias, commitcmd = cmdutil.findcmd('commit', commands.table)
+        try:
+            wlock = repo.wlock()
+            lock = repo.lock()
+            rewriteutil.precheck(repo, [repo['.'].rev()], action='amend')
+            return commitcmd[0](ui, repo, *pats, **opts)
+        finally:
+            lockmod.release(lock, wlock)
+
+def _touchedbetween(repo, source, dest, match=None):
+    touched = set()
+    for files in repo.status(source, dest, match=match)[:3]:
+        touched.update(files)
+    return touched
+
+def _commitfiltered(repo, ctx, match, target=None, message=None, user=None,
+                    date=None):
+    """Recommit ctx with changed files not in match. Return the new
+    node identifier, or None if nothing changed.
+    """
+    base = ctx.p1()
+    if target is None:
+        target = base
+    # ctx
+    initialfiles = _touchedbetween(repo, base, ctx)
+    if base == target:
+        affected = set(f for f in initialfiles if match(f))
+        newcontent = set()
+    else:
+        affected = _touchedbetween(repo, target, ctx, match=match)
+        newcontent = _touchedbetween(repo, target, base, match=match)
+    # The commit touchs all existing files
+    # + all file that needs a new content
+    # - the file affected bny uncommit with the same content than base.
+    files = (initialfiles - affected) | newcontent
+    if not newcontent and files == initialfiles:
+        return None
+
+    # Filter copies
+    copied = copies.pathcopies(target, ctx)
+    copied = dict((dst, src) for dst, src in copied.iteritems()
+                  if dst in files)
+
+    def filectxfn(repo, memctx, path, contentctx=ctx, redirect=newcontent):
+        if path in redirect:
+            return filectxfn(repo, memctx, path, contentctx=target, redirect=())
+        if path not in contentctx:
+            return None
+        fctx = contentctx[path]
+        flags = fctx.flags()
+        mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
+                                  islink='l' in flags,
+                                  isexec='x' in flags,
+                                  copied=copied.get(path))
+        return mctx
+
+    if message is None:
+        message = ctx.description()
+    if not user:
+        user = ctx.user()
+    if not date:
+        date = ctx.date()
+    new = context.memctx(repo,
+                         parents=[base.node(), node.nullid],
+                         text=message,
+                         files=files,
+                         filectxfn=filectxfn,
+                         user=user,
+                         date=date,
+                         extra=ctx.extra())
+    # commitctx always create a new revision, no need to check
+    newid = repo.commitctx(new)
+    return newid
+
+def _uncommitdirstate(repo, oldctx, match):
+    """Fix the dirstate after switching the working directory from
+    oldctx to a copy of oldctx not containing changed files matched by
+    match.
+    """
+    ctx = repo['.']
+    ds = repo.dirstate
+    copies = dict(ds.copies())
+    m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
+    for f in m:
+        if ds[f] == 'r':
+            # modified + removed -> removed
+            continue
+        ds.normallookup(f)
+
+    for f in a:
+        if ds[f] == 'r':
+            # added + removed -> unknown
+            ds.drop(f)
+        elif ds[f] != 'a':
+            ds.add(f)
+
+    for f in r:
+        if ds[f] == 'a':
+            # removed + added -> normal
+            ds.normallookup(f)
+        elif ds[f] != 'r':
+            ds.remove(f)
+
+    # Merge old parent and old working dir copies
+    oldcopies = {}
+    for f in (m + a):
+        src = oldctx[f].renamed()
+        if src:
+            oldcopies[f] = src[0]
+    oldcopies.update(copies)
+    copies = dict((dst, oldcopies.get(src, src))
+                  for dst, src in oldcopies.iteritems())
+    # Adjust the dirstate copies
+    for dst, src in copies.iteritems():
+        if (src not in ctx or dst in ctx or ds[dst] != 'a'):
+            src = None
+        ds.copy(src, dst)
+
+@eh.command(
+    '^uncommit',
+    [('a', 'all', None, _('uncommit all changes when no arguments given')),
+     ('r', 'rev', '', _('revert commit content to REV instead')),
+     ] + commands.walkopts + commitopts + commitopts2 + commitopts3,
+    _('[OPTION]... [NAME]'))
+def uncommit(ui, repo, *pats, **opts):
+    """move changes from parent revision to working directory
+
+    Changes to selected files in the checked out revision appear again as
+    uncommitted changed in the working directory. A new revision
+    without the selected changes is created, becomes the checked out
+    revision, and obsoletes the previous one.
+
+    The --include option specifies patterns to uncommit.
+    The --exclude option specifies patterns to keep in the commit.
+
+    The --rev argument let you change the commit file to a content of another
+    revision. It still does not change the content of your file in the working
+    directory.
+
+    Return 0 if changed files are uncommitted.
+    """
+
+    _resolveoptions(ui, opts) # process commitopts3
+    wlock = lock = tr = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        wctx = repo[None]
+        if len(wctx.parents()) <= 0:
+            raise error.Abort(_("cannot uncommit null changeset"))
+        if len(wctx.parents()) > 1:
+            raise error.Abort(_("cannot uncommit while merging"))
+        old = repo['.']
+        rewriteutil.precheck(repo, [repo['.'].rev()], action='uncommit')
+        if len(old.parents()) > 1:
+            raise error.Abort(_("cannot uncommit merge changeset"))
+        oldphase = old.phase()
+
+        rev = None
+        if opts.get('rev'):
+            rev = scmutil.revsingle(repo, opts.get('rev'))
+            ctx = repo[None]
+            if ctx.p1() == rev or ctx.p2() == rev:
+                raise error.Abort(_("cannot uncommit to parent changeset"))
+
+        onahead = old.rev() in repo.changelog.headrevs()
+        disallowunstable = not obsolete.isenabled(repo,
+                                                  obsolete.allowunstableopt)
+        if disallowunstable and not onahead:
+            raise error.Abort(_("cannot uncommit in the middle of a stack"))
+
+        # Recommit the filtered changeset
+        tr = repo.transaction('uncommit')
+        updatebookmarks = rewriteutil.bookmarksupdater(repo, old.node(), tr)
+        newid = None
+        includeorexclude = opts.get('include') or opts.get('exclude')
+        if (pats or includeorexclude or opts.get('all')):
+            match = scmutil.match(old, pats, opts)
+            if not (opts['message'] or opts['logfile']):
+                opts['message'] = old.description()
+            message = cmdutil.logmessage(ui, opts)
+            newid = _commitfiltered(repo, old, match, target=rev,
+                                    message=message, user=opts.get('user'),
+                                    date=opts.get('date'))
+        if newid is None:
+            raise error.Abort(_('nothing to uncommit'),
+                              hint=_("use --all to uncommit all files"))
+        # Move local changes on filtered changeset
+        obsolete.createmarkers(repo, [(old, (repo[newid],))])
+        phases.retractboundary(repo, tr, oldphase, [newid])
+        with repo.dirstate.parentchange():
+            repo.dirstate.setparents(newid, node.nullid)
+            _uncommitdirstate(repo, old, match)
+        updatebookmarks(newid)
+        if not repo[newid].files():
+            ui.warn(_("new changeset is empty\n"))
+            ui.status(_("(use 'hg prune .' to remove it)\n"))
+        tr.close()
+    finally:
+        lockmod.release(tr, lock, wlock)
+
+@eh.command(
+    '^fold|squash',
+    [('r', 'rev', [], _("revision to fold")),
+     ('', 'exact', None, _("only fold specified revisions")),
+     ('', 'from', None, _("fold revisions linearly to working copy parent"))
+    ] + commitopts + commitopts2 + commitopts3,
+    _('hg fold [OPTION]... [-r] REV'))
+def fold(ui, repo, *revs, **opts):
+    """fold multiple revisions into a single one
+
+    With --from, folds all the revisions linearly between the given revisions
+    and the parent of the working directory.
+
+    With --exact, folds only the specified revisions while ignoring the
+    parent of the working directory. In this case, the given revisions must
+    form a linear unbroken chain.
+
+    .. container:: verbose
+
+     Some examples:
+
+     - Fold the current revision with its parent::
+
+         hg fold --from .^
+
+     - Fold all draft revisions with working directory parent::
+
+         hg fold --from 'draft()'
+
+       See :hg:`help phases` for more about draft revisions and
+       :hg:`help revsets` for more about the `draft()` keyword
+
+     - Fold revisions between 3 and 6 with the working directory parent::
+
+         hg fold --from 3::6
+
+     - Fold revisions 3 and 4:
+
+        hg fold "3 + 4" --exact
+
+     - Only fold revisions linearly between foo and @::
+
+         hg fold foo::@ --exact
+    """
+    _resolveoptions(ui, opts)
+    revs = list(revs)
+    revs.extend(opts['rev'])
+    if not revs:
+        raise error.Abort(_('no revisions specified'))
+
+    revs = scmutil.revrange(repo, revs)
+
+    if opts['from'] and opts['exact']:
+        raise error.Abort(_('cannot use both --from and --exact'))
+    elif opts['from']:
+        # Try to extend given revision starting from the working directory
+        extrevs = repo.revs('(%ld::.) or (.::%ld)', revs, revs)
+        discardedrevs = [r for r in revs if r not in extrevs]
+        if discardedrevs:
+            msg = _("cannot fold non-linear revisions")
+            hint = _("given revisions are unrelated to parent of working"
+                     " directory")
+            raise error.Abort(msg, hint=hint)
+        revs = extrevs
+    elif opts['exact']:
+        # Nothing to do; "revs" is already set correctly
+        pass
+    else:
+        raise error.Abort(_('must specify either --from or --exact'))
+
+    if not revs:
+        raise error.Abort(_('specified revisions evaluate to an empty set'),
+                          hint=_('use different revision arguments'))
+    elif len(revs) == 1:
+        ui.write_err(_('single revision specified, nothing to fold\n'))
+        return 1
+
+    wlock = lock = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+
+        root, head = rewriteutil.foldcheck(repo, revs)
+
+        tr = repo.transaction('fold')
+        try:
+            commitopts = opts.copy()
+            allctx = [repo[r] for r in revs]
+            targetphase = max(c.phase() for c in allctx)
+
+            if commitopts.get('message') or commitopts.get('logfile'):
+                commitopts['edit'] = False
+            else:
+                msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
+                msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
+                         (c.rev(), c.description()) for c in allctx]
+                commitopts['message'] = "\n".join(msgs)
+                commitopts['edit'] = True
+
+            newid, unusedvariable = rewriteutil.rewrite(repo, root, allctx,
+                                                        head,
+                                                        [root.p1().node(),
+                                                         root.p2().node()],
+                                                        commitopts=commitopts)
+            phases.retractboundary(repo, tr, targetphase, [newid])
+            obsolete.createmarkers(repo, [(ctx, (repo[newid],))
+                                   for ctx in allctx])
+            tr.close()
+        finally:
+            tr.release()
+        ui.status('%i changesets folded\n' % len(revs))
+        if repo['.'].rev() in revs:
+            hg.update(repo, newid)
+    finally:
+        lockmod.release(lock, wlock)
+
+@eh.command(
+    '^metaedit',
+    [('r', 'rev', [], _("revision to edit")),
+     ('', 'fold', None, _("also fold specified revisions into one")),
+    ] + commitopts + commitopts2 + commitopts3,
+    _('hg metaedit [OPTION]... [-r] [REV]'))
+def metaedit(ui, repo, *revs, **opts):
+    """edit commit information
+
+    Edits the commit information for the specified revisions. By default, edits
+    commit information for the working directory parent.
+
+    With --fold, also folds multiple revisions into one if necessary. In this
+    case, the given revisions must form a linear unbroken chain.
+
+    .. container:: verbose
+
+     Some examples:
+
+     - Edit the commit message for the working directory parent::
+
+         hg metaedit
+
+     - Change the username for the working directory parent::
+
+         hg metaedit --user 'New User <new-email@example.com>'
+
+     - Combine all draft revisions that are ancestors of foo but not of @ into
+       one::
+
+         hg metaedit --fold 'draft() and only(foo,@)'
+
+       See :hg:`help phases` for more about draft revisions, and
+       :hg:`help revsets` for more about the `draft()` and `only()` keywords.
+    """
+    _resolveoptions(ui, opts)
+    revs = list(revs)
+    revs.extend(opts['rev'])
+    if not revs:
+        if opts['fold']:
+            raise error.Abort(_('revisions must be specified with --fold'))
+        revs = ['.']
+
+    wlock = lock = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+
+        revs = scmutil.revrange(repo, revs)
+        if not opts['fold'] and len(revs) > 1:
+            # TODO: handle multiple revisions. This is somewhat tricky because
+            # if we want to edit a series of commits:
+            #
+            #   a ---- b ---- c
+            #
+            # we need to rewrite a first, then directly rewrite b on top of the
+            # new a, then rewrite c on top of the new b. So we need to handle
+            # revisions in topological order.
+            raise error.Abort(_('editing multiple revisions without --fold is '
+                                'not currently supported'))
+
+        if opts['fold']:
+            root, head = rewriteutil.foldcheck(repo, revs)
+        else:
+            if repo.revs("%ld and public()", revs):
+                raise error.Abort(_('cannot edit commit information for public '
+                                    'revisions'))
+            newunstable = rewriteutil.disallowednewunstable(repo, revs)
+            if newunstable:
+                msg = _('cannot edit commit information in the middle'
+                        ' of a stack')
+                hint = _('%s will become unstable and new unstable changes'
+                         ' are not allowed')
+                hint %= repo[newunstable.first()]
+                raise error.Abort(msg, hint=hint)
+            root = head = repo[revs.first()]
+
+        wctx = repo[None]
+        p1 = wctx.p1()
+        tr = repo.transaction('metaedit')
+        newp1 = None
+        try:
+            commitopts = opts.copy()
+            allctx = [repo[r] for r in revs]
+            targetphase = max(c.phase() for c in allctx)
+
+            if commitopts.get('message') or commitopts.get('logfile'):
+                commitopts['edit'] = False
+            else:
+                if opts['fold']:
+                    msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
+                    msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
+                             (c.rev(), c.description()) for c in allctx]
+                else:
+                    msgs = [head.description()]
+                commitopts['message'] = "\n".join(msgs)
+                commitopts['edit'] = True
+
+            # TODO: if the author and message are the same, don't create a new
+            # hash. Right now we create a new hash because the date can be
+            # different.
+            newid, created = rewriteutil.rewrite(repo, root, allctx, head,
+                                                 [root.p1().node(),
+                                                  root.p2().node()],
+                                                 commitopts=commitopts)
+            if created:
+                if p1.rev() in revs:
+                    newp1 = newid
+                phases.retractboundary(repo, tr, targetphase, [newid])
+                obsolete.createmarkers(repo, [(ctx, (repo[newid],))
+                                              for ctx in allctx])
+            else:
+                ui.status(_("nothing changed\n"))
+            tr.close()
+        finally:
+            tr.release()
+
+        if opts['fold']:
+            ui.status('%i changesets folded\n' % len(revs))
+        if newp1 is not None:
+            hg.update(repo, newp1)
+    finally:
+        lockmod.release(lock, wlock)
+
+metadataopts = [
+    ('d', 'date', '',
+     _('record the specified date in metadata'), _('DATE')),
+    ('u', 'user', '',
+     _('record the specified user in metadata'), _('USER')),
+]
+
+def _getmetadata(**opts):
+    metadata = {}
+    date = opts.get('date')
+    user = opts.get('user')
+    if date:
+        metadata['date'] = '%i %i' % util.parsedate(date)
+    if user:
+        metadata['user'] = user
+    return metadata
+
+@eh.command(
+    '^prune|obsolete',
+    [('n', 'new', [], _("successor changeset (DEPRECATED)")),
+     ('s', 'succ', [], _("successor changeset")),
+     ('r', 'rev', [], _("revisions to prune")),
+     ('k', 'keep', None, _("does not modify working copy during prune")),
+     ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")),
+     ('', 'fold', False,
+      _("record a fold (multiple precursors, one successors)")),
+     ('', 'split', False,
+      _("record a split (on precursor, multiple successors)")),
+     ('B', 'bookmark', [], _("remove revs only reachable from given"
+                             " bookmark"))] + metadataopts,
+    _('[OPTION] [-r] REV...'))
+# XXX -U  --noupdate option to prevent wc update and or bookmarks update ?
+def cmdprune(ui, repo, *revs, **opts):
+    """hide changesets by marking them obsolete
+
+    Pruned changesets are obsolete with no successors. If they also have no
+    descendants, they are hidden (invisible to all commands).
+
+    Non-obsolete descendants of pruned changesets become "unstable". Use :hg:`evolve`
+    to handle this situation.
+
+    When you prune the parent of your working copy, Mercurial updates the working
+    copy to a non-obsolete parent.
+
+    You can use ``--succ`` to tell Mercurial that a newer version (successor) of the
+    pruned changeset exists. Mercurial records successor revisions in obsolescence
+    markers.
+
+    You can use the ``--biject`` option to specify a 1-1 mapping (bijection) between
+    revisions to pruned (precursor) and successor changesets. This option may be
+    removed in a future release (with the functionality provided automatically).
+
+    If you specify multiple revisions in ``--succ``, you are recording a "split" and
+    must acknowledge it by passing ``--split``. Similarly, when you prune multiple
+    changesets with a single successor, you must pass the ``--fold`` option.
+    """
+    revs = scmutil.revrange(repo, list(revs) + opts.get('rev'))
+    succs = opts['new'] + opts['succ']
+    bookmarks = set(opts.get('bookmark'))
+    metadata = _getmetadata(**opts)
+    biject = opts.get('biject')
+    fold = opts.get('fold')
+    split = opts.get('split')
+
+    options = [o for o in ('biject', 'fold', 'split') if opts.get(o)]
+    if 1 < len(options):
+        raise error.Abort(_("can only specify one of %s") % ', '.join(options))
+
+    if bookmarks:
+        reachablefrombookmark = rewriteutil.reachablefrombookmark
+        repomarks, revs = reachablefrombookmark(repo, revs, bookmarks)
+        if not revs:
+            # no revisions to prune - delete bookmark immediately
+            rewriteutil.deletebookmark(repo, repomarks, bookmarks)
+
+    if not revs:
+        raise error.Abort(_('nothing to prune'))
+
+    wlock = lock = tr = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        rewriteutil.precheck(repo, revs, 'touch')
+        tr = repo.transaction('prune')
+        # defines pruned changesets
+        precs = []
+        revs.sort()
+        for p in revs:
+            cp = repo[p]
+            precs.append(cp)
+        if not precs:
+            raise error.Abort('nothing to prune')
+
+        # defines successors changesets
+        sucs = scmutil.revrange(repo, succs)
+        sucs.sort()
+        sucs = tuple(repo[n] for n in sucs)
+        if not biject and len(sucs) > 1 and len(precs) > 1:
+            msg = "Can't use multiple successors for multiple precursors"
+            hint = _("use --biject to mark a series as a replacement"
+                     " for another")
+            raise error.Abort(msg, hint=hint)
+        elif biject and len(sucs) != len(precs):
+            msg = "Can't use %d successors for %d precursors" \
+                % (len(sucs), len(precs))
+            raise error.Abort(msg)
+        elif (len(precs) == 1 and len(sucs) > 1) and not split:
+            msg = "please add --split if you want to do a split"
+            raise error.Abort(msg)
+        elif len(sucs) == 1 and len(precs) > 1 and not fold:
+            msg = "please add --fold if you want to do a fold"
+            raise error.Abort(msg)
+        elif biject:
+            relations = [(p, (s,)) for p, s in zip(precs, sucs)]
+        else:
+            relations = [(p, sucs) for p in precs]
+
+        wdp = repo['.']
+
+        if len(sucs) == 1 and len(precs) == 1 and wdp in precs:
+            # '.' killed, so update to the successor
+            newnode = sucs[0]
+        else:
+            # update to an unkilled parent
+            newnode = wdp
+
+            while newnode in precs or newnode.obsolete():
+                newnode = newnode.parents()[0]
+
+        if newnode.node() != wdp.node():
+            if opts.get('keep', False):
+                # This is largely the same as the implementation in
+                # strip.stripcmd(). We might want to refactor this somewhere
+                # common at some point.
+
+                # only reset the dirstate for files that would actually change
+                # between the working context and uctx
+                descendantrevs = repo.revs("%d::." % newnode.rev())
+                changedfiles = []
+                for rev in descendantrevs:
+                    # blindly reset the files, regardless of what actually
+                    # changed
+                    changedfiles.extend(repo[rev].files())
+
+                # reset files that only changed in the dirstate too
+                dirstate = repo.dirstate
+                dirchanges = [f for f in dirstate if dirstate[f] != 'n']
+                changedfiles.extend(dirchanges)
+                repo.dirstate.rebuild(newnode.node(), newnode.manifest(),
+                                      changedfiles)
+                dirstate.write(tr)
+            else:
+                bookactive = repo._activebookmark
+                # Active bookmark that we don't want to delete (with -B option)
+                # we deactivate and move it before the update and reactivate it
+                # after
+                movebookmark = bookactive and not bookmarks
+                if movebookmark:
+                    bookmarksmod.deactivate(repo)
+                    bmchanges = [(bookactive, newnode.node())]
+                    compat.bookmarkapplychanges(repo, tr, bmchanges)
+                commands.update(ui, repo, newnode.rev())
+                ui.status(_('working directory now at %s\n')
+                          % ui.label(str(newnode), 'evolve.node'))
+                if movebookmark:
+                    bookmarksmod.activate(repo, bookactive)
+
+        # update bookmarks
+        if bookmarks:
+            rewriteutil.deletebookmark(repo, repomarks, bookmarks)
+
+        # create markers
+        obsolete.createmarkers(repo, relations, metadata=metadata)
+
+        # informs that changeset have been pruned
+        ui.status(_('%i changesets pruned\n') % len(precs))
+
+        for ctx in repo.unfiltered().set('bookmark() and %ld', precs):
+            # used to be:
+            #
+            #   ldest = list(repo.set('max((::%d) - obsolete())', ctx))
+            #   if ldest:
+            #      c = ldest[0]
+            #
+            # but then revset took a lazy arrow in the knee and became much
+            # slower. The new forms makes as much sense and a much faster.
+            for dest in ctx.ancestors():
+                if not dest.obsolete():
+                    bookmarksupdater = rewriteutil.bookmarksupdater
+                    updatebookmarks = bookmarksupdater(repo, ctx.node(), tr)
+                    updatebookmarks(dest.node())
+                    break
+
+        tr.close()
+    finally:
+        lockmod.release(tr, lock, wlock)
+
+@eh.command(
+    '^split',
+    [('r', 'rev', [], _("revision to split")),
+    ] + commitopts + commitopts2 + commitopts3,
+    _('hg split [OPTION]... [-r] REV'))
+def cmdsplit(ui, repo, *revs, **opts):
+    """split a changeset into smaller changesets
+
+    By default, split the current revision by prompting for all its hunks to be
+    redistributed into new changesets.
+
+    Use --rev to split a given changeset instead.
+    """
+    _resolveoptions(ui, opts)
+    tr = wlock = lock = None
+    newcommits = []
+
+    revarg = (list(revs) + opts.get('rev')) or ['.']
+    if len(revarg) != 1:
+        msg = _("more than one revset is given")
+        hnt = _("use either `hg split <rs>` or `hg split --rev <rs>`, not both")
+        raise error.Abort(msg, hint=hnt)
+
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        rev = scmutil.revsingle(repo, revarg[0])
+        cmdutil.bailifchanged(repo)
+        rewriteutil.precheck(repo, [rev], action='split')
+        tr = repo.transaction('split')
+        ctx = repo[rev]
+
+        if len(ctx.parents()) > 1:
+            raise error.Abort(_("cannot split merge commits"))
+        prev = ctx.p1()
+        bmupdate = rewriteutil.bookmarksupdater(repo, ctx.node(), tr)
+        bookactive = repo._activebookmark
+        if bookactive is not None:
+            repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark)
+        bookmarksmod.deactivate(repo)
+
+        # Prepare the working directory
+        rewriteutil.presplitupdate(repo, ui, prev, ctx)
+
+        def haschanges():
+            modified, added, removed, deleted = repo.status()[:4]
+            return modified or added or removed or deleted
+        msg = ("HG: This is the original pre-split commit message. "
+               "Edit it as appropriate.\n\n")
+        msg += ctx.description()
+        opts['message'] = msg
+        opts['edit'] = True
+        if not opts['user']:
+            opts['user'] = ctx.user()
+        while haschanges():
+            pats = ()
+            cmdutil.dorecord(ui, repo, commands.commit, 'commit', False,
+                             cmdutil.recordfilter, *pats, **opts)
+            # TODO: Does no seem like the best way to do this
+            # We should make dorecord return the newly created commit
+            newcommits.append(repo['.'])
+            if haschanges():
+                if ui.prompt('Done splitting? [yN]', default='n') == 'y':
+                    commands.commit(ui, repo, **opts)
+                    newcommits.append(repo['.'])
+                    break
+            else:
+                ui.status(_("no more change to split\n"))
+
+        if newcommits:
+            tip = repo[newcommits[-1]]
+            bmupdate(tip.node())
+            if bookactive is not None:
+                bookmarksmod.activate(repo, bookactive)
+            obsolete.createmarkers(repo, [(repo[rev], newcommits)])
+        tr.close()
+    finally:
+        lockmod.release(tr, lock, wlock)
+
+@eh.command(
+    '^touch',
+    [('r', 'rev', [], 'revision to update'),
+     ('D', 'duplicate', False,
+      'do not mark the new revision as successor of the old one'),
+     ('A', 'allowdivergence', False,
+      'mark the new revision as successor of the old one potentially creating '
+      'divergence')],
+    # allow to choose the seed ?
+    _('[-r] revs'))
+def touch(ui, repo, *revs, **opts):
+    """create successors that are identical to their predecessors except
+    for the changeset ID
+
+    This is used to "resurrect" changesets
+    """
+    duplicate = opts['duplicate']
+    allowdivergence = opts['allowdivergence']
+    revs = list(revs)
+    revs.extend(opts['rev'])
+    if not revs:
+        revs = ['.']
+    revs = scmutil.revrange(repo, revs)
+    if not revs:
+        ui.write_err('no revision to touch\n')
+        return 1
+    if not duplicate:
+        rewriteutil.precheck(repo, revs, touch)
+    tmpl = utility.shorttemplate
+    displayer = cmdutil.show_changeset(ui, repo, {'template': tmpl})
+    wlock = lock = tr = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        tr = repo.transaction('touch')
+        revs.sort() # ensure parent are run first
+        newmapping = {}
+        for r in revs:
+            ctx = repo[r]
+            extra = ctx.extra().copy()
+            extra['__touch-noise__'] = random.randint(0, 0xffffffff)
+            # search for touched parent
+            p1 = ctx.p1().node()
+            p2 = ctx.p2().node()
+            p1 = newmapping.get(p1, p1)
+            p2 = newmapping.get(p2, p2)
+
+            if not (duplicate or allowdivergence):
+                # The user hasn't yet decided what to do with the revived
+                # cset, let's ask
+                sset = compat.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
+
+            extradict = {'extra': extra}
+            new, unusedvariable = rewriteutil.rewrite(repo, ctx, [], ctx,
+                                                      [p1, p2],
+                                                      commitopts=extradict)
+            # store touched version to help potential children
+            newmapping[ctx.node()] = new
+
+            if not duplicate:
+                obsolete.createmarkers(repo, [(ctx, (repo[new],))])
+            phases.retractboundary(repo, tr, ctx.phase(), [new])
+            if ctx in repo[None].parents():
+                with repo.dirstate.parentchange():
+                    repo.dirstate.setparents(new, node.nullid)
+        tr.close()
+    finally:
+        lockmod.release(tr, lock, wlock)
--- a/hgext3rd/evolve/compat.py	Sun Jul 02 18:22:39 2017 +0200
+++ b/hgext3rd/evolve/compat.py	Tue Jul 25 03:59:22 2017 +0200
@@ -8,7 +8,8 @@
 
 from mercurial import (
     hg,
-    obsolete
+    obsolete,
+    util,
 )
 
 try:
@@ -23,7 +24,7 @@
 
 eh = exthelper.exthelper()
 
-if not hasattr(hg, '_copycache'):
+if not util.safehasattr(hg, '_copycache'):
     # exact copy of relevantmarkers as in Mercurial-176d1a0ce385
     # this fixes relevant markers computation for version < hg-4.3
     @eh.wrapfunction(obsolete.obsstore, 'relevantmarkers')
@@ -75,3 +76,17 @@
     if func is None:
         func = obsolete.allprecursors
     return func(*args, **kwargs)
+
+# compatibility layer for mercurial < 4.3
+def bookmarkapplychanges(repo, tr, changes):
+    """Apply a list of changes to bookmarks
+    """
+    bookmarks = repo._bookmarks
+    if util.safehasattr(bookmarks, 'applychanges'):
+        return bookmarks.applychanges(repo, tr, changes)
+    for name, node in changes:
+        if node is None:
+            del bookmarks[name]
+        else:
+            bookmarks[name] = node
+    bookmarks.recordchange(tr)
--- a/hgext3rd/evolve/hack/inhibit.py	Sun Jul 02 18:22:39 2017 +0200
+++ b/hgext3rd/evolve/hack/inhibit.py	Tue Jul 25 03:59:22 2017 +0200
@@ -106,7 +106,7 @@
         'keep': None,
         'biject': False,
     }
-    evolve.cmdprune(ui, repo, **optsdict)
+    evolve.cmdrewrite.cmdprune(ui, repo, **optsdict)
 
 # obsolescence inhibitor
 ########################
--- a/hgext3rd/evolve/metadata.py	Sun Jul 02 18:22:39 2017 +0200
+++ b/hgext3rd/evolve/metadata.py	Tue Jul 25 03:59:22 2017 +0200
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-__version__ = '6.5.0.dev'
-testedwith = '3.8.4 3.9.2 4.0.2 4.1.2 4.2'
+__version__ = '6.6.0.dev'
+testedwith = '3.8.4 3.9.2 4.0.2 4.1.3 4.2.1'
 minimumhgversion = '3.8'
 buglink = 'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obscache.py	Sun Jul 02 18:22:39 2017 +0200
+++ b/hgext3rd/evolve/obscache.py	Tue Jul 25 03:59:22 2017 +0200
@@ -42,6 +42,12 @@
 else:
     timer = time.time
 
+# hg < 4.2 compat
+try:
+    from mercurial import vfs as vfsmod
+    vfsmod.vfs
+except ImportError:
+    from mercurial import scmutil as vfsmod
 
 try:
     obsstorefilecache = localrepo.localrepository.obsstore
@@ -320,6 +326,12 @@
 
         return reset, revs, markers, (obssize, obskey)
 
+def getcachevfs(repo):
+    cachevfs = getattr(repo, 'cachevfs', None)
+    if cachevfs is None:
+        cachevfs = vfsmod.vfs(repo.vfs.join('cache'))
+        cachevfs.createmode = repo.store.createmode
+    return cachevfs
 
 class obscache(dualsourcecache):
     """cache the "does a rev" is the precursors of some obsmarkers data
@@ -355,7 +367,7 @@
         zero. That would be especially useful for the '.pending' overlay.
     """
 
-    _filepath = 'cache/evoext-obscache-00'
+    _filepath = 'evoext-obscache-00'
     _headerformat = '>q20sQQ20s'
 
     _cachename = 'evo-ext-obscache' # used for error message
@@ -363,8 +375,7 @@
     def __init__(self, repo):
         super(obscache, self).__init__()
         self._ondiskkey = None
-        self._vfs = repo.vfs
-        self._setdata(bytearray())
+        self._vfs = getcachevfs(repo)
 
     @util.propertycache
     def get(self):
@@ -445,7 +456,7 @@
         if self._cachekey is None or self._cachekey == self._ondiskkey:
             return
 
-        cachefile = repo.vfs(self._filepath, 'w', atomictemp=True)
+        cachefile = self._vfs(self._filepath, 'w', atomictemp=True)
         headerdata = struct.pack(self._headerformat, *self._cachekey)
         cachefile.write(headerdata)
         cachefile.write(self._data)
@@ -455,7 +466,7 @@
         """load data from disk"""
         assert repo.filtername is None
 
-        data = repo.vfs.tryread(self._filepath)
+        data = self._vfs.tryread(self._filepath)
         if not data:
             self._cachekey = self.emptykey
             self._setdata(bytearray())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/rewriteutil.py	Tue Jul 25 03:59:22 2017 +0200
@@ -0,0 +1,247 @@
+# Module dedicated to host utility code dedicated to changeset rewrite
+#
+# Copyright 2017 Octobus <contact@octobus.net>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+# Status: Stabilization of the API in progress
+#
+#   The content of this module should move into core incrementally once we are
+#   happy one piece of it (and hopefully, able to reuse it in other core
+#   commands).
+
+from mercurial import (
+    cmdutil,
+    commands,
+    context,
+    copies,
+    error,
+    hg,
+    lock as lockmod,
+    node,
+    obsolete,
+    phases,
+    repair,
+    revset,
+    util,
+)
+
+from mercurial.i18n import _
+
+from . import (
+    compat,
+)
+
+def _formatrevs(repo, revs, maxrevs=4):
+    """return a string summarising revision in a descent size
+
+    If there is few enough revision, we list them otherwise we display a
+    summary in the form:
+
+        1ea73414a91b and 5 others
+    """
+    tonode = repo.changelog.node
+    numrevs = len(revs)
+    if numrevs < maxrevs:
+        shorts = [node.short(tonode(r)) for r in revs]
+        summary = ', '.join(shorts)
+    else:
+        if util.safehasattr(revs, 'first'):
+            first = revs.first()
+        else:
+            first = revs[0]
+        summary = _('%s and %d others')
+        summary %= (node.short(tonode(first)), numrevs - 1)
+    return summary
+
+def precheck(repo, revs, action='rewrite'):
+    """check if <revs> can be rewritten
+
+    <action> can be used to control the commit message.
+    """
+    if node.nullrev in revs:
+        msg = _("cannot %s the null revision") % (action)
+        hint = _("no changeset checked out")
+        raise error.Abort(msg, hint=hint)
+    publicrevs = repo.revs('%ld and public()', revs)
+    if publicrevs:
+        summary = _formatrevs(repo, publicrevs)
+        msg = _("cannot %s public changesets: %s") % (action, summary)
+        hint = _("see 'hg help phases' for details")
+        raise error.Abort(msg, hint=hint)
+    newunstable = disallowednewunstable(repo, revs)
+    if newunstable:
+        msg = _("%s will orphan %i descendants")
+        msg %= (action, len(newunstable))
+        hint = _("see 'hg help evolution.instability'")
+        raise error.Abort(msg, hint=hint)
+
+def bookmarksupdater(repo, oldid, tr):
+    """Return a callable update(newid) updating the current bookmark
+    and bookmarks bound to oldid to newid.
+    """
+    def updatebookmarks(newid):
+        oldbookmarks = repo.nodebookmarks(oldid)
+        bmchanges = [(b, newid) for b in oldbookmarks]
+        if bmchanges:
+            compat.bookmarkapplychanges(repo, tr, bmchanges)
+    return updatebookmarks
+
+def disallowednewunstable(repo, revs):
+    """Check that editing <revs> will not create disallowed unstable
+
+    (unstable creation is controled by some special config).
+    """
+    allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
+    if allowunstable:
+        return revset.baseset()
+    return repo.revs("(%ld::) - %ld", revs, revs)
+
+def foldcheck(repo, revs):
+    """check that <revs> can be folded"""
+    precheck(repo, revs, action='fold')
+    roots = repo.revs('roots(%ld)', revs)
+    if len(roots) > 1:
+        raise error.Abort(_("cannot fold non-linear revisions "
+                            "(multiple roots given)"))
+    root = repo[roots.first()]
+    if root.phase() <= phases.public:
+        raise error.Abort(_("cannot fold public revisions"))
+    heads = repo.revs('heads(%ld)', revs)
+    if len(heads) > 1:
+        raise error.Abort(_("cannot fold non-linear revisions "
+                            "(multiple heads given)"))
+    head = repo[heads.first()]
+    return root, head
+
+def deletebookmark(repo, repomarks, bookmarks):
+    wlock = lock = tr = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        tr = repo.transaction('prune')
+        bmchanges = []
+        for bookmark in bookmarks:
+            bmchanges.append((bookmark, None))
+        compat.bookmarkapplychanges(repo, tr, bmchanges)
+        tr.close()
+        for bookmark in sorted(bookmarks):
+            repo.ui.write(_("bookmark '%s' deleted\n") % bookmark)
+    finally:
+        lockmod.release(tr, lock, wlock)
+
+def presplitupdate(repo, ui, prev, ctx):
+    """prepare the working directory for a split (for topic hooking)
+    """
+    hg.update(repo, prev)
+    commands.revert(ui, repo, rev=ctx.rev(), all=True)
+
+def reachablefrombookmark(repo, revs, bookmarks):
+    """filter revisions and bookmarks reachable from the given bookmark
+    yoinked from mq.py
+    """
+    repomarks = repo._bookmarks
+    if not bookmarks.issubset(repomarks):
+        raise error.Abort(_("bookmark '%s' not found") %
+                          ','.join(sorted(bookmarks - set(repomarks.keys()))))
+
+    # If the requested bookmark is not the only one pointing to a
+    # a revision we have to only delete the bookmark and not strip
+    # anything. revsets cannot detect that case.
+    nodetobookmarks = {}
+    for mark, bnode in repomarks.iteritems():
+        nodetobookmarks.setdefault(bnode, []).append(mark)
+    for marks in nodetobookmarks.values():
+        if bookmarks.issuperset(marks):
+            rsrevs = repair.stripbmrevset(repo, marks[0])
+            revs = set(revs)
+            revs.update(set(rsrevs))
+            revs = sorted(revs)
+    return repomarks, revs
+
+def rewrite(repo, old, updates, head, newbases, commitopts):
+    """Return (nodeid, created) where nodeid is the identifier of the
+    changeset generated by the rewrite process, and created is True if
+    nodeid was actually created. If created is False, nodeid
+    references a changeset existing before the rewrite call.
+    """
+    wlock = lock = tr = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        tr = repo.transaction('rewrite')
+        if len(old.parents()) > 1: # XXX remove this unnecessary limitation.
+            raise error.Abort(_('cannot amend merge changesets'))
+        base = old.p1()
+        updatebookmarks = bookmarksupdater(repo, old.node(), tr)
+
+        # commit a new version of the old changeset, including the update
+        # collect all files which might be affected
+        files = set(old.files())
+        for u in updates:
+            files.update(u.files())
+
+        # Recompute copies (avoid recording a -> b -> a)
+        copied = copies.pathcopies(base, head)
+
+        # prune files which were reverted by the updates
+        def samefile(f):
+            if f in head.manifest():
+                a = head.filectx(f)
+                if f in base.manifest():
+                    b = base.filectx(f)
+                    return (a.data() == b.data()
+                            and a.flags() == b.flags())
+                else:
+                    return False
+            else:
+                return f not in base.manifest()
+        files = [f for f in files if not samefile(f)]
+        # commit version of these files as defined by head
+        headmf = head.manifest()
+
+        def filectxfn(repo, ctx, path):
+            if path in headmf:
+                fctx = head[path]
+                flags = fctx.flags()
+                mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
+                                          islink='l' in flags,
+                                          isexec='x' in flags,
+                                          copied=copied.get(path))
+                return mctx
+            return None
+
+        message = cmdutil.logmessage(repo.ui, commitopts)
+        if not message:
+            message = old.description()
+
+        user = commitopts.get('user') or old.user()
+        # TODO: In case not date is given, we should take the old commit date
+        # if we are working one one changeset or mimic the fold behavior about
+        # date
+        date = commitopts.get('date') or None
+        extra = dict(commitopts.get('extra', old.extra()))
+        extra['branch'] = head.branch()
+
+        new = context.memctx(repo,
+                             parents=newbases,
+                             text=message,
+                             files=files,
+                             filectxfn=filectxfn,
+                             user=user,
+                             date=date,
+                             extra=extra)
+
+        if commitopts.get('edit'):
+            new._text = cmdutil.commitforceeditor(repo, new, [])
+        revcount = len(repo)
+        newid = repo.commitctx(new)
+        new = repo[newid]
+        created = len(repo) != revcount
+        updatebookmarks(newid)
+
+        tr.close()
+        return newid, created
+    finally:
+        lockmod.release(tr, lock, wlock)
--- a/hgext3rd/evolve/utility.py	Sun Jul 02 18:22:39 2017 +0200
+++ b/hgext3rd/evolve/utility.py	Tue Jul 25 03:59:22 2017 +0200
@@ -5,6 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+shorttemplate = "[{label('evolve.rev', rev)}] {desc|firstline}\n"
+
 def obsexcmsg(ui, message, important=False):
     verbose = ui.configbool('experimental', 'verbose-obsolescence-exchange',
                             False)
--- a/hgext3rd/topic/__init__.py	Sun Jul 02 18:22:39 2017 +0200
+++ b/hgext3rd/topic/__init__.py	Tue Jul 25 03:59:22 2017 +0200
@@ -53,6 +53,7 @@
 from __future__ import absolute_import
 
 import re
+import time
 
 from mercurial.i18n import _
 from mercurial import (
@@ -68,9 +69,11 @@
     namespaces,
     node,
     obsolete,
+    obsutil,
     patch,
     phases,
     registrar,
+    templatefilters,
     util,
 )
 
@@ -98,11 +101,13 @@
               'topic.stack.index': 'yellow',
               'topic.stack.index.base': 'none dim',
               'topic.stack.desc.base': 'none dim',
+              'topic.stack.shortnode.base': 'none dim',
               'topic.stack.state.base': 'dim',
               'topic.stack.state.clean': 'green',
               'topic.stack.index.current': 'cyan',       # random pick
               'topic.stack.state.current': 'cyan bold',  # random pick
               'topic.stack.desc.current': 'cyan',        # random pick
+              'topic.stack.shortnode.current': 'cyan',   # random pick
               'topic.stack.state.unstable': 'red',
               'topic.stack.summary.behindcount': 'cyan',
               'topic.stack.summary.behinderror': 'red',
@@ -113,13 +118,29 @@
               'topic.active': 'green',
              }
 
-testedwith = '4.0.2 4.1.3 4.2'
+version = '0.2.0.dev'
+testedwith = '4.0.2 4.1.3 4.2.1'
+minimumhgversion = '4.0'
+buglink = 'https://bz.mercurial-scm.org/'
 
 def _contexttopic(self, force=False):
     if not (force or self.mutable()):
         return ''
     return self.extra().get(constants.extrakey, '')
 context.basectx.topic = _contexttopic
+def _contexttopicidx(self):
+    topic = self.topic()
+    if not topic:
+        # XXX we might want to include t0 here,
+        # however t0 is related to  'currenttopic' which has no place here.
+        return None
+    revlist = stack.getstack(self._repo, topic=topic)
+    try:
+        return revlist.index(self.rev())
+    except IndexError:
+        # Lets move to the last ctx of the current topic
+        return None
+context.basectx.topicidx = _contexttopicidx
 
 topicrev = re.compile(r'^t\d+$')
 branchrev = re.compile(r'^b\d+$')
@@ -141,10 +162,14 @@
 
     if revs is not None:
         try:
-            r = revs[idx - 1]
+            r = revs[idx]
         except IndexError:
             msg = _('cannot resolve "%s": %s "%s" has only %d changesets')
-            raise error.Abort(msg % (name, ttype, tname, len(revs)))
+            raise error.Abort(msg % (name, ttype, tname, len(revs) - 1))
+        # b0 or t0 can be None
+        if r == -1 and idx == 0:
+            msg = _('the %s "%s" has no %s')
+            raise error.Abort(msg % (ttype, tname, name))
         return [repo[r].node()]
     if name not in repo.topics:
         return []
@@ -173,10 +198,16 @@
 
     extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap)
     extensions.wrapfunction(merge, 'update', mergeupdatewrap)
+    # We need to check whether t0 or b0 is passed to override the default update
+    # behaviour of changing topic and I can't find a better way
+    # to do that as scmutil.revsingle returns the rev number and hence we can't
+    # plug into logic for this into mergemod.update().
+    extensions.wrapcommand(commands.table, 'update', checkt0)
 
     try:
         evolve = extensions.find('evolve')
-        extensions.wrapfunction(evolve, "presplitupdate", presplitupdatetopic)
+        extensions.wrapfunction(evolve.rewriteutil, "presplitupdate",
+                                presplitupdatetopic)
     except (KeyError, AttributeError):
         pass
 
@@ -280,9 +311,30 @@
         ('', 'clear', False, 'clear active topic if any'),
         ('r', 'rev', '', 'revset of existing revisions', _('REV')),
         ('l', 'list', False, 'show the stack of changeset in the topic'),
+        ('', 'age', False, 'show when you last touched the topics')
     ] + commands.formatteropts)
 def topics(ui, repo, topic='', clear=False, rev=None, list=False, **opts):
-    """View current topic, set current topic, or see all topics.
+    """View current topic, set current topic, change topic for a set of revisions, or see all topics.
+
+    Clear topic on existing topiced revisions:
+        `hg topic --rev <related revset> --clear`
+
+    Change topic on some revisions:
+        `hg topic <newtopicname> --rev <related revset>`
+
+    Clear current topic:
+        `hg topic --clear`
+
+    Set current topic:
+        `hg topic <topicname>`
+
+    List of topics:
+        `hg topics`
+
+    List of topics with their last touched time sorted according to it:
+        `hg topic --age`
+
+    The active topic (if any) will be prepended with a "*".
 
     The --verbose version of this command display various information on the state of each topic."""
     if list:
@@ -317,7 +369,10 @@
 def cmdstack(ui, repo, topic='', **opts):
     """list all changesets in a topic and other information
 
-    List the current topic by default."""
+    List the current topic by default.
+
+    The --verbose version shows short nodes for the commits also.
+    """
     if not topic:
         topic = None
     branch = None
@@ -408,6 +463,11 @@
 
 def _listtopics(ui, repo, opts):
     fm = ui.formatter('topics', opts)
+    showlast = opts.get('age')
+    if showlast:
+        # we have a new function as plugging logic into existing function is
+        # pretty much difficult
+        return _showlasttouched(repo, fm, opts)
     activetopic = repo.currenttopic
     namemask = '%s'
     if repo.topics and ui.verbose:
@@ -460,6 +520,76 @@
         fm.plain('\n')
     fm.end()
 
+def _showlasttouched(repo, fm, opts):
+    topics = repo.topics
+    timedict = _getlasttouched(repo, topics)
+    times = timedict.keys()
+    times.sort()
+    if topics:
+        maxwidth = max(len(t) for t in topics)
+        namemask = '%%-%is' % maxwidth
+    activetopic = repo.currenttopic
+    for timevalue in times:
+        curtopics = timedict[timevalue][1]
+        for topic in curtopics:
+            fm.startitem()
+            marker = ' '
+            label = 'topic'
+            active = (topic == activetopic)
+            if active:
+                marker = '*'
+                label = 'topic.active'
+            fm.plain(' %s ' % marker, label=label)
+            fm.write('topic', namemask, topic, label=label)
+            fm.data(active=active)
+            fm.plain(' (')
+            if timevalue == -1:
+                timestr = 'not yet touched'
+            else:
+                timestr = templatefilters.age(timedict[timevalue][0])
+            fm.write('lasttouched', '%s', timestr, label='topic.list.time')
+            fm.plain(')')
+            fm.plain('\n')
+    fm.end()
+
+def _getlasttouched(repo, topics):
+    """
+    Calculates the last time a topic was used. Returns a dictionary of seconds
+    passed from current time for a topic as keys and topic name as values.
+    """
+    topicstime = {}
+    curtime = time.time()
+    for t in topics:
+        maxtime = (0, 0)
+        trevs = repo.revs("topic(%s)", t)
+        # Need to check for the time of all changesets in the topic, whether
+        # they are obsolete of non-heads
+        # XXX: can we just rely on the max rev number for this
+        for revs in trevs:
+            rt = repo[revs].date()
+            if rt[0] > maxtime[0]:
+                # Can store the rev to gather more info
+                # latesthead = revs
+                maxtime = rt
+            # looking on the markers also to get more information and accurate
+            # last touch time.
+            obsmarkers = obsutil.getmarkers(repo, [repo[revs].node()])
+            for marker in obsmarkers:
+                rt = marker.date()
+                if rt[0] > maxtime[0]:
+                    maxtime = rt
+        # is the topic still yet untouched
+        if not trevs:
+            secspassed = -1
+        else:
+            secspassed = (curtime - maxtime[0])
+        try:
+            topicstime[secspassed][1].append(t)
+        except KeyError:
+            topicstime[secspassed] = (maxtime, [t])
+
+    return topicstime
+
 def summaryhook(ui, repo):
     t = repo.currenttopic
     if not t:
@@ -469,10 +599,16 @@
 
 def commitwrap(orig, ui, repo, *args, **opts):
     with repo.wlock():
+        enforcetopic = ui.configbool('experimental', 'enforce-topic')
         if opts.get('topic'):
             t = opts['topic']
             with repo.vfs.open('topic', 'w') as f:
                 f.write(t)
+        elif not repo.currenttopic and enforcetopic:
+            msg = _("no active topic")
+            hint = _("set a current topic or use '--config " +
+                     "experimental.enforce-topic=no' to commit without a topic")
+            raise error.Abort(msg, hint=hint)
         return orig(ui, repo, *args, **opts)
 
 def committextwrap(orig, repo, ctx, subs, extramsg):
@@ -488,6 +624,7 @@
     partial = not (matcher is None or matcher.always())
     wlock = repo.wlock()
     isrebase = False
+    ist0 = False
     try:
         ret = orig(repo, node, branchmerge, force, *args, **kwargs)
         # The mergeupdatewrap function makes the destination's topic as the
@@ -497,7 +634,9 @@
         # running.
         if repo.ui.hasconfig('experimental', 'topicrebase'):
             isrebase = True
-        if (not partial and not branchmerge) or isrebase:
+        if repo.ui.configbool('_internal', 'keep-topic'):
+            ist0 = True
+        if ((not partial and not branchmerge) or isrebase) and not ist0:
             ot = repo.currenttopic
             t = ''
             pctx = repo[node]
@@ -507,10 +646,25 @@
                 f.write(t)
             if t and t != ot:
                 repo.ui.status(_("switching to topic %s\n") % t)
+        elif ist0:
+            repo.ui.status(_("preserving the current topic '%s'\n") %
+                           repo.currenttopic)
         return ret
     finally:
         wlock.release()
 
+def checkt0(orig, ui, repo, node=None, rev=None, *args, **kwargs):
+
+    thezeros = set(['t0', 'b0'])
+    backup = repo.ui.backupconfig('_internal', 'keep-topic')
+    try:
+        if node in thezeros or rev in thezeros:
+            repo.ui.setconfig('_internal', 'keep-topic', 'yes',
+                              source='topic-extension')
+        return orig(ui, repo, node, rev, *args, **kwargs)
+    finally:
+        repo.ui.restoreconfig(backup)
+
 def _fixrebase(loaded):
     if not loaded:
         return
--- a/hgext3rd/topic/revset.py	Sun Jul 02 18:22:39 2017 +0200
+++ b/hgext3rd/topic/revset.py	Tue Jul 25 03:59:22 2017 +0200
@@ -78,7 +78,7 @@
         topic = repo.currenttopic
     if not topic:
         branch = repo[None].branch()
-    return revset.baseset(stack.getstack(repo, branch=branch, topic=topic)) & subset
+    return revset.baseset(stack.getstack(repo, branch=branch, topic=topic)[1:]) & subset
 
 
 def modsetup(ui):
--- a/hgext3rd/topic/stack.py	Sun Jul 02 18:22:39 2017 +0200
+++ b/hgext3rd/topic/stack.py	Tue Jul 25 03:59:22 2017 +0200
@@ -10,6 +10,8 @@
 )
 from .evolvebits import builddependencies, _orderrevs, _singlesuccessor
 
+short = node.short
+
 def getstack(repo, branch=None, topic=None):
     # XXX need sorting
     if topic is not None and branch is not None:
@@ -20,7 +22,13 @@
         trevs = repo.revs("branch(%s) - public() - obsolete() - topic()", branch)
     else:
         raise error.ProgrammingError('neither branch and topic specified (not defined yet)')
-    return _orderrevs(repo, trevs)
+    revs = _orderrevs(repo, trevs)
+    if revs:
+        pt1 = repo[revs[0]].p1()
+        if pt1.obsolete():
+            pt1 = repo[_singlesuccessor(repo, pt1)]
+        revs.insert(0, pt1.rev())
+    return revs
 
 def labelsgen(prefix, labelssuffix):
     """ Takes a label prefix and a list of suffixes. Returns a string of the prefix
@@ -84,8 +92,16 @@
             fm.plain('%d behind' % data['behindcount'], label='topic.stack.summary.behindcount')
     fm.plain('\n')
 
-    for idx, r in enumerate(getstack(repo, branch=branch, topic=topic), 1):
+    for idx, r in enumerate(getstack(repo, branch=branch, topic=topic), 0):
         ctx = repo[r]
+        # special case for t0, b0 as it's hard to plugin into rest of the logic
+        if idx == 0:
+            # t0, b0 can be None
+            if r == -1:
+                continue
+            entries.append((idx, False, ctx))
+            prev = ctx.rev()
+            continue
         p1 = ctx.p1()
         if p1.obsolete():
             p1 = repo[_singlesuccessor(repo, p1)]
@@ -125,9 +141,14 @@
 
         if idx is None:
             fm.plain('  ')
+            if ui.verbose:
+                fm.plain('              ')
         else:
             fm.write('topic.stack.index', '%s%%d' % prefix, idx,
                      label='topic.stack.index ' + labelsgen('topic.stack.index.%s', states))
+            if ui.verbose:
+                fm.write('topic.stack.shortnode', '(%s)', short(ctx.node()),
+                         label='topic.stack.shortnode ' + labelsgen('topic.stack.shortnode.%s', states))
         fm.write('topic.stack.state.symbol', '%s', symbol,
                  label='topic.stack.state ' + labelsgen('topic.stack.state.%s', states))
         fm.plain(' ')
@@ -148,7 +169,7 @@
     :behindcount: number of changeset on rebase destination
     """
     data = {}
-    revs = getstack(repo, branch, topic)
+    revs = getstack(repo, branch, topic)[1:]
     data['changesetcount'] = len(revs)
     data['troubledcount'] = len([r for r in revs if repo[r].troubled()])
     deps, rdeps = builddependencies(repo, revs)
--- a/tests/test-amend.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-amend.t	Tue Jul 25 03:59:22 2017 +0200
@@ -1,11 +1,10 @@
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
   $ glog() {
-  >   hg glog --template '{rev}@{branch}({phase}) {desc|firstline}\n' "$@"
+  >   hg log -G --template '{rev}@{branch}({phase}) {desc|firstline}\n' "$@"
   > }
 
   $ hg init repo --traceback
@@ -132,11 +131,9 @@
   
       If you don't specify -m, the parent's message will be reused.
   
-      Behind the scenes, Mercurial first commits the update as a regular child
-      of the current parent. Then it creates a new commit on the parent's
-      parents with the updated contents. Then it changes the working copy parent
-      to this new combined changeset. Finally, the old changeset and its update
-      are hidden from 'hg log' (unless you use --hidden with log).
+      If --extra is specified, the behavior of 'hg amend' is reversed: Changes
+      to selected files in the checked out revision appear again as uncommitted
+      changed in the working directory.
   
       Returns 0 on success, 1 if nothing changed.
   
@@ -144,7 +141,9 @@
   
    -A --addremove           mark new/missing files as added/removed before
                             committing
+   -a --all                 match all files
    -e --edit                invoke editor on commit messages
+      --extract             extract changes from the commit to the working copy
       --close-branch        mark a branch as closed, hiding it from the branch
                             list
    -s --secret              use the secret phase for committing
--- a/tests/test-corrupt.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-corrupt.t	Tue Jul 25 03:59:22 2017 +0200
@@ -13,7 +13,6 @@
   > git = 1
   > unified = 0
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
   $ mkcommit() {
@@ -30,7 +29,7 @@
   $ mkcommit A
   $ mkcommit B
   $ mkcommit C
-  $ hg glog
+  $ hg log -G
   @  changeset:   2:829b19580856
   |  tag:         tip
   |  user:        test
@@ -67,7 +66,7 @@
   $ hg up -q .^^
   $ hg revert -r tip -a -q
   $ hg ci -m 'coin' -q
-  $ hg glog
+  $ hg log -G
   @  changeset:   5:8313a6afebbb
   |  tag:         tip
   |  parent:      2:829b19580856
--- a/tests/test-divergent.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-divergent.t	Tue Jul 25 03:59:22 2017 +0200
@@ -15,7 +15,6 @@
   > [ui]
   > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline} [{troubles}]\n
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
   $ mkcommit() {
--- a/tests/test-evolve-order.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-evolve-order.t	Tue Jul 25 03:59:22 2017 +0200
@@ -16,7 +16,6 @@
   > [ui]
   > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
   $ mkcommit() {
--- a/tests/test-evolve-split.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-evolve-split.t	Tue Jul 25 03:59:22 2017 +0200
@@ -15,7 +15,6 @@
   > [ui]
   > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
   $ mkcommit() {
--- a/tests/test-evolve-topic.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-evolve-topic.t	Tue Jul 25 03:59:22 2017 +0200
@@ -78,7 +78,7 @@
   t3: add eee
   t2: add ddd
   t1: add ccc
-    ^ add bbb
+  t0^ add bbb (base)
   $ hg up 'desc(ddd)'
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ echo ddd >> ddd
@@ -209,9 +209,14 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   [16] add ggg
   $ hg prev
+  preserving the current topic 'bar'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [15] add fff
+  $ hg prev
   no parent in topic "bar"
   (do you want --no-topic)
+  [1]
   $ hg prev --no-topic
   switching to topic foo
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  [15] add fff
+  [14] add eee
--- a/tests/test-evolve.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-evolve.t	Tue Jul 25 03:59:22 2017 +0200
@@ -14,7 +14,6 @@
   > git = 1
   > unified = 0
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
   $ mkcommit() {
@@ -31,7 +30,7 @@
   > }
 
   $ glog() {
-  >   hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
+  >   hg log -G --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
   > }
 
   $ shaof() {
@@ -81,6 +80,39 @@
       Obsolescence markers will be exchanged between repositories that
       explicitly assert support for the obsolescence feature (this can currently
       only be done via an extension).
+  
+      Instability ==========
+  
+      (note: the vocabulary is in the process of being updated)
+  
+      Rewriting changesets might introduce instability (currently 'trouble').
+  
+      There are two main kinds of instability: orphaning and diverging.
+  
+      Orphans are changesets left behind when their ancestors are rewritten,
+      (currently: 'unstable'). Divergence has two variants:
+  
+      * Content-divergence occurs when independent rewrites of the same changesets
+        lead to different results. (currently: 'divergent')
+  
+      * Phase-divergence occurs when the old (obsolete) version of a changeset
+        becomes public. (currently: 'bumped')
+  
+      If it possible to prevent local creation of orphans by using the following
+      config:
+  
+        [experimental]
+        evolution=createmarkers,allnewcommands,exchange
+  
+      You can also enable that option explicitly:
+  
+        [experimental]
+        evolution=createmarkers,allnewcommands,allowunstable,exchange
+  
+      or simply:
+  
+        [experimental]
+        evolution=all
 
 various init
 
@@ -114,7 +146,7 @@
   $ hg log -r 1 --template '{rev} {phase} {obsolete}\n'
   1 public 
   $ hg prune 1
-  abort: cannot prune immutable changeset: 7c3bad9141dc
+  abort: cannot touch public changesets: 7c3bad9141dc
   (see 'hg help phases' for details)
   [255]
   $ hg log -r 1 --template '{rev} {phase} {obsolete}\n'
@@ -372,7 +404,7 @@
 
 all solving bumped troubled
 
-  $ hg glog
+  $ hg log -G
   @  8	feature-B: another feature that rox - test
   |
   | o  7	: another feature (child of ba0ec09b1bab) - test
@@ -387,7 +419,7 @@
   computing new diff
   committed as 6707c5e1c49d
   working directory is now at 6707c5e1c49d
-  $ hg glog
+  $ hg log -G
   @  9	feature-B: bumped update to 99833d22b0c6: - test
   |
   o  7	: another feature (child of ba0ec09b1bab) - test
@@ -446,7 +478,7 @@
   atop:[14] dansk 2!
   merging main-file-1
   working directory is now at 68557e4f0048
-  $ hg glog
+  $ hg log -G
   @  15	: dansk 3! - test
   |
   o  14	: dansk 2! - test
@@ -683,48 +715,13 @@
   
 
 Test fold
+(most of the testing have been moved to test-fold
 
   $ rm *.orig
-  $ hg fold
-  abort: no revisions specified
-  [255]
-  $ hg fold --from
-  abort: no revisions specified
-  [255]
-  $ hg fold .
-  abort: must specify either --from or --exact
-  [255]
-  $ hg fold --from . --exact
-  abort: cannot use both --from and --exact
-  [255]
-  $ hg fold --from .
-  single revision specified, nothing to fold
-  [1]
-  $ hg fold 0::10 --rev 1 --exact
-  abort: cannot fold non-linear revisions (multiple heads given)
-  [255]
-  $ hg fold -r 4 -r 6 --exact
-  abort: cannot fold non-linear revisions (multiple roots given)
-  [255]
-  $ hg fold --from 10 1
-  abort: cannot fold non-linear revisions
-  (given revisions are unrelated to parent of working directory)
-  [255]
-  $ hg fold --exact -r "4 and not 4"
-  abort: specified revisions evaluate to an empty set
-  (use different revision arguments)
-  [255]
   $ hg phase --public 0
-  $ hg fold --from -r 0
-  abort: cannot fold public revisions
-  [255]
   $ hg fold --from -r 5
   3 changesets folded
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg fold --from 6 # want to run hg fold 6
-  abort: hidden revision '6'!
-  (use --hidden to access hidden revisions)
-  [255]
   $ hg log -r 11 --template '{desc}\n'
   add 3
   
@@ -1019,7 +1016,7 @@
   $ hg commit -m "add new file bumped" -o 11
   $ hg phase --public --hidden 11
   1 new bumped changesets
-  $ hg glog
+  $ hg log -G
   @  12	: add new file bumped - test
   |
   | o  11	: a2 - test
@@ -1038,7 +1035,7 @@
 Now we have a bumped and an unstable changeset, we solve the bumped first
 normally the unstable changeset would be solve first
 
-  $ hg glog
+  $ hg log -G
   @  12	: add new file bumped - test
   |
   | o  11	: a2 - test
@@ -1074,7 +1071,7 @@
   $ hg up 14 
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ printf "newline\nnewline\n" >> a
-  $ hg glog
+  $ hg log -G
   o  16	: add gh - test
   |
   | o  15	: add gg - test
@@ -1091,7 +1088,7 @@
   
   $ hg amend
   2 new unstable changesets
-  $ hg glog
+  $ hg log -G
   @  18	: a3 - test
   |
   | o  16	: add gh - test
@@ -1124,7 +1121,7 @@
   move:[16] add gh
   atop:[18] a3
   working directory is now at e02107f98737
-  $ hg glog
+  $ hg log -G
   @  20	: add gh - test
   |
   | o  19	: add gg - test
@@ -1284,7 +1281,8 @@
   $ hg up 8dc373be86d9^
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ hg uncommit --all
-  abort: cannot uncommit in the middle of a stack
+  abort: uncommit will orphan 4 descendants
+  (see 'hg help evolution.instability')
   [255]
   $ hg up 8dc373be86d9
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -1342,12 +1340,12 @@
   $ mkcommit c5_
   created new head
   $ hg prune '26 + 27'
-  abort: cannot prune in the middle of a stack
-  (new unstable changesets are not allowed)
+  abort: touch will orphan 1 descendants
+  (see 'hg help evolution.instability')
   [255]
   $ hg prune '19::28'
-  abort: cannot prune in the middle of a stack
-  (new unstable changesets are not allowed)
+  abort: touch will orphan 1 descendants
+  (see 'hg help evolution.instability')
   [255]
   $ hg prune '26::'
   3 changesets pruned
@@ -1363,6 +1361,9 @@
   ~
 
 Check that fold respects the allowunstable option
+
+(most of this has been moved to test-fold.t)
+
   $ hg up edc3c9de504e
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ mkcommit unstableifparentisfolded
@@ -1380,14 +1381,6 @@
   |
   ~
 
-  $ hg fold --exact "19 + 18"
-  abort: cannot fold chain not ending with a head or with branching
-  (new unstable changesets are not allowed)
-  [255]
-  $ hg fold --exact "18::29"
-  abort: cannot fold chain not ending with a head or with branching
-  (new unstable changesets are not allowed)
-  [255]
   $ hg fold --exact "19::"
   2 changesets folded
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fold.t	Tue Jul 25 03:59:22 2017 +0200
@@ -0,0 +1,225 @@
+  $ . $TESTDIR/testlib/common.sh
+
+setup
+
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > fold=-d "0 0"
+  > [extensions]
+  > evolve=
+  > [ui]
+  > logtemplate = '{rev} - {node|short} {desc|firstline} [{author}] ({phase})\n'
+  > EOF
+
+  $ hg init fold-tests
+  $ cd fold-tests/
+  $ hg debugbuilddag .+3:branchpoint+4*branchpoint+2
+  $ hg up 'desc("r7")'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log -G
+  o  10 - a8407f9a3dc1 r10 [debugbuilddag] (draft)
+  |
+  o  9 - 529dfc5bb875 r9 [debugbuilddag] (draft)
+  |
+  o  8 - abf57d94268b r8 [debugbuilddag] (draft)
+  |
+  | @  7 - 4de32a90b66c r7 [debugbuilddag] (draft)
+  | |
+  | o  6 - f69452c5b1af r6 [debugbuilddag] (draft)
+  | |
+  | o  5 - c8d03c1b5e94 r5 [debugbuilddag] (draft)
+  | |
+  | o  4 - bebd167eb94d r4 [debugbuilddag] (draft)
+  |/
+  o  3 - 2dc09a01254d r3 [debugbuilddag] (draft)
+  |
+  o  2 - 01241442b3c2 r2 [debugbuilddag] (draft)
+  |
+  o  1 - 66f7d451a68b r1 [debugbuilddag] (draft)
+  |
+  o  0 - 1ea73414a91b r0 [debugbuilddag] (draft)
+  
+
+Test various error case
+
+  $ hg fold --exact null::
+  abort: cannot fold the null revision
+  (no changeset checked out)
+  [255]
+  $ hg fold
+  abort: no revisions specified
+  [255]
+  $ hg fold --from
+  abort: no revisions specified
+  [255]
+  $ hg fold .
+  abort: must specify either --from or --exact
+  [255]
+  $ hg fold --from . --exact
+  abort: cannot use both --from and --exact
+  [255]
+  $ hg fold --from .
+  single revision specified, nothing to fold
+  [1]
+  $ hg fold '0::(7+10)' --exact
+  abort: cannot fold non-linear revisions (multiple heads given)
+  [255]
+  $ hg fold -r 4 -r 6 --exact
+  abort: cannot fold non-linear revisions (multiple roots given)
+  [255]
+  $ hg fold --from 10 1
+  abort: cannot fold non-linear revisions
+  (given revisions are unrelated to parent of working directory)
+  [255]
+  $ hg fold --exact -r "4 and not 4"
+  abort: specified revisions evaluate to an empty set
+  (use different revision arguments)
+  [255]
+  $ hg phase --public 0
+  $ hg fold --from -r 0
+  abort: cannot fold public changesets: 1ea73414a91b
+  (see 'hg help phases' for details)
+  [255]
+
+Test actual folding
+
+  $ hg fold --from -r 'desc("r5")'
+  3 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+(test inherited from test-evolve.t)
+
+  $ hg fold --from 6 # want to run hg fold 6
+  abort: hidden revision '6'!
+  (use --hidden to access hidden revisions)
+  [255]
+
+  $ hg log -G
+  @  11 - 198b5c405d01 r5 [debugbuilddag] (draft)
+  |
+  | o  10 - a8407f9a3dc1 r10 [debugbuilddag] (draft)
+  | |
+  | o  9 - 529dfc5bb875 r9 [debugbuilddag] (draft)
+  | |
+  | o  8 - abf57d94268b r8 [debugbuilddag] (draft)
+  | |
+  o |  4 - bebd167eb94d r4 [debugbuilddag] (draft)
+  |/
+  o  3 - 2dc09a01254d r3 [debugbuilddag] (draft)
+  |
+  o  2 - 01241442b3c2 r2 [debugbuilddag] (draft)
+  |
+  o  1 - 66f7d451a68b r1 [debugbuilddag] (draft)
+  |
+  o  0 - 1ea73414a91b r0 [debugbuilddag] (public)
+  
+
+test fold --exact
+
+  $ hg fold --exact 'desc("r8") + desc("r10")'
+  abort: cannot fold non-linear revisions (multiple roots given)
+  [255]
+  $ hg fold --exact 'desc("r8")::desc("r10")'
+  3 changesets folded
+  $ hg log -G
+  o  12 - b568edbee6e0 r8 [debugbuilddag] (draft)
+  |
+  | @  11 - 198b5c405d01 r5 [debugbuilddag] (draft)
+  | |
+  | o  4 - bebd167eb94d r4 [debugbuilddag] (draft)
+  |/
+  o  3 - 2dc09a01254d r3 [debugbuilddag] (draft)
+  |
+  o  2 - 01241442b3c2 r2 [debugbuilddag] (draft)
+  |
+  o  1 - 66f7d451a68b r1 [debugbuilddag] (draft)
+  |
+  o  0 - 1ea73414a91b r0 [debugbuilddag] (public)
+  
+
+Test allow unstable
+
+  $ echo a > a
+  $ hg add a
+  $ hg commit '-m r11'
+  $ hg up '.^'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg log -G
+  o  13 - 14d0e0da8e91 r11 [test] (draft)
+  |
+  | o  12 - b568edbee6e0 r8 [debugbuilddag] (draft)
+  | |
+  @ |  11 - 198b5c405d01 r5 [debugbuilddag] (draft)
+  | |
+  o |  4 - bebd167eb94d r4 [debugbuilddag] (draft)
+  |/
+  o  3 - 2dc09a01254d r3 [debugbuilddag] (draft)
+  |
+  o  2 - 01241442b3c2 r2 [debugbuilddag] (draft)
+  |
+  o  1 - 66f7d451a68b r1 [debugbuilddag] (draft)
+  |
+  o  0 - 1ea73414a91b r0 [debugbuilddag] (public)
+  
+
+  $ cat << EOF >> .hg/hgrc
+  > [experimental]
+  > evolution = createmarkers, allnewcommands
+  > EOF
+  $ hg fold --from 'desc("r4")'
+  abort: fold will orphan 1 descendants
+  (see 'hg help evolution.instability')
+  [255]
+  $ hg fold --from 'desc("r3")::desc("r11")'
+  abort: fold will orphan 1 descendants
+  (see 'hg help evolution.instability')
+  [255]
+
+test --user variant
+
+  $ cat << EOF >> .hg/hgrc
+  > [experimental]
+  > evolution = createmarkers, allnewcommands
+  > EOF
+  $ cat << EOF >> .hg/hgrc
+  > [experimental]
+  > evolution = all
+  > EOF
+
+  $ hg fold --exact 'desc("r5") + desc("r11")' --user 'Victor Rataxes <victor@rhino.savannah>'
+  2 changesets folded
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log -G
+  @  14 - 29b470a33594 r5 [Victor Rataxes <victor@rhino.savannah>] (draft)
+  |
+  | o  12 - b568edbee6e0 r8 [debugbuilddag] (draft)
+  | |
+  o |  4 - bebd167eb94d r4 [debugbuilddag] (draft)
+  |/
+  o  3 - 2dc09a01254d r3 [debugbuilddag] (draft)
+  |
+  o  2 - 01241442b3c2 r2 [debugbuilddag] (draft)
+  |
+  o  1 - 66f7d451a68b r1 [debugbuilddag] (draft)
+  |
+  o  0 - 1ea73414a91b r0 [debugbuilddag] (public)
+  
+
+  $ hg fold --from 'desc("r4")' -U
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log -G
+  @  15 - 91880abed0f2 r4 [test] (draft)
+  |
+  | o  12 - b568edbee6e0 r8 [debugbuilddag] (draft)
+  |/
+  o  3 - 2dc09a01254d r3 [debugbuilddag] (draft)
+  |
+  o  2 - 01241442b3c2 r2 [debugbuilddag] (draft)
+  |
+  o  1 - 66f7d451a68b r1 [debugbuilddag] (draft)
+  |
+  o  0 - 1ea73414a91b r0 [debugbuilddag] (public)
+  
+  $ cd ..
+
--- a/tests/test-metaedit.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-metaedit.t	Tue Jul 25 03:59:22 2017 +0200
@@ -14,7 +14,6 @@
   > git = 1
   > unified = 0
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
   $ mkcommit() {
@@ -31,7 +30,7 @@
   > }
 
   $ glog() {
-  >   hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
+  >   hg log -G --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
   > }
 
   $ shaof() {
@@ -104,7 +103,8 @@
   abort: revisions must be specified with --fold
   [255]
   $ hg metaedit -r 0 --fold
-  abort: cannot fold public revisions
+  abort: cannot fold public changesets: ea207398892e
+  (see 'hg help phases' for details)
   [255]
   $ hg metaedit 'desc(C) + desc(F)' --fold
   abort: cannot fold non-linear revisions (multiple roots given)
@@ -118,8 +118,8 @@
   (587528abfffe will become unstable and new unstable changes are not allowed)
   [255]
   $ hg metaedit 'desc(A)::desc(B)' --fold --config 'experimental.evolution=createmarkers, allnewcommands'
-  abort: cannot fold chain not ending with a head or with branching
-  (new unstable changesets are not allowed)
+  abort: fold will orphan 4 descendants
+  (see 'hg help evolution.instability')
   [255]
   $ hg metaedit --user foobar
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -209,7 +209,7 @@
   $ hg diff -r "10" -r "11" --hidden
 
 'fold' one commit
-  $ hg metaedit "desc(D2)" --fold --user foobar3
+  $ HGUSER=foobar3 hg metaedit "desc(D2)" --fold -U --config
   1 changesets folded
   $ hg log -r "tip" --template '{rev}: {author}\n'
   13: foobar3
--- a/tests/test-obsolete-push.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-obsolete-push.t	Tue Jul 25 03:59:22 2017 +0200
@@ -2,13 +2,12 @@
   > [defaults]
   > amend=-d "0 0"
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
   $ template='{rev}:{node|short}@{branch}({separate("/", obsolete, phase)}) {desc|firstline}\n'
   $ glog() {
-  >   hg glog --template "$template" "$@"
+  >   hg log -G --template "$template" "$@"
   > }
 
 Test outgoing, common A is suspended, B unstable and C secret, remote
--- a/tests/test-oldconvert.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-oldconvert.t	Tue Jul 25 03:59:22 2017 +0200
@@ -7,7 +7,6 @@
   > [alias]
   > odiff=diff --rev 'limit(obsparents(.),1)' --rev .
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -34,13 +33,13 @@
 
   $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/legacy.py" >> $HGRCPATH
 
-  $ hg glog
+  $ hg log -G
   abort: old format of obsolete marker detected!
   run `hg debugconvertobsolete` once.
   [255]
   $ hg debugconvertobsolete --traceback
   1 obsolete marker converted
-  $ hg glog
+  $ hg log -G
   @  changeset:   2:d67cd0334eee
   |  tag:         tip
   |  parent:      0:1f0dee641bb7
@@ -101,7 +100,7 @@
   >     }
   > ]
   > EOF
-  $ hg glog
+  $ hg log -G
   abort: old format of obsolete marker detected!
   run `hg debugconvertobsolete` once.
   [255]
--- a/tests/test-prev-next.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-prev-next.t	Tue Jul 25 03:59:22 2017 +0200
@@ -1,12 +1,12 @@
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
-  > hgext.graphlog=
   > color =
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
 hg prev -B should move active bookmark
-  $ hg init
+  $ hg init test-repo
+  $ cd test-repo
   $ touch a
   $ hg add a
   $ hg commit -m 'added a'
@@ -94,6 +94,15 @@
      mark                      2:4e26ef31f919
      no-move                   2:4e26ef31f919
 
+test prev on root
+
+  $ hg up null
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg prev
+  already at repository root
+  [1]
+  $ hg up 1
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Behavior with local modification
 --------------------------------
@@ -217,6 +226,8 @@
   atop:[6] added b (3)
   working directory is now at 47ea25be8aea
 
+  $ cd ..
+
 prev and next should lock properly against other commands
 
   $ hg init repo
--- a/tests/test-prune.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-prune.t	Tue Jul 25 03:59:22 2017 +0200
@@ -87,7 +87,7 @@
 cannot prune public changesets
 
   $ hg prune 0
-  abort: cannot prune immutable changeset: 1f0dee641bb7
+  abort: cannot touch public changesets: 1f0dee641bb7
   (see 'hg help phases' for details)
   [255]
   $ hg debugobsolete
--- a/tests/test-sharing.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-sharing.t	Tue Jul 25 03:59:22 2017 +0200
@@ -144,7 +144,8 @@
 
 Now that the fix is public, we cannot amend it any more.
   $ hg amend -m 'fix bug 37'
-  abort: cannot amend public changesets
+  abort: cannot amend public changesets: de6151c48e1c
+  (see 'hg help phases' for details)
   [255]
 
 Figure SG05
--- a/tests/test-split.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-split.t	Tue Jul 25 03:59:22 2017 +0200
@@ -1,6 +1,8 @@
 test of the split command
 -----------------------
 
+  $ . $TESTDIR/testlib/common.sh
+
   $ cat >> $HGRCPATH <<EOF
   > [defaults]
   > amend=-d "0 0"
@@ -18,9 +20,8 @@
   > [ui]
   > interactive = true
   > [extensions]
-  > hgext.graphlog=
+  > evolve =
   > EOF
-  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
   $ mkcommit() {
   >    echo "$1" > "$1"
   >    hg add "$1"
@@ -94,7 +95,7 @@
   1334a80b33c3f9873edab728fbbcf500eab61d2e d2fe56e71366c2c5376c89960c281395062c0619 0 (*) {'ef1': '8', 'user': 'test'} (glob)
   06be89dfe2ae447383f30a2984933352757b6fb4 0 {1334a80b33c3f9873edab728fbbcf500eab61d2e} (*) {'ef1': '0', 'user': 'test'} (glob)
   d2fe56e71366c2c5376c89960c281395062c0619 2d8abdb827cdf71ca477ef6985d7ceb257c53c1b 033b3f5ae73db67c10de938fb6f26b949aaef172 0 (*) {'ef1': '13', 'user': 'test'} (glob)
-  $ hg glog
+  $ hg log -G
   @  changeset:   7:033b3f5ae73d
   |  tag:         tip
   |  user:        test
@@ -130,10 +131,21 @@
   $ hg split
   abort: uncommitted changes
   [255]
+  $ hg up "desc(_c)" -C
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Cannot split public changeset
+
+  $ hg phase --rev 'desc("_a")'
+  0: draft
+  $ hg phase --rev 'desc("_a")' --public
+  $ hg split --rev 'desc("_a")'
+  abort: cannot split public changesets: 135f39f4bd78
+  (see 'hg help phases' for details)
+  [255]
+  $ hg phase --rev 'desc("_a")' --draft --force
 
 Split a revision specified with -r
-  $ hg up "desc(_c)" -C
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo "change to b" >> _b
   $ hg amend -m "_cprim"
   2 new unstable changesets
@@ -212,7 +224,7 @@
   move:[11] split2
   atop:[14] split1
   working directory is now at d74c6715e706
-  $ hg glog
+  $ hg log -G
   @  changeset:   15:d74c6715e706
   |  tag:         tip
   |  user:        test
@@ -255,7 +267,7 @@
   $ hg book
      bookA                     17:7a6b35779b85
    * bookB                     17:7a6b35779b85
-  $ hg glog -r "14::"
+  $ hg log -G -r "14::"
   @  changeset:   17:7a6b35779b85
   |  bookmark:    bookA
   |  bookmark:    bookB
@@ -270,7 +282,7 @@
   ~  date:        Thu Jan 01 00:00:00 1970 +0000
      summary:     split1
   
-  $ hg split <<EOF
+  $ hg split --user victor <<EOF
   > y
   > y
   > n
@@ -296,18 +308,18 @@
   
   created new head
   Done splitting? [yN] y
-  $ hg glog -r "14::"
-  @  changeset:   19:60ea019b0f8d
+  $ hg log -G -r "14::"
+  @  changeset:   19:452a26648478
   |  bookmark:    bookA
   |  bookmark:    bookB
   |  tag:         tip
-  |  user:        test
+  |  user:        victor
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     split6
   |
-  o  changeset:   18:2c7a2e53f23b
+  o  changeset:   18:1315679b77dc
   |  parent:      14:3f134f739075
-  |  user:        test
+  |  user:        victor
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     split5
   |
@@ -317,8 +329,8 @@
      summary:     split1
   
   $ hg book
-     bookA                     19:60ea019b0f8d
-   * bookB                     19:60ea019b0f8d
+     bookA                     19:452a26648478
+   * bookB                     19:452a26648478
  
 Lastest revision is selected if multiple are given to -r
   $ hg split -r "desc(_a)::"
@@ -337,7 +349,8 @@
   > evolutioncommands=split
   > EOF
   $ hg split -r "desc(split3)"
-  abort: cannot split commit: ead2066d1dbf not a head
+  abort: split will orphan 4 descendants
+  (see 'hg help evolution.instability')
   [255]
 
 Changing evolution level to createmarkers
@@ -399,9 +412,18 @@
   $ echo babar > babar
   $ echo celeste > celeste
   $ hg add babar celeste
-  $ hg commit -m "Works on mytopic" babar celeste
+  $ hg commit -m "Works on mytopic" babar celeste --user victor
+  $ hg log -r . 
+  changeset:   21:26f72cfaf036
+  branch:      new-branch
+  tag:         tip
+  topic:       mytopic
+  user:        victor
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Works on mytopic
+  
   $ hg summary
-  parent: 21:615c369f47f0 tip
+  parent: 21:26f72cfaf036 tip
    Works on mytopic
   branch: new-branch
   commit: 2 unknown (clean)
@@ -411,7 +433,7 @@
 
 Split it
 
-  $ hg split << EOF
+  $ hg split -U << EOF
   > Y
   > Y
   > N
@@ -448,15 +470,15 @@
 Check that the topic is still here
 
   $ hg log -r "tip~1::"
-  changeset:   22:f879ab83f991
+  changeset:   22:addcf498f19e
   branch:      new-branch
   topic:       mytopic
-  parent:      20:89e64a2c68b3
+  parent:      20:fdb403258632
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     split7
   
-  changeset:   23:db75dbc1a3a6
+  changeset:   23:2532b288af61
   branch:      new-branch
   tag:         tip
   topic:       mytopic
--- a/tests/test-stabilize-order.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-stabilize-order.t	Tue Jul 25 03:59:22 2017 +0200
@@ -2,12 +2,11 @@
   > [defaults]
   > amend=-d "0 0"
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
   $ glog() {
-  >   hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
+  >   hg log -G --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
   > }
 
   $ hg init repo
--- a/tests/test-stabilize-result.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-stabilize-result.t	Tue Jul 25 03:59:22 2017 +0200
@@ -3,12 +3,11 @@
   > amend=-d "0 0"
   > [extensions]
   > hgext.rebase=
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
   $ glog() {
-  >   hg glog --template \
+  >   hg log -G --template \
   >     '{rev}:{node|short}@{branch}({phase}) bk:[{bookmarks}] {desc|firstline}\n' "$@"
   > }
 
--- a/tests/test-stack-branch.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-stack-branch.t	Tue Jul 25 03:59:22 2017 +0200
@@ -75,7 +75,14 @@
   b3: c_e
   b2: c_d
   b1: c_c
-    ^ c_b
+  b0^ c_b (base)
+  $ hg stack -v
+  ### branch: foo
+  b4(913c298d8b0a)@ c_f (current)
+  b3(4f2a69f6d380): c_e
+  b2(f61adbacd17a): c_d
+  b1(3e9313bc4b71): c_c
+  b0(4a04f1104a27)^ c_b (base)
 
 Test "t#" reference
 -------------------
@@ -123,7 +130,7 @@
   b3$ c_e (unstable)
   b2@ c_d (current)
   b1: c_c
-    ^ c_b
+  b0^ c_b (base)
   $ hg up b3
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg stack
@@ -132,7 +139,7 @@
   b3$ c_e (current unstable)
   b2: c_d
   b1: c_c
-    ^ c_b
+  b0^ c_b (base)
   $ hg up b2
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
 
@@ -206,7 +213,7 @@
   b3: c_g
   b2: c_d
   b1: c_c
-    ^ c_b
+  b0^ c_b (base)
 
 Case with multiple heads on the topic with unstability involved
 ---------------------------------------------------------------
@@ -249,7 +256,7 @@
   b3: c_g
   b2@ c_D (current)
   b1: c_c
-    ^ c_b
+  b0^ c_b (base)
 
 Check that stack doesn't show draft changesets on a branch
 ----------------------------------------------------------
@@ -263,7 +270,7 @@
   b3: c_g
   b2@ c_D (current)
   b1: c_c
-    ^ c_b
+  b0^ c_b (base)
   $ hg phase --public b1
   $ hg stack
   ### branch: foo (2 heads)
@@ -273,7 +280,7 @@
   b3: c_h
   b2: c_g
   b1@ c_D (current)
-    ^ c_c
+  b0^ c_c (base)
 
 Check that stack doesn't show changeset with a topic
 ----------------------------------------------------
@@ -285,4 +292,4 @@
   b3: c_h
   b2: c_g
   b1@ c_D (current)
-    ^ c_c
+  b0^ c_c (base)
--- a/tests/test-topic-fold.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-topic-fold.t	Tue Jul 25 03:59:22 2017 +0200
@@ -18,7 +18,6 @@
   > [ui]
   > interactive = true
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "topic=$(echo $(dirname $TESTDIR))/hgext3rd/topic/" >> $HGRCPATH
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
@@ -54,7 +53,7 @@
   ### topic: myfeature
   ### branch: default
   t1@ folded (current)
-    ^ add ROOT
+  t0^ add ROOT (base)
   $ logtopic
   @  3:4fd43e5bdc443dc8489edffac19bd8f93ccf1a5c
   |  topics: myfeature
--- a/tests/test-topic-rebase.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-topic-rebase.t	Tue Jul 25 03:59:22 2017 +0200
@@ -18,7 +18,6 @@
   > [ui]
   > interactive = true
   > [extensions]
-  > hgext.graphlog=
   > rebase=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
@@ -46,7 +45,7 @@
   ### topic: myfeature
   ### branch: default
   t1@ add feature1 (current)
-    ^ add ROOT
+  t0^ add ROOT (base)
   $ logtopic
   @  1:39e7a938055e87615edf675c24a10997ff05bb06
   |  topics: myfeature
@@ -76,7 +75,7 @@
   ### topic: myfeature
   ### branch: default
   t1@ add feature1 (current)
-    ^ add default
+  t0^ add default (base)
   $ logtopic
   @  3:fc6593661cf3256ba165cbccd6019ead17cc3726
   |  topics: myfeature
@@ -90,7 +89,7 @@
   ### topic: myfeature
   ### branch: default
   t1@ add feature1 (current)
-    ^ add default
+  t0^ add default (base)
 
 Check that rebase keep the topic in case of merge conflict
 ----------------------------------------------------------
@@ -152,11 +151,11 @@
   ### topic: myotherfeature
   ### branch: default
   t1@ myotherfeature1 (current)
-    ^ default3
+  t0^ default3 (base)
   $ hg update --rev 7
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg stack
   ### topic: myotherfeature
   ### branch: default
   t1@ myotherfeature1 (current)
-    ^ default3
+  t0^ default3 (base)
--- a/tests/test-topic-stack-data.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-topic-stack-data.t	Tue Jul 25 03:59:22 2017 +0200
@@ -248,23 +248,33 @@
   t3: bar1_d
   t2: add bar_b
   t1: add bar_a
-    ^ add base_e
+  t0^ add base_e (base)
+  $ hg stack bar -v
+  ### topic: bar (2 heads)
+  ### branch: default
+  t5(9cbadf11b44d): add bar_c
+  t2(e555c7e8c767)^ add bar_b (base)
+  t4(a920412b5a05)$ add bar_e (unstable)
+  t3(6915989374b1): bar1_d
+  t2(e555c7e8c767): add bar_b
+  t1(a5c2b4e00bbf): add bar_a
+  t0(92f489a6251f)^ add base_e (base)
   $ hg stack baz
   ### topic: baz
   ### branch: default, 2 behind
   t2: add baz_b
   t1: add baz_a
-    ^ add base_c
+  t0^ add base_c (base)
   $ hg stack foo
   ### topic: foo
   ### branch: lake, ambigious rebase destination
   t2@ add foo_b (current)
   t1: add foo_a
-    ^ add lake_a
+  t0^ add lake_a (base)
   $ hg stack fuz
   ### topic: fuz
   ### branch: default, 1 behind
   t3$ add fuz_c (unstable)
   t2$ add fuz_b (unstable)
   t1: fuz1_a
-    ^ add base_d
+  t0^ add base_d (base)
--- a/tests/test-topic-stack.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-topic-stack.t	Tue Jul 25 03:59:22 2017 +0200
@@ -78,7 +78,15 @@
   t3: c_e
   t2: c_d
   t1: c_c
-    ^ c_b
+  t0^ c_b (base)
+  $ hg stack -v
+  ### topic: foo
+  ### branch: default
+  t4(6559e6d93aea)@ c_f (current)
+  t3(0f9ac936c87d): c_e
+  t2(e629654d7050): c_d
+  t1(8522f9e3fee9): c_c
+  t0(ea705abc4f51)^ c_b (base)
   $ hg stack -Tjson | python -m json.tool
   [
       {
@@ -120,6 +128,60 @@
       {
           "isentry": false,
           "topic.stack.desc": "c_b",
+          "topic.stack.index": 0,
+          "topic.stack.state": [
+              "base"
+          ],
+          "topic.stack.state.symbol": "^"
+      }
+  ]
+  $ hg stack -v -Tjson | python -m json.tool
+  [
+      {
+          "isentry": true,
+          "topic.stack.desc": "c_f",
+          "topic.stack.index": 4,
+          "topic.stack.shortnode": "6559e6d93aea",
+          "topic.stack.state": [
+              "current"
+          ],
+          "topic.stack.state.symbol": "@"
+      },
+      {
+          "isentry": true,
+          "topic.stack.desc": "c_e",
+          "topic.stack.index": 3,
+          "topic.stack.shortnode": "0f9ac936c87d",
+          "topic.stack.state": [
+              "clean"
+          ],
+          "topic.stack.state.symbol": ":"
+      },
+      {
+          "isentry": true,
+          "topic.stack.desc": "c_d",
+          "topic.stack.index": 2,
+          "topic.stack.shortnode": "e629654d7050",
+          "topic.stack.state": [
+              "clean"
+          ],
+          "topic.stack.state.symbol": ":"
+      },
+      {
+          "isentry": true,
+          "topic.stack.desc": "c_c",
+          "topic.stack.index": 1,
+          "topic.stack.shortnode": "8522f9e3fee9",
+          "topic.stack.state": [
+              "clean"
+          ],
+          "topic.stack.state.symbol": ":"
+      },
+      {
+          "isentry": false,
+          "topic.stack.desc": "c_b",
+          "topic.stack.index": 0,
+          "topic.stack.shortnode": "ea705abc4f51",
           "topic.stack.state": [
               "base"
           ],
@@ -182,7 +244,7 @@
   t3$ c_e (unstable)
   t2@ c_d (current)
   t1: c_c
-    ^ c_b
+  t0^ c_b (base)
   $ hg up t3
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg topic --list
@@ -192,7 +254,7 @@
   t3$ c_e (current unstable)
   t2: c_d
   t1: c_c
-    ^ c_b
+  t0^ c_b (base)
   $ hg topic --list --color=debug
   [topic.stack.summary.topic|### topic: [topic.active|foo]]
   [topic.stack.summary.branches|### branch: default]
@@ -200,7 +262,7 @@
   [topic.stack.index topic.stack.index.current topic.stack.index.unstable|t3][topic.stack.state topic.stack.state.current topic.stack.state.unstable|$] [topic.stack.desc topic.stack.desc.current topic.stack.desc.unstable|c_e][topic.stack.state topic.stack.state.current topic.stack.state.unstable| (current unstable)]
   [topic.stack.index topic.stack.index.clean|t2][topic.stack.state topic.stack.state.clean|:] [topic.stack.desc topic.stack.desc.clean|c_d]
   [topic.stack.index topic.stack.index.clean|t1][topic.stack.state topic.stack.state.clean|:] [topic.stack.desc topic.stack.desc.clean|c_c]
-    [topic.stack.state topic.stack.state.base|^] [topic.stack.desc topic.stack.desc.base|c_b]
+  [topic.stack.index topic.stack.index.base|t0][topic.stack.state topic.stack.state.base|^] [topic.stack.desc topic.stack.desc.base|c_b][topic.stack.state topic.stack.state.base| (base)]
   $ hg up t2
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
 
@@ -283,7 +345,7 @@
   t3: c_g
   t2: c_d
   t1: c_c
-    ^ c_b
+  t0^ c_b (base)
 
 Case with multiple heads on the topic with unstability involved
 ---------------------------------------------------------------
@@ -327,7 +389,7 @@
   t3: c_g
   t2@ c_D (current)
   t1: c_c
-    ^ c_b
+  t0^ c_b (base)
 
 Trying to list non existing topic
   $ hg stack thisdoesnotexist
--- a/tests/test-topic-tutorial.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-topic-tutorial.t	Tue Jul 25 03:59:22 2017 +0200
@@ -42,7 +42,7 @@
 Topic Basics
 ============
 
-Let's says we use Mercurial to manage our shopping list::
+Let's say we use Mercurial to manage our shopping list::
 
   $ hg log --graph
   @  changeset:   0:38da43f0a2ea
@@ -52,12 +52,13 @@
      summary:     Shopping list
   
 
-We are about to do some edition to this list and would like to do them within
-a topic. Creating a new topic is done using the ``topic`` command::
+We are about to make some additions to this list and would like to do them
+within a topic. Creating a new topic is done using the ``topic`` command::
 
   $ hg topic food
 
-As for named branch, our topic is active but it does not contains any changesets yet::
+Much like a named branch, our topic is active but it does not contain any
+changesets yet::
 
   $ hg topic
    * food
@@ -94,7 +95,7 @@
      summary:     adding condiments
   
 
-And future commit will be part of that topic too::
+And future commits will be part of that topic too::
 
   $ cat >> shopping << EOF
   > Bananas
@@ -117,23 +118,25 @@
      summary:     adding condiments
   
 
-We can get a compact view of the content of our topic using the ``stack`` command::
+We can get a compact view of the content of our topic using the ``stack``
+command::
 
   $ hg stack
   ### topic: food
   ### branch: default
   t2@ adding fruits (current)
   t1: adding condiments
-    ^ Shopping list
+  t0^ Shopping list (base)
 
-The topic desactivate when we update away from it::
+The topic deactivates when we update away from it::
 
   $ hg up default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg topic
      food
 
-Note that ``default`` (name of the branch) now refers to the tipmost changeset of default without a topic::
+Note that ``default`` (name of the branch) now refers to the tipmost
+changeset of default without a topic::
 
   $ hg log --graph
   o  changeset:   2:287de11b401f
@@ -155,7 +158,7 @@
      summary:     Shopping list
   
 
-And updating back to the topic reactivate it::
+And updating back to the topic reactivates it::
 
   $ hg up food
   switching to topic food
@@ -163,7 +166,8 @@
   $ hg topic
    * food
 
-The name used for updating does not affect the activation of the topic, updating to a revision part of a topic will activate it in all cases::
+Updating to any changeset that is part of a topic activates the topic
+regardless of how the revision was specified::
 
   $ hg up default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -190,7 +194,8 @@
   $ hg commit -A -m "Adding clothes"
   $ cd ../client
 
-Topic will also affect rebase and merge destination. Let's pull the latest update from the main server::
+The topic will also affect the rebase and the merge destinations. Let's pull
+the latest update from the main server::
 
   $ hg pull
   pulling from $TESTTMP/server (glob)
@@ -226,7 +231,8 @@
      summary:     Shopping list
   
 
-The topic head will not be considered when merge from the new head of the branch::
+The topic head will not be considered when merging from the new head of the
+branch::
 
   $ hg up default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -272,7 +278,7 @@
      summary:     Shopping list
   
 
-The topic information will fade out when we publish the changesets::
+The topic information will disappear when we publish the changesets::
 
   $ hg topic
    * food
@@ -315,12 +321,12 @@
 Working with Multiple Topics
 ============================
 
-In the above example, topic are not bring much benefit since you only have one
-line of developement. Topic start to be more useful when you have to work on
-multiple features are the same time.
+In the above example, topics do not bring much benefit since you only have one
+line of development. Topics start to be more useful when you have to work on
+multiple features at the same time.
 
 We might go shopping in a hardware store in the same go, so let's add some
-tools to the shopping list withing a new topic::
+tools to the shopping list within a new topic::
 
   $ hg topic tools
   $ echo hammer >> shopping
@@ -330,9 +336,9 @@
   $ echo drill >> shopping
   $ hg ci -m 'Adding drill'
 
-But are not sure to actually go in the hardward store, so in the meantime, we
-want to extend the list with drinks. We go back to the official default branch
-and start a new topic::
+But we are not sure we will actually go to the hardware store, so in the
+meantime, we want to extend the list with drinks. We go back to the official
+default branch and start a new topic::
 
   $ hg up default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -348,14 +354,14 @@
    * drinks
      tools
 
-The information ``hg stack`` command adapt to the active topic::
+The information displayed by ``hg stack`` adapts to the active topic::
 
   $ hg stack
   ### topic: drinks
   ### branch: default
   t2@ Adding orange juice (current)
   t1: Adding apple juice
-    ^ adding fruits
+  t0^ adding fruits (base)
   $ hg up tools
   switching to topic tools
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -365,9 +371,10 @@
   t3@ Adding drill (current)
   t2: Adding saw
   t1: Adding hammer
-    ^ adding fruits
+  t0^ adding fruits (base)
 
-They are seen as independant branch by Mercurial. No rebase or merge betwen them will be attempted by default::
+They are seen as independent branches by Mercurial. No rebase or merge
+between them will be attempted by default::
 
   $ hg rebase
   nothing to rebase
@@ -385,13 +392,11 @@
   $ echo 'Coat' > shopping
   $ echo 'Shoes' >> shopping
   $ cat foo >> shopping
-  $ hg rm foo
-  not removing foo: file is untracked
-  [1]
+  $ rm foo
   $ hg ci -m 'add a pair of shoes'
   $ cd ../client
 
-Lets see what other people did in the mean time::
+Let's see what other people did in the meantime::
 
   $ hg pull
   pulling from $TESTTMP/server (glob)
@@ -402,7 +407,8 @@
   added 2 changesets with 2 changes to 1 files (+1 heads)
   (run 'hg heads' to see heads)
 
-There is new changes! We can simply use ``hg rebase`` to update our changeset on top of the latest::
+There are new changes! We can simply use ``hg rebase`` to update our
+changeset on top of the latest::
 
   $ hg rebase
   rebasing 6:183984ef46d1 "Adding hammer"
@@ -413,15 +419,17 @@
   rebasing 8:34255b455dac "Adding drill"
   merging shopping
 
-But what about the other topic? You can use 'hg topic --verbose' to see information about them::
+But what about the other topic? You can use 'hg topic --verbose' to see
+information about all the topics::
 
   $ hg topic --verbose
      drinks (on branch: default, 2 changesets, 2 behind)
    * tools  (on branch: default, 3 changesets)
 
-The "2 behind" is telling you that there is 2 new changesets on the named branch of the topic. You need to merge or rebase to incorporate them.
+The "2 behind" is telling you that there are 2 new changesets on the named
+branch of the topic. You need to merge or rebase to incorporate them.
 
-Pushing that topic would create a new heads will be prevented::
+Pushing that topic would create a new head, and therefore will be prevented::
 
   $ hg push --rev drinks
   pushing to $TESTTMP/server (glob)
@@ -431,7 +439,8 @@
   [255]
 
 
-Even after a rebase Pushing all active topics at the same time will complains about the multiple heads it would create on that branch::
+Even after a rebase, pushing all active topics at the same time will complain
+about the multiple heads it would create on that branch::
 
   $ hg rebase -b drinks
   rebasing 9:8dfa45bd5e0c "Adding apple juice"
@@ -447,7 +456,8 @@
   (merge or see 'hg help push' for details about pushing new heads)
   [255]
 
-Publishing only one of them is allowed (as long as it does not create a new branch head has we just saw in the previous case)::
+Publishing only one of them is allowed (as long as it does not create a new
+branch head as we just saw in the previous case)::
 
   $ hg push -r drinks
   pushing to $TESTTMP/server (glob)
@@ -458,7 +468,8 @@
   added 2 changesets with 2 changes to 1 files
   2 new obsolescence markers
 
-The publishing topic has now vanished, and the one still draft is now marked as "behind"::
+The published topic has now disappeared, and the other is now marked as
+"behind"::
 
   $ hg topic --verbose
    * tools (on branch: default, 3 changesets, 2 behind)
@@ -468,5 +479,5 @@
   t3@ Adding drill (current)
   t2: Adding saw
   t1: Adding hammer
-    ^ add a pair of shoes
+  t0^ add a pair of shoes (base)
 
--- a/tests/test-topic.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-topic.t	Tue Jul 25 03:59:22 2017 +0200
@@ -15,7 +15,28 @@
   $ hg help topics
   hg topics [TOPIC]
   
-  View current topic, set current topic, or see all topics.
+  View current topic, set current topic, change topic for a set of revisions, or
+  see all topics.
+  
+      Clear topic on existing topiced revisions:
+          'hg topic --rev <related revset> --clear'
+  
+      Change topic on some revisions:
+          'hg topic <newtopicname> --rev <related revset>'
+  
+      Clear current topic:
+          'hg topic --clear'
+  
+      Set current topic:
+          'hg topic <topicname>'
+  
+      List of topics:
+          'hg topics'
+  
+      List of topics with their last touched time sorted according to it:
+          'hg topic --age'
+  
+      The active topic (if any) will be prepended with a "*".
   
       The --verbose version of this command display various information on the
       state of each topic.
@@ -25,6 +46,7 @@
       --clear   clear active topic if any
    -r --rev REV revset of existing revisions
    -l --list    show the stack of changeset in the topic
+      --age     show when you last touched the topics
   
   (some details hidden, use --verbose to show complete help)
   $ hg topics
@@ -777,3 +799,115 @@
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     start on fran
   |
+
+Testing for updating to t0
+==========================
+
+  $ hg stack
+  ### topic: changewut (2 heads)
+  ### branch: default, 5 behind
+  t3: fran?
+  t1^ start on fran (base)
+  t2@ gamma (current)
+  t1: start on fran
+  t0^ Add file delta (base)
+  $ hg up t0
+  preserving the current topic 'changewut'
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg topic
+   * changewut
+  $ hg stack
+  ### topic: changewut (2 heads)
+  ### branch: default, 5 behind
+  t3: fran?
+  t1^ start on fran (base)
+  t2: gamma
+  t1: start on fran
+  t0^ Add file delta (base)
+
+  $ hg topics --age
+   * changewut (*) (glob)
+
+  $ cd ..
+
+Testing the new config knob to forbid untopiced commit
+======================================================
+
+  $ hg init ponky
+  $ cd ponky
+  $ cat <<EOF >> .hg/hgrc
+  > [phases]
+  > publish=false
+  > EOF
+  $ cat <<EOF >> $HGRCPATH
+  > [experimental]
+  > enforce-topic = yes
+  > EOF
+  $ touch a b c d
+  $ hg add a
+  $ hg ci -m "Added a"
+  abort: no active topic
+  (set a current topic or use '--config experimental.enforce-topic=no' to commit without a topic)
+  [255]
+
+(same test, checking we abort before the editor)
+
+  $ EDITOR=cat hg ci -m "Added a" --edit
+  abort: no active topic
+  (set a current topic or use '--config experimental.enforce-topic=no' to commit without a topic)
+  [255]
+  $ hg ci -m "added a" --config experimental.enforce-topic=no
+  $ hg log
+  changeset:   0:a154386e50d1
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     added a
+  
+  $ hg topic topic1970 --rev 0
+  switching to topic topic1970
+  changed topic on 1 changes
+  $ hg add b
+  $ hg topic topic1990
+  $ hg ci -m "Added b" --config devel.default-date="631152000 0" --date "631152000 0"
+  $ hg add c
+  $ hg topic topic2010
+  $ hg ci -m "Added c" --config devel.default-date="1262304000 0" --date "1262304000 0"
+  $ hg log
+  changeset:   3:9048b194797d
+  tag:         tip
+  topic:       topic2010
+  user:        test
+  date:        Fri Jan 01 00:00:00 2010 +0000
+  summary:     Added c
+  
+  changeset:   2:186d493c7f8d
+  topic:       topic1990
+  user:        test
+  date:        Mon Jan 01 00:00:00 1990 +0000
+  summary:     Added b
+  
+  changeset:   1:e5a30a141954
+  topic:       topic1970
+  parent:      -1:000000000000
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     added a
+  
+  $ hg topics
+     topic1970
+     topic1990
+   * topic2010
+  $ hg topics --age
+     topic1970 (*) (glob)
+   * topic2010 (2010-01-01)
+     topic1990 (1990-01-01)
+  $ hg up topic1970
+  switching to topic topic1970
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg topics --age
+   * topic1970 (*) (glob)
+     topic2010 (2010-01-01)
+     topic1990 (1990-01-01)
+
+  $ cd ..
--- a/tests/test-tutorial.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-tutorial.t	Tue Jul 25 03:59:22 2017 +0200
@@ -461,6 +461,12 @@
    -r --rev VALUE           revert commit content to REV instead
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
+   -m --message TEXT        use text as commit message
+   -l --logfile FILE        read commit message from file
+   -d --date DATE           record the specified date as commit date
+   -u --user USER           record the specified user as committer
+   -D --current-date        record the current date as commit date
+   -U --current-user        record the current user as committer
   
   (some details hidden, use --verbose to show complete help)
 
@@ -496,6 +502,8 @@
    -l --logfile FILE  read commit message from file
    -d --date DATE     record the specified date as commit date
    -u --user USER     record the specified user as committer
+   -D --current-date  record the current date as commit date
+   -U --current-user  record the current user as committer
   
   (some details hidden, use --verbose to show complete help)
 
--- a/tests/test-uncommit.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-uncommit.t	Tue Jul 25 03:59:22 2017 +0200
@@ -1,11 +1,10 @@
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
   $ glog() {
-  >   hg glog --template '{rev}:{node|short}@{branch}({separate("/", obsolete, phase)}) {desc|firstline}\n' "$@"
+  >   hg log -G --template '{rev}:{node|short}@{branch}({separate("/", obsolete, phase)}) {desc|firstline}\n' "$@"
   > }
 
   $ hg init repo
@@ -14,7 +13,8 @@
 Cannot uncommit null changeset
 
   $ hg uncommit
-  abort: cannot rewrite immutable changeset
+  abort: cannot uncommit the null revision
+  (no changeset checked out)
   [255]
 
 Cannot uncommit public changeset
@@ -23,7 +23,8 @@
   $ hg ci -Am adda a
   $ hg phase --public .
   $ hg uncommit
-  abort: cannot rewrite immutable changeset
+  abort: cannot uncommit public changesets: 07f494440405
+  (see 'hg help phases' for details)
   [255]
   $ hg phase --force --draft .
 
@@ -358,7 +359,103 @@
 
 Test uncommiting precursors
 
-  $ hg uncommit --hidden --rev 'precursors(.)' b
+  $ hg uncommit --hidden --rev 'precursors(.)' b --traceback
   $ hg cat b --rev .
   b
   b
+
+Test date, message and user update
+
+  $ hg log -r .
+  changeset:   12:912ed871207c
+  branch:      bar
+  tag:         tip
+  parent:      7:4f1c269eab68
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     touncommit
+  
+  $ hg uncommit -m 'to-uncommit' d --user test2 --date '1337 0'
+  $ hg log -r .
+  changeset:   13:f1efd9ec508c
+  branch:      bar
+  tag:         tip
+  parent:      7:4f1c269eab68
+  user:        test2
+  date:        Thu Jan 01 00:22:17 1970 +0000
+  summary:     to-uncommit
+  
+
+test -U option
+
+  $ hg uncommit -U b
+  $ hg log -r .
+  changeset:   14:288da4a95941
+  branch:      bar
+  tag:         tip
+  parent:      7:4f1c269eab68
+  user:        test
+  date:        Thu Jan 01 00:22:17 1970 +0000
+  summary:     to-uncommit
+  
+
+test the `hg amend --extract` entry point
+
+  $ hg status --change .
+  M j
+  M o
+  A e
+  A ff
+  A h
+  A k
+  A l
+  R c
+  R f
+  R g
+  R m
+  R n
+  $ hg status
+  M d
+  A aa
+  R b
+  $ hg amend --extract j
+  $ hg status --change .
+  M o
+  A e
+  A ff
+  A h
+  A k
+  A l
+  R c
+  R f
+  R g
+  R m
+  R n
+  $ hg status
+  M d
+  M j
+  A aa
+  R b
+
+(with all)
+
+  $ hg amend --extract --all
+  new changeset is empty
+  (use 'hg prune .' to remove it)
+  $ hg status --change .
+  $ hg status
+  M d
+  M j
+  M o
+  A aa
+  A e
+  A ff
+  A h
+  A k
+  A l
+  R b
+  R c
+  R f
+  R g
+  R m
+  R n
--- a/tests/test-unstable.t	Sun Jul 02 18:22:39 2017 +0200
+++ b/tests/test-unstable.t	Tue Jul 25 03:59:22 2017 +0200
@@ -13,7 +13,6 @@
   > [ui]
   > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n
   > [extensions]
-  > hgext.graphlog=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
   $ mkcommit() {