changeset 4077:9c025ec4af88 mercurial-4.3

test-compat: merge mercurial-4.4 into mercurial-4.3
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Mon, 03 Sep 2018 22:09:26 +0200
parents b609412b42b0 (current diff) f9a850018daa (diff)
children 185770c10a4e
files tests/test-discovery-obshashrange.t tests/test-evolve-abort-phasediv.t tests/test-evolve-issue5832.t tests/test-evolve-serveronly-bundle2.t tests/test-evolve-topic.t tests/test-evolve.t tests/test-grab.t tests/test-prune.t tests/test-stabilize-result.t tests/test-stack-branch.t tests/test-topic-rebase.t tests/test-topic-stack-complex.t tests/test-topic-stack-data.t tests/test-topic-stack.t tests/test-topic-tutorial.t tests/test-topic.t tests/test-tutorial.t tests/test-wireproto.t
diffstat 45 files changed, 1014 insertions(+), 678 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Tue Aug 28 11:25:32 2018 +0200
+++ b/.hgtags	Mon Sep 03 22:09:26 2018 +0200
@@ -69,3 +69,4 @@
 0887c30255a1a1808d74a63b16e896d457f8ef32 8.0.1
 2c5d79c6459c6fabe0eb8723fc5041ac0dac7a9a 8.1.0
 e7abf863e1130e14cd4d65e53467a199d267b4fd 8.1.1
+f1cde4c97806fc6d6cc4c1e09ea2f4081a3ebaec 8.1.2
--- a/CHANGELOG	Tue Aug 28 11:25:32 2018 +0200
+++ b/CHANGELOG	Mon Sep 03 22:09:26 2018 +0200
@@ -4,7 +4,23 @@
 8.1.2 - in progress
 -------------------
 
+  * prune: rename `--biject` flag to `--pair` (old flag is kept as an alias)
+  * pick: rename the "grab" command to "pick" to avoid ambiguity with graft
+  * discovery: enable obshashrange based discovery by default
+
+topic
+
+  * revset: `topic("patterns")` now handle standard patterns ("re:", etc)
+  * revset: `topic(REVS)` matches revisions with same topic as REVS
+  * topic: using `s#` alias instead of `t#` and `b#` alias
+           (compat with old form is preserved)
+
+8.1.2 -- 2018-08-28
+-------------------
+
   * obshashrange: improved robusness of the cache under heavy load
+  * obshashrange: force recomputation of the final obshash related cache
+                  (to make sure people benefit from the 8.1.1 fixes)
 
 8.1.1 -- 2018-08-21
 -------------------
--- a/README	Tue Aug 28 11:25:32 2018 +0200
+++ b/README	Mon Sep 03 22:09:26 2018 +0200
@@ -79,6 +79,22 @@
     [extensions]
     evolve.serveronly =
 
+Extension Purpose
+=================
+
+The goal of this extension is to provide an appropriate place for code and
+concept related to changeset evolution to mature. In this extension we allow
+for hackier code, unlocking quick experimentation and faster iterations.
+
+In addition, the evolve extensions support a wider set of Mercurial version,
+allowing us to reach a larger user base for feedback. The Evolve extension is
+not tight to the Mercurial release cycle and can release new feature and bug
+fix at a higher rate if necessary.
+
+Once a concept is ready enough, its implementation is moved into Mercurial
+core. The maturation period helped us to get a clearer picture of what was
+needed. During the upstreaming process, we can use this clearer picture to
+clean up the code and upgrade it to an appropriate quality for Mercurial core.
 
 How to Contribute
 =================
@@ -125,3 +141,7 @@
 In addition, we have compatibility branches to check tests on older version of
 Mercurial. They are the "mercurial-x.y" branches. They are used to apply
 expected test change only, no code change should happen there.
+
+test output change from a changeset in core should adds the following line to their description:
+
+CORE-TEST-OUTPUT-UPDATE: <CORE-NODE-ID>
--- a/debian/changelog	Tue Aug 28 11:25:32 2018 +0200
+++ b/debian/changelog	Mon Sep 03 22:09:26 2018 +0200
@@ -1,4 +1,10 @@
-mercurial-evolve (8.1.1) unstable; urgency=medium
+mercurial-evolve (8.1.2-1) unstable; urgency=medium
+
+  * new upstream release
+
+ -- Pierre-Yves David <pierre-yves.david@ens-lyon.org>  Tue, 28 Aug 2018 17:43:36 +0200
+
+mercurial-evolve (8.1.1-1) unstable; urgency=medium
 
   * new upstream release
 
--- a/hgext3rd/evolve/__init__.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/__init__.py	Mon Sep 03 22:09:26 2018 +0200
@@ -1403,9 +1403,9 @@
     cmdutil.unfinishedstates.append(data)
 
     afterresolved = ('evolvestate', _('hg evolve --continue'))
-    grabresolved = ('grabstate', _('hg grab --continue'))
+    pickresolved = ('pickstate', _('hg pick --continue'))
     cmdutil.afterresolvedstates.append(afterresolved)
-    cmdutil.afterresolvedstates.append(grabresolved)
+    cmdutil.afterresolvedstates.append(pickresolved)
 
     if util.safehasattr(cmdutil, 'STATES'):
         statedata = ('evolve', cmdutil.fileexistspredicate('evolvestate'),
--- a/hgext3rd/evolve/cmdrewrite.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/cmdrewrite.py	Mon Sep 03 22:09:26 2018 +0200
@@ -942,7 +942,9 @@
      ('r', 'rev', [], _("revisions to prune")),
      ('k', 'keep', None, _("does not modify working copy during prune")),
      ('n', 'note', '', _('store a note on prune')),
-     ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")),
+     ('', 'pair', False, _("record a pairing, such as a rebase or divergence resolution "
+                           "(pairing multiple precursors to multiple successors)")),
+     ('', 'biject', False, _("alias to --pair (DEPRECATED)")),
      ('', 'fold', False,
       _("record a fold (multiple precursors, one successors)")),
      ('', 'split', False,
@@ -972,7 +974,7 @@
     single successor, you must pass the ``--fold`` option.
 
     If you want to supersede multiple revisions at the same time, use
-    ``--biject`` option to pair the pruned precursor and successor changesets.
+    ``--pair`` option to pair the pruned precursor and successor changesets.
     This is commonly useful for resolving history divergence, or when someone
     else does edits history without obsolescence enabled.
     """
@@ -981,11 +983,11 @@
     succs = opts['new'] + opts['succ']
     bookmarks = set(opts.get('bookmark'))
     metadata = _getmetadata(**opts)
-    biject = opts.get('biject')
+    biject = opts.get('pair') or opts.get('biject')
     fold = opts.get('fold')
     split = opts.get('split')
 
-    options = [o for o in ('biject', 'fold', 'split') if opts.get(o)]
+    options = [o for o in ('pair', 'fold', 'split') if opts.get(o)]
     if 1 < len(options):
         raise error.Abort(_("can only specify one of %s") % ', '.join(options))
 
@@ -1020,7 +1022,7 @@
         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"
+            hint = _("use --pair to mark a series as a replacement"
                      " for another")
             raise error.Abort(msg, hint=hint)
         elif biject and len(sucs) != len(precs):
@@ -1314,13 +1316,13 @@
         lockmod.release(tr, lock, wlock)
 
 @eh.command(
-    'grab',
-    [('r', 'rev', '', 'revision to grab'),
-     ('c', 'continue', False, 'continue interrupted grab'),
-     ('a', 'abort', False, 'abort interrupted grab'),
+    'pick|grab',
+    [('r', 'rev', '', 'revision to pick'),
+     ('c', 'continue', False, 'continue interrupted pick'),
+     ('a', 'abort', False, 'abort interrupted pick'),
     ],
     _('[-r] rev'))
-def grab(ui, repo, *revs, **opts):
+def cmdpick(ui, repo, *revs, **opts):
     """grabs a commit, move it on the top of working directory parent and
     updates to it."""
 
@@ -1334,8 +1336,8 @@
     if opts.get('rev'):
         revs.append(opts['rev'])
 
-    with repo.wlock(), repo.lock(), repo.transaction('grab'):
-        grabstate = state.cmdstate(repo, path='grabstate')
+    with repo.wlock(), repo.lock(), repo.transaction('pick'):
+        pickstate = state.cmdstate(repo, path='pickstate')
         pctx = repo['.']
 
         if not cont and not abort:
@@ -1349,28 +1351,28 @@
             origctx = repo[revs.first()]
 
             if origctx in pctx.ancestors() or origctx.node() == pctx.node():
-                raise error.Abort(_("cannot grab an ancestor revision"))
+                raise error.Abort(_("cannot pick an ancestor revision"))
 
-            rewriteutil.precheck(repo, [origctx.rev()], 'grab')
+            rewriteutil.precheck(repo, [origctx.rev()], 'pick')
 
-            ui.status(_('grabbing %d:%s "%s"\n') %
+            ui.status(_('picking %d:%s "%s"\n') %
                       (origctx.rev(), origctx,
                        origctx.description().split("\n", 1)[0]))
             stats = merge.graft(repo, origctx, origctx.p1(), ['local',
                                                               'destination'])
             if compat.hasconflict(stats):
-                grabstate.addopts({'orignode': origctx.node(),
+                pickstate.addopts({'orignode': origctx.node(),
                                    'oldpctx': pctx.node()})
-                grabstate.save()
+                pickstate.save()
                 raise error.InterventionRequired(_("unresolved merge conflicts"
                                                    " (see hg help resolve)"))
 
         elif abort:
-            if not grabstate:
-                raise error.Abort(_("no interrupted grab state exists"))
-            grabstate.load()
-            pctxnode = grabstate['oldpctx']
-            ui.status(_("aborting grab, updating to %s\n") %
+            if not pickstate:
+                raise error.Abort(_("no interrupted pick state exists"))
+            pickstate.load()
+            pctxnode = pickstate['oldpctx']
+            ui.status(_("aborting pick, updating to %s\n") %
                       node.hex(pctxnode)[:12])
             hg.updaterepo(repo, pctxnode, True)
             return 0
@@ -1379,26 +1381,26 @@
             if revs:
                 raise error.Abort(_("cannot specify both --continue and "
                                     "revision"))
-            if not grabstate:
-                raise error.Abort(_("no interrupted grab state exists"))
+            if not pickstate:
+                raise error.Abort(_("no interrupted pick state exists"))
 
-            grabstate.load()
-            orignode = grabstate['orignode']
+            pickstate.load()
+            orignode = pickstate['orignode']
             origctx = repo[orignode]
 
         overrides = {('phases', 'new-commit'): origctx.phase()}
-        with repo.ui.configoverride(overrides, 'grab'):
+        with repo.ui.configoverride(overrides, 'pick'):
             newnode = repo.commit(text=origctx.description(),
                                   user=origctx.user(),
                                   date=origctx.date(), extra=origctx.extra())
 
-        if grabstate:
-            grabstate.delete()
+        if pickstate:
+            pickstate.delete()
         newctx = repo[newnode] if newnode else pctx
-        obsolete.createmarkers(repo, [(origctx, (newctx,))], operation="grab")
+        obsolete.createmarkers(repo, [(origctx, (newctx,))], operation="pick")
 
         if newnode is None:
-            ui.warn(_("note: grab of %d:%s created no changes to commit\n") %
+            ui.warn(_("note: picking %d:%s created no changes to commit\n") %
                     (origctx.rev(), origctx))
             return 0
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/dagutil.py	Mon Sep 03 22:09:26 2018 +0200
@@ -0,0 +1,287 @@
+# dagutil.py - dag utilities for mercurial
+#
+# Copyright 2010 Benoit Boissinot <bboissin@gmail.com>
+# and Peter Arrenbrecht <peter@arrenbrecht.ch>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+#
+# Imported from Mercurial code at cee9043c7dba
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+from mercurial.node import nullrev
+
+class basedag(object):
+    '''generic interface for DAGs
+
+    terms:
+    "ix" (short for index) identifies a nodes internally,
+    "id" identifies one externally.
+
+    All params are ixs unless explicitly suffixed otherwise.
+    Pluralized params are lists or sets.
+    '''
+
+    def __init__(self):
+        self._inverse = None
+
+    def nodeset(self):
+        '''set of all node ixs'''
+        raise NotImplementedError
+
+    def heads(self):
+        '''list of head ixs'''
+        raise NotImplementedError
+
+    def parents(self, ix):
+        '''list of parents ixs of ix'''
+        raise NotImplementedError
+
+    def inverse(self):
+        '''inverse DAG, where parents becomes children, etc.'''
+        raise NotImplementedError
+
+    def ancestorset(self, starts, stops=None):
+        '''
+        set of all ancestors of starts (incl), but stop walk at stops (excl)
+        '''
+        raise NotImplementedError
+
+    def descendantset(self, starts, stops=None):
+        '''
+        set of all descendants of starts (incl), but stop walk at stops (excl)
+        '''
+        return self.inverse().ancestorset(starts, stops)
+
+    def headsetofconnecteds(self, ixs):
+        '''
+        subset of connected list of ixs so that no node has a descendant in it
+
+        By "connected list" we mean that if an ancestor and a descendant are in
+        the list, then so is at least one path connecting them.
+        '''
+        raise NotImplementedError
+
+    def externalize(self, ix):
+        '''return a node id'''
+        return self._externalize(ix)
+
+    def externalizeall(self, ixs):
+        '''return a list of (or set if given a set) of node ids'''
+        ids = self._externalizeall(ixs)
+        if isinstance(ixs, set):
+            return set(ids)
+        return list(ids)
+
+    def internalize(self, id):
+        '''return a node ix'''
+        return self._internalize(id)
+
+    def internalizeall(self, ids, filterunknown=False):
+        '''return a list of (or set if given a set) of node ixs'''
+        ixs = self._internalizeall(ids, filterunknown)
+        if isinstance(ids, set):
+            return set(ixs)
+        return list(ixs)
+
+class genericdag(basedag):
+    '''generic implementations for DAGs'''
+
+    def ancestorset(self, starts, stops=None):
+        if stops:
+            stops = set(stops)
+        else:
+            stops = set()
+        seen = set()
+        pending = list(starts)
+        while pending:
+            n = pending.pop()
+            if n not in seen and n not in stops:
+                seen.add(n)
+                pending.extend(self.parents(n))
+        return seen
+
+    def headsetofconnecteds(self, ixs):
+        hds = set(ixs)
+        if not hds:
+            return hds
+        for n in ixs:
+            for p in self.parents(n):
+                hds.discard(p)
+        assert hds
+        return hds
+
+class revlogbaseddag(basedag):
+    '''generic dag interface to a revlog'''
+
+    def __init__(self, revlog, nodeset):
+        basedag.__init__(self)
+        self._revlog = revlog
+        self._heads = None
+        self._nodeset = nodeset
+
+    def nodeset(self):
+        return self._nodeset
+
+    def heads(self):
+        if self._heads is None:
+            self._heads = self._getheads()
+        return self._heads
+
+    def _externalize(self, ix):
+        return self._revlog.index[ix][7]
+
+    def _externalizeall(self, ixs):
+        idx = self._revlog.index
+        return [idx[i][7] for i in ixs]
+
+    def _internalize(self, id):
+        ix = self._revlog.rev(id)
+        if ix == nullrev:
+            raise LookupError(id, self._revlog.indexfile, _('nullid'))
+        return ix
+
+    def _internalizeall(self, ids, filterunknown):
+        rl = self._revlog
+        if filterunknown:
+            return [r for r in map(rl.nodemap.get, ids)
+                    if (r is not None
+                        and r != nullrev
+                        and r not in rl.filteredrevs)]
+        return [self._internalize(i) for i in ids]
+
+class revlogdag(revlogbaseddag):
+    '''dag interface to a revlog'''
+
+    def __init__(self, revlog, localsubset=None):
+        revlogbaseddag.__init__(self, revlog, set(revlog))
+        self._heads = localsubset
+
+    def _getheads(self):
+        return [r for r in self._revlog.headrevs() if r != nullrev]
+
+    def parents(self, ix):
+        rlog = self._revlog
+        idx = rlog.index
+        revdata = idx[ix]
+        prev = revdata[5]
+        if prev != nullrev:
+            prev2 = revdata[6]
+            if prev2 == nullrev:
+                return [prev]
+            return [prev, prev2]
+        prev2 = revdata[6]
+        if prev2 != nullrev:
+            return [prev2]
+        return []
+
+    def inverse(self):
+        if self._inverse is None:
+            self._inverse = inverserevlogdag(self)
+        return self._inverse
+
+    def ancestorset(self, starts, stops=None):
+        rlog = self._revlog
+        idx = rlog.index
+        if stops:
+            stops = set(stops)
+        else:
+            stops = set()
+        seen = set()
+        pending = list(starts)
+        while pending:
+            rev = pending.pop()
+            if rev not in seen and rev not in stops:
+                seen.add(rev)
+                revdata = idx[rev]
+                for i in [5, 6]:
+                    prev = revdata[i]
+                    if prev != nullrev:
+                        pending.append(prev)
+        return seen
+
+    def headsetofconnecteds(self, ixs):
+        if not ixs:
+            return set()
+        rlog = self._revlog
+        idx = rlog.index
+        headrevs = set(ixs)
+        for rev in ixs:
+            revdata = idx[rev]
+            for i in [5, 6]:
+                prev = revdata[i]
+                if prev != nullrev:
+                    headrevs.discard(prev)
+        assert headrevs
+        return headrevs
+
+    def linearize(self, ixs):
+        '''linearize and topologically sort a list of revisions
+
+        The linearization process tries to create long runs of revs where
+        a child rev comes immediately after its first parent. This is done by
+        visiting the heads of the given revs in inverse topological order,
+        and for each visited rev, visiting its second parent, then its first
+        parent, then adding the rev itself to the output list.
+        '''
+        sorted = []
+        visit = list(self.headsetofconnecteds(ixs))
+        visit.sort(reverse=True)
+        finished = set()
+
+        while visit:
+            cur = visit.pop()
+            if cur < 0:
+                cur = -cur - 1
+                if cur not in finished:
+                    sorted.append(cur)
+                    finished.add(cur)
+            else:
+                visit.append(-cur - 1)
+                visit += [p for p in self.parents(cur)
+                          if p in ixs and p not in finished]
+        assert len(sorted) == len(ixs)
+        return sorted
+
+class inverserevlogdag(revlogbaseddag, genericdag):
+    '''inverse of an existing revlog dag; see revlogdag.inverse()'''
+
+    def __init__(self, orig):
+        revlogbaseddag.__init__(self, orig._revlog, orig._nodeset)
+        self._orig = orig
+        self._children = {}
+        self._roots = []
+        self._walkfrom = len(self._revlog) - 1
+
+    def _walkto(self, walkto):
+        rev = self._walkfrom
+        cs = self._children
+        roots = self._roots
+        idx = self._revlog.index
+        while rev >= walkto:
+            data = idx[rev]
+            isroot = True
+            for prev in [data[5], data[6]]: # parent revs
+                if prev != nullrev:
+                    cs.setdefault(prev, []).append(rev)
+                    isroot = False
+            if isroot:
+                roots.append(rev)
+            rev -= 1
+        self._walkfrom = rev
+
+    def _getheads(self):
+        self._walkto(nullrev)
+        return self._roots
+
+    def parents(self, ix):
+        if ix is None:
+            return []
+        if ix <= self._walkfrom:
+            self._walkto(ix)
+        return self._children.get(ix, [])
+
+    def inverse(self):
+        return self._orig
--- a/hgext3rd/evolve/depthcache.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/depthcache.py	Mon Sep 03 22:09:26 2018 +0200
@@ -10,11 +10,9 @@
 from __future__ import absolute_import
 
 import array
-import weakref
 
 from mercurial import (
     localrepo,
-    util,
     scmutil,
 )
 
@@ -85,30 +83,12 @@
                 self.depthcache.clear()
             super(depthcacherepo, self).destroyed()
 
-        if util.safehasattr(repo, 'updatecaches'):
-            @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None, **kwargs):
-                if utility.shouldwarmcache(self, tr):
-                    self.depthcache.update(self)
-                    self.depthcache.save(self)
-                super(depthcacherepo, self).updatecaches(tr, **kwargs)
-
-        else:
-            def transaction(self, *args, **kwargs):
-                tr = super(depthcacherepo, self).transaction(*args, **kwargs)
-                reporef = weakref.ref(self)
-
-                def _warmcache(tr):
-                    repo = reporef()
-                    if repo is None:
-                        return
-                    repo = repo.unfiltered()
-                    repo.depthcache.update(repo)
-                    repo.depthcache.save(repo)
-
-                if utility.shouldwarmcache(self, tr):
-                    tr.addpostclose('warmcache-00depthcache', _warmcache)
-                return tr
+        @localrepo.unfilteredmethod
+        def updatecaches(self, tr=None, **kwargs):
+            if utility.shouldwarmcache(self, tr):
+                self.depthcache.update(self)
+                self.depthcache.save(self)
+            super(depthcacherepo, self).updatecaches(tr, **kwargs)
 
     repo.__class__ = depthcacherepo
 
--- a/hgext3rd/evolve/evolvecmd.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/evolvecmd.py	Mon Sep 03 22:09:26 2018 +0200
@@ -230,7 +230,7 @@
 
     # Checking for whether the phase-divergent changeset has common parents as
     # it's precursors. Phase-divergent changeset and precursor having different
-    # parents is a result of when the changeset is rebased, grabbed, histedit or
+    # parents is a result of when the changeset is rebased, picked, histedit or
     # evolved or any other operation which can change parent. In such cases,
     # when parents are not same, we first rebase the divergent changeset onto
     # parent or precursor and then perform later steps
--- a/hgext3rd/evolve/exthelper.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/exthelper.py	Mon Sep 03 22:09:26 2018 +0200
@@ -11,12 +11,6 @@
     util,
 )
 
-if util.safehasattr(registrar, 'command'):
-    command = registrar.command
-else: # compat with hg < 4.3
-    from mercurial import cmdutil
-    command = cmdutil.command
-
 configitem = None
 dynamicdefault = None
 if util.safehasattr(registrar, 'configitem'):
@@ -44,7 +38,7 @@
         self._functionwrappers = []
         self._duckpunchers = []
         self.cmdtable = {}
-        self.command = command(self.cmdtable)
+        self.command = registrar.command(self.cmdtable)
 
         self.configtable = {}
         self._configitem = None
--- a/hgext3rd/evolve/firstmergecache.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/firstmergecache.py	Mon Sep 03 22:09:26 2018 +0200
@@ -10,12 +10,10 @@
 from __future__ import absolute_import
 
 import array
-import weakref
 
 from mercurial import (
     localrepo,
     node as nodemod,
-    util,
 )
 
 from . import (
@@ -47,30 +45,12 @@
                 self.firstmergecache.clear()
             super(firstmergecacherepo, self).destroyed()
 
-        if util.safehasattr(repo, 'updatecaches'):
-            @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None, **kwargs):
-                if utility.shouldwarmcache(self, tr):
-                    self.firstmergecache.update(self)
-                    self.firstmergecache.save(self)
-                super(firstmergecacherepo, self).updatecaches(tr, **kwargs)
-
-        else:
-            def transaction(self, *args, **kwargs):
-                tr = super(firstmergecacherepo, self).transaction(*args, **kwargs)
-                reporef = weakref.ref(self)
-
-                def _warmcache(tr):
-                    repo = reporef()
-                    if repo is None:
-                        return
-                    repo = repo.unfiltered()
-                    repo.firstmergecache.update(repo)
-                    repo.firstmergecache.save(repo)
-
-                if utility.shouldwarmcache(self, tr):
-                    tr.addpostclose('warmcache-01-firstparentcache', _warmcache)
-                return tr
+        @localrepo.unfilteredmethod
+        def updatecaches(self, tr=None, **kwargs):
+            if utility.shouldwarmcache(self, tr):
+                self.firstmergecache.update(self)
+                self.firstmergecache.save(self)
+            super(firstmergecacherepo, self).updatecaches(tr, **kwargs)
 
     repo.__class__ = firstmergecacherepo
 
--- a/hgext3rd/evolve/hack/drophack.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/hack/drophack.py	Mon Sep 03 22:09:26 2018 +0200
@@ -21,12 +21,7 @@
 
 cmdtable = {}
 
-if util.safehasattr(registrar, 'command'):
-    command = registrar.command(cmdtable)
-else: # compat with hg < 4.3
-    from mercurial import cmdutil
-    command = cmdutil.command(cmdtable)
-
+command = registrar.command(cmdtable)
 
 @contextlib.contextmanager
 def timed(ui, caption):
--- a/hgext3rd/evolve/legacy.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/legacy.py	Mon Sep 03 22:09:26 2018 +0200
@@ -36,11 +36,7 @@
     # compat with hg < 4.6
     from mercurial.util import makedate
 
-if util.safehasattr(registrar, 'command'):
-    commandfunc = registrar.command
-else: # compat with hg < 4.3
-    from mercurial import cmdutil
-    commandfunc = cmdutil.command
+commandfunc = registrar.command
 
 #####################################################################
 ### Older format management                                       ###
--- a/hgext3rd/evolve/metadata.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/metadata.py	Mon Sep 03 22:09:26 2018 +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__ = '8.1.2.dev'
+__version__ = '8.2.0.dev'
 testedwith = '4.3.2 4.4.2 4.5.2 4.6.2 4.7'
 minimumhgversion = '4.3'
 buglink = 'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obscache.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/obscache.py	Mon Sep 03 22:09:26 2018 +0200
@@ -10,7 +10,6 @@
 import errno
 import hashlib
 import struct
-import weakref
 
 from mercurial import (
     localrepo,
@@ -478,31 +477,10 @@
                 self.obsstore.obscache.clear()
             super(obscacherepo, self).destroyed()
 
-        if util.safehasattr(repo, 'updatecaches'):
-            @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None, **kwargs):
-                super(obscacherepo, self).updatecaches(tr, **kwargs)
-                self.obsstore.obscache.update(self)
-                self.obsstore.obscache.save(self)
-
-        else:
-            def transaction(self, *args, **kwargs):
-                tr = super(obscacherepo, self).transaction(*args, **kwargs)
-                reporef = weakref.ref(self)
-
-                def _warmcache(tr):
-                    repo = reporef()
-                    if repo is None:
-                        return
-                    repo = repo.unfiltered()
-                    # As pointed in 'obscache.update', we could have the changelog
-                    # and the obsstore in charge of updating the cache when new
-                    # items goes it. The tranaction logic would then only be
-                    # involved for the 'pending' and final writing on disk.
-                    self.obsstore.obscache.update(repo)
-                    self.obsstore.obscache.save(repo)
-
-                tr.addpostclose('warmcache-obscache', _warmcache)
-                return tr
+        @localrepo.unfilteredmethod
+        def updatecaches(self, tr=None, **kwargs):
+            super(obscacherepo, self).updatecaches(tr, **kwargs)
+            self.obsstore.obscache.update(self)
+            self.obsstore.obscache.save(self)
 
     repo.__class__ = obscacherepo
--- a/hgext3rd/evolve/obsdiscovery.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/obsdiscovery.py	Mon Sep 03 22:09:26 2018 +0200
@@ -29,7 +29,6 @@
 import weakref
 
 from mercurial import (
-    dagutil,
     error,
     exchange,
     extensions,
@@ -60,6 +59,12 @@
     wireprotov1server = wireprototypes
     from mercurial.wireproto import wirepeer, encodelist, decodelist
 
+try:
+    from mercurial import dagutil
+    dagutil.revlogdag
+except (ImportError, AttributeError): # <= hg-4.7
+    from . import dagutil
+
 _pack = struct.pack
 _unpack = struct.unpack
 _calcsize = struct.calcsize
@@ -394,7 +399,7 @@
 
 class _obshashcache(obscache.dualsourcecache):
 
-    _schemaversion = 2
+    _schemaversion = 3
 
     _cachename = 'evo-ext-obshashrange' # used for error message
     _filename = 'cache/evoext_obshashrange_v2.sqlite'
@@ -662,30 +667,12 @@
                 self.obsstore.rangeobshashcache.clear()
             super(obshashrepo, self).destroyed()
 
-        if util.safehasattr(repo, 'updatecaches'):
-            @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None, **kwargs):
-                if utility.shouldwarmcache(self, tr):
-                    self.obsstore.rangeobshashcache.update(self)
-                    self.obsstore.rangeobshashcache.save(self)
-                super(obshashrepo, self).updatecaches(tr, **kwargs)
-
-        else:
-            def transaction(self, *args, **kwargs):
-                tr = super(obshashrepo, self).transaction(*args, **kwargs)
-                reporef = weakref.ref(self)
-
-                def _warmcache(tr):
-                    repo = reporef()
-                    if repo is None:
-                        return
-                    repo = repo.unfiltered()
-                    repo.obsstore.rangeobshashcache.update(repo)
-                    repo.obsstore.rangeobshashcache.save(repo)
-
-                if utility.shouldwarmcache(self, tr):
-                    tr.addpostclose('warmcache-20obshashrange', _warmcache)
-                return tr
+        @localrepo.unfilteredmethod
+        def updatecaches(self, tr=None, **kwargs):
+            if utility.shouldwarmcache(self, tr):
+                self.obsstore.rangeobshashcache.update(self)
+                self.obsstore.rangeobshashcache.save(self)
+            super(obshashrepo, self).updatecaches(tr, **kwargs)
 
     repo.__class__ = obshashrepo
 
@@ -750,7 +737,7 @@
     return encodelist(hashes)
 
 def _useobshashrange(repo):
-    base = repo.ui.configbool('experimental', 'obshashrange', False)
+    base = repo.ui.configbool('experimental', 'obshashrange', True)
     if base:
         maxrevs = repo.ui.configint('experimental', 'obshashrange.max-revs', None)
         if maxrevs is not None and maxrevs < len(repo.unfiltered()):
--- a/hgext3rd/evolve/stablerangecache.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/stablerangecache.py	Mon Sep 03 22:09:26 2018 +0200
@@ -12,7 +12,6 @@
 import random
 import sqlite3
 import time
-import weakref
 
 from mercurial import (
     error,
@@ -32,6 +31,33 @@
 
 eh = exthelper.exthelper()
 
+LONG_WARNING_TIME = 60
+
+LONG_MESSAGE = """Stable range cache is taking a while to load
+
+Your repository is probably big.
+
+Stable range are used for discovery missing osbsolescence markers during
+exchange. While the algorithm we use can scale well for large repositories, the
+naive python implementation that you are using is not very efficient, the
+storage backend for that cache neither.
+
+This computation will finish in a finite amount of time, even for repositories
+with millions of revision and many merges. However It might take multiple tens
+of minutes to complete in such case.
+
+In the future, better implementation of the algorithm in a more appropriate
+language than Python will make it much faster. This data should also get
+exchanged between server and clients removing recomputation needs.
+
+In the mean time, got take a break while this cache is warming.
+
+See `hg help -e evolve` for details about how to control obsmarkers discovery and
+the update of related cache.
+
+--- Sorry for the delay ---
+"""
+
 class stablerangeondiskbase(stablerange.stablerangecached,
                             genericcaches.changelogsourcebase):
     """combine the generic stablerange cache usage with generic changelog one
@@ -57,7 +83,8 @@
         # progress report is showing up in the profile for small and fast
         # repository so we only update it every so often
         progress_each = 100
-        progress_last = time.time()
+        initial_time = progress_last = time.time()
+        warned_long = False
         heapify(rangeheap)
         while rangeheap:
             rangeid = heappop(rangeheap)
@@ -65,6 +92,8 @@
                 if not seen % progress_each:
                     # if a lot of time passed, report more often
                     progress_new = time.time()
+                    if not warned_long and LONG_WARNING_TIME < (progress_new - initial_time):
+                        repo.ui.warn(LONG_MESSAGE)
                     if (1 < progress_each) and (0.1 < progress_new - progress_last):
                         progress_each /= 10
                     ui.progress(_("filling stablerange cache"), seen,
@@ -403,30 +432,12 @@
                 del self.stablerange
             super(stablerangerepo, self).destroyed()
 
-        if util.safehasattr(repo, 'updatecaches'):
-            @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None, **kwargs):
-                if utility.shouldwarmcache(self, tr):
-                    self.stablerange.update(self)
-                    self.stablerange.save(self)
-                super(stablerangerepo, self).updatecaches(tr, **kwargs)
-
-        else:
-            def transaction(self, *args, **kwargs):
-                tr = super(stablerangerepo, self).transaction(*args, **kwargs)
-                reporef = weakref.ref(self)
-
-                def _warmcache(tr):
-                    repo = reporef()
-                    if repo is None:
-                        return
-                    repo = repo.unfiltered()
-                    repo.stablerange.update(repo)
-                    repo.stablerange.save(repo)
-
-                if utility.shouldwarmcache(self, tr):
-                    tr.addpostclose('warmcache-10stablerange', _warmcache)
-                return tr
+        @localrepo.unfilteredmethod
+        def updatecaches(self, tr=None, **kwargs):
+            if utility.shouldwarmcache(self, tr):
+                self.stablerange.update(self)
+                self.stablerange.save(self)
+            super(stablerangerepo, self).updatecaches(tr, **kwargs)
 
     repo.__class__ = stablerangerepo
 
--- a/hgext3rd/evolve/stablesort.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/stablesort.py	Mon Sep 03 22:09:26 2018 +0200
@@ -10,7 +10,6 @@
 import array
 import collections
 import struct
-import weakref
 
 from mercurial import (
     commands,
@@ -18,7 +17,6 @@
     error,
     node as nodemod,
     scmutil,
-    util,
 )
 
 from mercurial.i18n import _
@@ -669,30 +667,12 @@
                 self.stablesort.clear()
             super(stablesortrepo, self).destroyed()
 
-        if util.safehasattr(repo, 'updatecaches'):
-            @localrepo.unfilteredmethod
-            def updatecaches(self, tr=None, **kwargs):
-                if utility.shouldwarmcache(self, tr):
-                    self.stablesort.update(self)
-                    self.stablesort.save(self)
-                super(stablesortrepo, self).updatecaches(tr, **kwargs)
-
-        else:
-            def transaction(self, *args, **kwargs):
-                tr = super(stablesortrepo, self).transaction(*args, **kwargs)
-                reporef = weakref.ref(self)
-
-                def _warmcache(tr):
-                    repo = reporef()
-                    if repo is None:
-                        return
-                    repo = repo.unfiltered()
-                    repo.stablesort.update(repo)
-                    repo.stablesort.save(repo)
-
-                if utility.shouldwarmcache(self, tr):
-                    tr.addpostclose('warmcache-02stablesort', _warmcache)
-                return tr
+        @localrepo.unfilteredmethod
+        def updatecaches(self, tr=None, **kwargs):
+            if utility.shouldwarmcache(self, tr):
+                self.stablesort.update(self)
+                self.stablesort.save(self)
+            super(stablesortrepo, self).updatecaches(tr, **kwargs)
 
     repo.__class__ = stablesortrepo
 
--- a/hgext3rd/evolve/state.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/state.py	Mon Sep 03 22:09:26 2018 +0200
@@ -28,7 +28,7 @@
 from mercurial.i18n import _
 
 class cmdstate():
-    """a wrapper class to store the state of commands like `evolve`, `grab`
+    """a wrapper class to store the state of commands like `evolve`, `pick`
 
     All the data for the state is stored in the form of key-value pairs in a
     dictionary.
--- a/hgext3rd/evolve/templatekw.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/templatekw.py	Mon Sep 03 22:09:26 2018 +0200
@@ -24,17 +24,6 @@
 eh = exthelper.exthelper()
 
 ### template keywords
-# XXX it does not handle troubles well :-/
-
-if not util.safehasattr(templatekw, 'showobsolete'):
-    # hg < 4.2
-    @eh.templatekw('obsolete')
-    def obsoletekw(repo, ctx, templ, **args):
-        """String. Whether the changeset is ``obsolete``.
-        """
-        if ctx.obsolete():
-            return 'obsolete'
-        return ''
 
 if util.safehasattr(templatekw, 'compatlist'):
     @eh.templatekw('troubles', requires=set(['ctx', 'templ']))
--- a/hgext3rd/evolve/utility.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/evolve/utility.py	Mon Sep 03 22:09:26 2018 +0200
@@ -64,7 +64,7 @@
         # note: we should not get to the default case
         warm = configbool('experimental', 'obshashrange.warm-cache', True)
 
-    if not configbool('experimental', 'obshashrange', False):
+    if not configbool('experimental', 'obshashrange', True):
         return False
     if not warm:
         return False
--- a/hgext3rd/topic/__init__.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/topic/__init__.py	Mon Sep 03 22:09:26 2018 +0200
@@ -29,8 +29,10 @@
 your current topic.
 
 Topic is offering you aliases reference to changeset in your current topic
-stack as 't#'. For example, 't1' refers to the root of your stack, 't2' to the
-second commits, etc. The 'hg stack' command show these number.
+stack as 's#'. For example, 's1' refers to the root of your stack, 's2' to the
+second commits, etc. The 'hg stack' command show these number. 's0' can be used
+to refer to the parent of the topic root. Updating using `hg up s0` will keep
+the topic active.
 
 Push behavior will change a bit with topic. When pushing to a publishing
 repository the changesets will turn public and the topic data on them will fade
@@ -132,7 +134,6 @@
     registrar,
     scmutil,
     templatefilters,
-    templatekw,
     util,
 )
 
@@ -149,13 +150,8 @@
     topicmap,
 )
 
-if util.safehasattr(registrar, 'command'):
-    commandfunc = registrar.command
-else: # compat with hg < 4.3
-    commandfunc = cmdutil.command
-
 cmdtable = {}
-command = commandfunc(cmdtable)
+command = registrar.command(cmdtable)
 colortable = {'topic.active': 'green',
               'topic.list.troubledcount': 'red',
               'topic.list.headcount.multiple': 'yellow',
@@ -181,7 +177,7 @@
               'topic.active': 'green',
              }
 
-__version__ = '0.10.1.dev'
+__version__ = '0.11.0.dev'
 
 testedwith = '4.3.3 4.4.2 4.5.2 4.6.2 4.7'
 minimumhgversion = '4.3'
@@ -231,6 +227,8 @@
                       default=None,
             )
 
+templatekeyword = registrar.templatekeyword()
+
 def _contexttopic(self, force=False):
     if not (force or self.mutable()):
         return ''
@@ -240,8 +238,8 @@
 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.
+        # XXX we might want to include s0 here,
+        # however s0 is related to  'currenttopic' which has no place here.
         return None
     revlist = stack.stack(self._repo, topic=topic)
     try:
@@ -257,12 +255,23 @@
         return None
 context.basectx.topicidx = _contexttopicidx
 
+stackrev = re.compile(r'^s\d+$')
 topicrev = re.compile(r'^t\d+$')
 branchrev = re.compile(r'^b\d+$')
 
 def _namemap(repo, name):
     revs = None
-    if topicrev.match(name):
+    if stackrev.match(name):
+        idx = int(name[1:])
+        tname = topic = repo.currenttopic
+        if topic:
+            ttype = 'topic'
+            revs = list(stack.stack(repo, topic=topic))
+        else:
+            ttype = 'branch'
+            tname = branch = repo[None].branch()
+            revs = list(stack.stack(repo, branch=branch))
+    elif topicrev.match(name):
         idx = int(name[1:])
         ttype = 'topic'
         tname = topic = repo.currenttopic
@@ -279,9 +288,12 @@
         try:
             r = revs[idx]
         except IndexError:
-            msg = _('cannot resolve "%s": %s "%s" has only %d changesets')
+            if ttype == 'topic':
+                msg = _('cannot resolve "%s": %s "%s" has only %d changesets')
+            elif ttype == 'branch':
+                msg = _('cannot resolve "%s": %s "%s" has only %d non-public changesets')
             raise error.Abort(msg % (name, ttype, tname, len(revs) - 1))
-        # b0 or t0 can be None
+        # b0 or t0 or s0 can be None
         if r == -1 and idx == 0:
             msg = _('the %s "%s" has no %s')
             raise error.Abort(msg % (ttype, tname, name))
@@ -323,7 +335,7 @@
 
     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
+    # We need to check whether t0 or b0 or s0 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().
@@ -338,7 +350,6 @@
 
     cmdutil.summaryhooks.add('topic', summaryhook)
 
-    templatekw.keywords['topic'] = topickw
     # Wrap workingctx extra to return the topic name
     extensions.wrapfunction(context.workingctx, '__init__', wrapinit)
     # Wrap changelog.add to drop empty topic
@@ -510,9 +521,11 @@
             'topics', 'topic', namemap=_namemap, nodemap=_nodemap,
             listnames=lambda repo: repo.topics))
 
-def topickw(**args):
+@templatekeyword('topic', requires={'ctx'})
+def topickw(context, mapping):
     """:topic: String. The topic of the changeset"""
-    return args['ctx'].topic()
+    ctx = context.resource(mapping, 'ctx')
+    return ctx.topic()
 
 def wrapinit(orig, self, repo, *args, **kwargs):
     orig(self, repo, *args, **kwargs)
@@ -647,9 +660,9 @@
 
     ct = repo.currenttopic
     if clear:
-        empty = stack.stack(repo, topic=ct).changesetcount == 0
-        if empty:
-            if ct:
+        if ct:
+            empty = stack.stack(repo, topic=ct).changesetcount == 0
+            if empty:
                 ui.status(_('clearing empty topic "%s"\n') % ct)
         return _changecurrenttopic(repo, None)
 
@@ -1152,7 +1165,6 @@
         # rebased commit. We have explicitly stored in config if rebase is
         # running.
         ot = repo.currenttopic
-        empty = stack.stack(repo, topic=ot).changesetcount == 0
         if repo.ui.hasconfig('experimental', 'topicrebase'):
             isrebase = True
         if repo.ui.configbool('_internal', 'keep-topic'):
@@ -1166,8 +1178,10 @@
                 f.write(t)
             if t and t != ot:
                 repo.ui.status(_("switching to topic %s\n") % t)
-            if ot and not t and empty:
-                repo.ui.status(_('clearing empty topic "%s"\n') % ot)
+            if ot and not t:
+                empty = stack.stack(repo, topic=ot).changesetcount == 0
+                if empty:
+                    repo.ui.status(_('clearing empty topic "%s"\n') % ot)
         elif ist0:
             repo.ui.status(_("preserving the current topic '%s'\n") % ot)
         return ret
@@ -1176,7 +1190,7 @@
 
 def checkt0(orig, ui, repo, node=None, rev=None, *args, **kwargs):
 
-    thezeros = set(['t0', 'b0'])
+    thezeros = set(['t0', 'b0', 's0'])
     backup = repo.ui.backupconfig('_internal', 'keep-topic')
     try:
         if node in thezeros or rev in thezeros:
--- a/hgext3rd/topic/destination.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/topic/destination.py	Mon Sep 03 22:09:26 2018 +0200
@@ -59,7 +59,10 @@
     """decide on an update destination from current topic"""
     movemark = node = None
     topic = repo.currenttopic
-    revs = repo.revs('.::topic("%s")' % topic)
+    if topic:
+        revs = repo.revs('.::topic(%s)', topic)
+    else:
+        revs = []
     if not revs:
         return None, None, None
     node = revs.last()
--- a/hgext3rd/topic/revset.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/topic/revset.py	Mon Sep 03 22:09:26 2018 +0200
@@ -1,13 +1,13 @@
 from __future__ import absolute_import
 
 from mercurial import (
+    error,
     registrar,
     revset,
     util,
 )
 
 from . import (
-    constants,
     destination,
     stack,
 )
@@ -23,36 +23,64 @@
 
 revsetpredicate = registrar.revsetpredicate()
 
-@revsetpredicate('topic([topic])')
-def topicset(repo, subset, x):
-    """Specified topic or all changes with any topic specified.
+def getstringstrict(x, err):
+    if x and (x[0] == 'string'):
+        return x[1]
+    raise error.ParseError(err)
 
-    If `topic` starts with `re:` the remainder of the name is treated
+@revsetpredicate('topic([string or set])')
+def topicset(repo, subset, x):
+    """All changesets with the specified topic or the topics of the given
+    changesets. Without the argument, all changesets with any topic specified.
+
+    If `string` starts with `re:` the remainder of the name is treated
     as a regular expression.
-
-    TODO: make `topic(revset)` work the same as `branch(revset)`.
     """
     args = revset.getargs(x, 0, 1, 'topic takes one or no arguments')
-    if args:
-        # match a specific topic
-        topic = revset.getstring(args[0], 'topic() argument must be a string')
-        if topic == '.':
-            topic = repo['.'].extra().get('topic', '')
-        _kind, _pattern, matcher = mkmatcher(topic)
-    else:
-        matcher = lambda t: bool(t)
 
     mutable = revset._notpublic(repo, revset.fullreposet(repo), ())
 
-    rawchange = repo.changelog.changelogrevision
-    key = constants.extrakey
+    if not args:
+        return (subset & mutable).filter(lambda r: bool(repo[r].topic()))
+
+    try:
+        topic = getstringstrict(args[0], '')
+    except error.ParseError:
+        # not a string, but another revset
+        pass
+    else:
+        kind, pattern, matcher = mkmatcher(topic)
+
+        def matches(r):
+            topic = repo[r].topic()
+            if not topic:
+                return False
+            return matcher(topic)
+
+        if kind == 'literal':
+            # note: falls through to the revset case if no topic with this name
+            # exists and pattern kind is not specified explicitly
 
-    def matchtopic(r):
-        topic = rawchange(r).extra.get(key)
-        if topic is None:
+            if pattern not in repo.topics and topic.startswith('literal:'):
+                raise error.RepoLookupError("topic '%s' does not exist"
+                                            % pattern)
+            return (subset & mutable).filter(matches)
+        else:
+            return (subset & mutable).filter(matches)
+
+    s = revset.getset(repo, revset.fullreposet(repo), x)
+    topics = set(repo[r].topic() for r in s)
+    topics.discard('')
+
+    def matches(r):
+        if r in s:
+            return True
+        topic = repo[r].topic()
+        if not topic:
             return False
-        return matcher(topic)
-    return (subset & mutable).filter(matchtopic)
+        return topic in topics
+
+    return (subset & mutable).filter(matches)
 
 @revsetpredicate('ngtip([branch])')
 def ngtipset(repo, subset, x):
--- a/hgext3rd/topic/stack.py	Tue Aug 28 11:25:32 2018 +0200
+++ b/hgext3rd/topic/stack.py	Mon Sep 03 22:09:26 2018 +0200
@@ -239,11 +239,11 @@
         msg %= (branch, topic)
         raise error.ProgrammingError(msg)
     elif topic is not None:
-        prefix = 't'
+        prefix = 's'
         if topic not in repo.topics:
             raise error.Abort(_('cannot resolve "%s": no such topic found') % topic)
     elif branch is not None:
-        prefix = 'b'
+        prefix = 's'
     else:
         raise error.ProgrammingError('neither branch and topic specified (not defined yet)')
 
--- a/tests/test-discovery-obshashrange.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-discovery-obshashrange.t	Mon Sep 03 22:09:26 2018 +0200
@@ -1064,3 +1064,31 @@
              5 c8d03c1b5e94            5            1            6 446c2dc3bce5
              6 f69452c5b1af            6            1            7 000000000000
 
+Cache warming capabilities
+--------------------------
+
+  $ hg config experimental.obshashrange
+  1
+  $ hg config experimental.obshashrange.warm-cache
+  [1]
+  $ hg debugupdatecache
+  $ ls -1 .hg/cache/evoext*
+  .hg/cache/evoext-depthcache-00
+  .hg/cache/evoext-firstmerge-00
+  .hg/cache/evoext-obscache-00
+  .hg/cache/evoext-stablesortcache-00
+  .hg/cache/evoext_obshashrange_v2.sqlite
+  .hg/cache/evoext_stablerange_v2.sqlite
+  $ rm -f .hg/cache/evoext*
+  $ ls -1 .hg/cache/ | grep evoext
+  [1]
+  $ hg debugupdatecache --debug
+  updating the branch cache
+  invalid branchheads cache (served): tip differs
+  $ f -s .hg/cache/evoext*
+  .hg/cache/evoext-depthcache-00: size=96
+  .hg/cache/evoext-firstmerge-00: size=96
+  .hg/cache/evoext-obscache-00: size=73
+  .hg/cache/evoext-stablesortcache-00: size=100
+  .hg/cache/evoext_obshashrange_v2.sqlite: size=??* (glob)
+  .hg/cache/evoext_stablerange_v2.sqlite: size=??* (glob)
--- a/tests/test-evolve-abort-phasediv.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-evolve-abort-phasediv.t	Mon Sep 03 22:09:26 2018 +0200
@@ -43,8 +43,8 @@
 
   $ hg up .^^^
   0 files updated, 0 files merged, 3 files removed, 0 files unresolved
-  $ hg grab -r .~-3
-  grabbing 4:c41c793e0ef1 "added d"
+  $ hg pick -r .~-3
+  picking 4:c41c793e0ef1 "added d"
   $ echo foobar > c
   $ hg add c
   $ hg amend
@@ -140,8 +140,8 @@
   $ hg prev
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   [1] added a
-  $ hg grab -r ca1b80f
-  grabbing 3:ca1b80f7960a "added c"
+  $ hg pick -r ca1b80f
+  picking 3:ca1b80f7960a "added c"
   $ echo foobar > b
   $ hg add b
   $ hg amend
--- a/tests/test-evolve-issue5832.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-evolve-issue5832.t	Mon Sep 03 22:09:26 2018 +0200
@@ -191,8 +191,8 @@
   0 files updated, 0 files merged, 4 files removed, 0 files unresolved
   $ echo l > l
   $ hg ci -Aqm "added l"
-  $ hg grab -r 1b24879c5c3c
-  grabbing 1:1b24879c5c3c "added a"
+  $ hg pick -r 1b24879c5c3c
+  picking 1:1b24879c5c3c "added a"
 
   $ hg up bde1d2b6b5e5
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
@@ -336,8 +336,8 @@
   0 files updated, 0 files merged, 4 files removed, 0 files unresolved
   $ echo l > l
   $ hg ci -Aqm "added l"
-  $ hg grab -r 1b24879c5c3c
-  grabbing 1:1b24879c5c3c "added a"
+  $ hg pick -r 1b24879c5c3c
+  picking 1:1b24879c5c3c "added a"
 
   $ hg up bde1d2b6b5e5
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
--- a/tests/test-evolve-serveronly-bundle2.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-evolve-serveronly-bundle2.t	Mon Sep 03 22:09:26 2018 +0200
@@ -85,9 +85,9 @@
 ===================
 
   $ curl -s http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (glob)
+  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v1 batch * (glob)
   $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (no-eol) (glob)
+  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v1 batch * (no-eol) (glob)
 
   $ curl -s "http://localhost:$HGPORT/?cmd=listkeys&namespace=namespaces" | sort
   bookmarks	
@@ -149,9 +149,9 @@
   obsolete	
   phases	
   $ curl -s http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (glob)
+  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v1 batch * (glob)
   $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (no-eol) (glob)
+  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v1 batch * (no-eol) (glob)
 
   $ echo '[experimental]' >> server/.hg/hgrc
   $ echo 'evolution=!' >> server/.hg/hgrc
@@ -176,9 +176,9 @@
   phases	
 
   $ curl -s http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (glob)
+  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v1 batch * (glob)
   $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (no-eol) (glob)
+  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v1 batch * (no-eol) (glob)
 
 Test obshashrange discover
 ===========================================
--- a/tests/test-evolve-topic.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-evolve-topic.t	Mon Sep 03 22:09:26 2018 +0200
@@ -80,11 +80,11 @@
   ### topic: foo (?)
   ### branch: default (?)
   ### target: default (branch)
-  t4@ add fff (current)
-  t3: add eee
-  t2: add ddd
-  t1: add ccc
-  t0^ add bbb (base)
+  s4@ add fff (current)
+  s3: add eee
+  s2: add ddd
+  s1: add ccc
+  s0^ add bbb (base)
   $ hg up 'desc(ddd)'
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ echo ddd >> ddd
@@ -259,12 +259,12 @@
   $ hg stack
   ### topic: bar
   ### target: default (branch)
-  t5$ add jjj (unstable)
-  t4$ add iii (unstable)
-  t3$ add hhh (unstable)
-  t2$ add ggg (current unstable)
-  t1: add fff
-  t0^ add eee (base)
+  s5$ add jjj (unstable)
+  s4$ add iii (unstable)
+  s3$ add hhh (unstable)
+  s2$ add ggg (current unstable)
+  s1: add fff
+  s0^ add eee (base)
 
   $ hg prev
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-evolve.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-evolve.t	Mon Sep 03 22:09:26 2018 +0200
@@ -600,8 +600,8 @@
   |/
   o  0:8685c6d34325@default(draft) add 0
   
-  $ hg grab -r3
-  grabbing 3:0e84df4912da "add 3"
+  $ hg pick -r3
+  picking 3:0e84df4912da "add 3"
   $ hg graft -r1
   grafting 1:73d38bb17fd7 "add 1"
   $ hg prune -r2 --succ .
@@ -632,8 +632,8 @@
   $ hg ci -Am conflict 1
   created new head
   $ hg up -qC 6
-  $ hg grab -r 7
-  grabbing 7:a5bfd90a2f29 "conflict"
+  $ hg pick -r 7
+  picking 7:a5bfd90a2f29 "conflict"
   merging 1
   warning: conflicts while merging 1! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts (see hg help resolve)
@@ -643,8 +643,8 @@
   $ echo 3 > 1
   $ hg resolve -m 1
   (no more unresolved files)
-  continue: hg grab --continue
-  $ hg grab --continue
+  continue: hg pick --continue
+  $ hg pick --continue
   $ glog --hidden
   @  8:fb2c0f0a0c54@default(draft) conflict
   |
--- a/tests/test-grab.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-grab.t	Mon Sep 03 22:09:26 2018 +0200
@@ -16,16 +16,18 @@
   $ hg init repo
   $ cd repo
   $ hg help grab
-  hg grab [-r] rev
+  hg pick [-r] rev
+  
+  aliases: grab
   
   grabs a commit, move it on the top of working directory parent and
       updates to it.
   
   options:
   
-   -r --rev VALUE revision to grab
-   -c --continue  continue interrupted grab
-   -a --abort     abort interrupted grab
+   -r --rev VALUE revision to pick
+   -c --continue  continue interrupted pick
+   -a --abort     abort interrupted pick
   
   (some details hidden, use --verbose to show complete help)
 
@@ -43,45 +45,45 @@
 
 Grabbing an ancestor
 
-  $ hg grab -r 7c3bad9141dc
-  abort: cannot grab an ancestor revision
+  $ hg pick -r 7c3bad9141dc
+  abort: cannot pick an ancestor revision
   [255]
 
 Grabbing the working directory parent
 
-  $ hg grab -r .
-  abort: cannot grab an ancestor revision
+  $ hg pick -r .
+  abort: cannot pick an ancestor revision
   [255]
 
 Specifying multiple revisions to grab
 
-  $ hg grab 1f0dee641bb7 -r 7c3bad9141dc
+  $ hg pick 1f0dee641bb7 -r 7c3bad9141dc
   abort: specify just one revision
   [255]
 
 Specifying no revisions to grab
 
-  $ hg grab
+  $ hg pick
   abort: empty revision set
   [255]
 
 Continuing without interrupted grab
 
-  $ hg grab --continue
-  abort: no interrupted grab state exists
+  $ hg pick --continue
+  abort: no interrupted pick state exists
   [255]
 
 Aborting without interrupted grab
 
-  $ hg grab --abort
-  abort: no interrupted grab state exists
+  $ hg pick --abort
+  abort: no interrupted pick state exists
   [255]
 
 Specifying both continue and revs
 
   $ hg up 1f0dee641bb7
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg grab -r 4538525df7e2 --continue
+  $ hg pick -r 4538525df7e2 --continue
   abort: cannot specify both --continue and revision
   [255]
 
@@ -104,8 +106,8 @@
   
 Grabbing a revision
 
-  $ hg grab 7c3bad9141dc
-  grabbing 1:7c3bad9141dc "add b"
+  $ hg pick 7c3bad9141dc
+  picking 1:7c3bad9141dc "add b"
   $ hg glog
   @  5:7c15c05db6fa add b
   |
@@ -140,9 +142,9 @@
   |/
   o  0:1f0dee641bb7 add a
   
-  $ hg grab -r 4538525df7e2
-  grabbing 2:4538525df7e2 "add c"
-  note: grab of 2:4538525df7e2 created no changes to commit
+  $ hg pick -r 4538525df7e2
+  picking 2:4538525df7e2 "add c"
+  note: picking 2:4538525df7e2 created no changes to commit
 
   $ hg glog
   @  6:c4636a81ebeb add c
@@ -161,8 +163,8 @@
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ echo foo > c
   $ hg ci -Aqm "foo to c"
-  $ hg grab -r c4636a81ebeb
-  grabbing 6:c4636a81ebeb "add c"
+  $ hg pick -r c4636a81ebeb
+  picking 6:c4636a81ebeb "add c"
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts (see hg help resolve)
@@ -171,8 +173,8 @@
   $ echo foobar > c
   $ hg resolve --all --mark
   (no more unresolved files)
-  continue: hg grab --continue
-  $ hg grab --continue
+  continue: hg pick --continue
+  $ hg pick --continue
   $ hg glog
   @  8:44e155eb95c7 add c
   |
@@ -199,8 +201,8 @@
   $ hg up 44e155eb95c7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-  $ hg grab 4e04628911f6
-  grabbing 9:4e04628911f6 "foo to c"
+  $ hg pick 4e04628911f6
+  picking 9:4e04628911f6 "foo to c"
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts (see hg help resolve)
@@ -208,12 +210,12 @@
   $ echo foobar > c
   $ hg resolve -m
   (no more unresolved files)
-  continue: hg grab --continue
+  continue: hg pick --continue
 
-  $ hg grab --continue
-  note: grab of 9:4e04628911f6 created no changes to commit
+  $ hg pick --continue
+  note: picking 9:4e04628911f6 created no changes to commit
 
-Testing the abort functionality of hg grab
+Testing the abort functionality of hg pick
 
   $ echo foo > b
   $ hg ci -Aqm "foo to b"
@@ -221,15 +223,15 @@
   hg: parse error at 4: not a prefix: end
   [255]
 
-  $ hg grab -r 7c15c05db6fa
-  grabbing 5:7c15c05db6fa "add b"
+  $ hg pick -r 7c15c05db6fa
+  picking 5:7c15c05db6fa "add b"
   merging b
   warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts (see hg help resolve)
   [1]
 
-  $ hg grab --abort
-  aborting grab, updating to c437988de89f
+  $ hg pick --abort
+  aborting pick, updating to c437988de89f
 
   $ hg glog
   @  10:c437988de89f foo to b
@@ -251,8 +253,8 @@
 
   $ hg phase -r 7c15c05db6fa -p
 
-  $ hg grab -r 7c15c05db6fa
-  abort: cannot grab public changesets: 7c15c05db6fa
+  $ hg pick -r 7c15c05db6fa
+  abort: cannot pick public changesets: 7c15c05db6fa
   (see 'hg help phases' for details)
   [255]
 
@@ -277,8 +279,8 @@
 
   $ hg phase -r 7c15c05db6fa -s -f
 
-  $ hg grab -r 7c15c05db6fa
-  grabbing 5:7c15c05db6fa "add b"
+  $ hg pick -r 7c15c05db6fa
+  picking 5:7c15c05db6fa "add b"
   merging b
   warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts (see hg help resolve)
@@ -287,9 +289,9 @@
   $ echo bar > b
   $ hg resolve -m
   (no more unresolved files)
-  continue: hg grab --continue
+  continue: hg pick --continue
 
-  $ hg grab --continue
+  $ hg pick --continue
   $ hg phase -r .
   11: secret
 
@@ -324,8 +326,8 @@
   $ hg up 10427de9e26e
   3 files updated, 0 files merged, 1 files removed, 0 files unresolved
 
-  $ hg grab -r 508d572e7053
-  grabbing 12:508d572e7053 "added l"
+  $ hg pick -r 508d572e7053
+  picking 12:508d572e7053 "added l"
 
   $ hg phase -r .
   13: secret
--- a/tests/test-prune.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-prune.t	Mon Sep 03 22:09:26 2018 +0200
@@ -35,14 +35,20 @@
 Check arguments exclusive to each other
 ---------------------------------------
 
+  $ hg prune --fold --pair
+  abort: can only specify one of pair, fold
+  [255]
   $ hg prune --fold --biject
-  abort: can only specify one of biject, fold
+  abort: nothing to prune
   [255]
   $ hg prune --split --fold
   abort: can only specify one of fold, split
   [255]
+  $ hg prune --split --fold --pair
+  abort: can only specify one of pair, fold, split
+  [255]
   $ hg prune --split --fold --biject
-  abort: can only specify one of biject, fold, split
+  abort: can only specify one of fold, split
   [255]
 
 Check simple case
@@ -196,7 +202,7 @@
 
   $ hg prune 'desc("add cc")' 'desc("add bb")' -s 'desc("add nD")' -s 'desc("add nC")'
   abort: Can't use multiple successors for multiple precursors
-  (use --biject to mark a series as a replacement for another)
+  (use --pair to mark a series as a replacement for another)
   [255]
   $ hg debugobsolete
   9d206ffc875e1bc304590549be293be36821e66c 0 {47d2a3944de8b013de3be9578e8e344ea2e6c097} (Sat Dec 15 00:00:00 1979 +0000) {'ef1': '0', 'user': 'blah'}
@@ -223,7 +229,7 @@
   814c38b95e72dfe2cbf675b1649ea9d780c89a80 6f6f25e4f748d8f7571777e6e168aedf50350ce8 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'user': 'test'}
   354011cd103f58bbbd9091a3cee6d6a6bd0dddf7 6f6f25e4f748d8f7571777e6e168aedf50350ce8 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'user': 'test'}
 
-two old, two new with --biject
+two old, two new with --pair
 
   $ hg up 0
   0 files updated, 0 files merged, 4 files removed, 0 files unresolved
@@ -231,7 +237,7 @@
   created new head
   $ mkcommit n2
 
-  $ hg prune 'desc("add n1")::desc("add n2")' -s 'desc("add nD")::desc("add nE")' --biject
+  $ hg prune 'desc("add n1")::desc("add n2")' -s 'desc("add nD")::desc("add nE")' --pair
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   working directory now at 1f0dee641bb7
   2 changesets pruned
--- a/tests/test-stabilize-result.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-stabilize-result.t	Mon Sep 03 22:09:26 2018 +0200
@@ -131,8 +131,8 @@
 
 Get a successors of 8 on it
 
-  $ hg grab 1cf0aacfd363
-  grabbing 8:1cf0aacfd363 "newer a"
+  $ hg pick 1cf0aacfd363
+  picking 8:1cf0aacfd363 "newer a"
 
 Add real change to the successors
 
--- a/tests/test-stack-branch.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-stack-branch.t	Mon Sep 03 22:09:26 2018 +0200
@@ -56,8 +56,8 @@
   0 files updated, 0 files merged, 4 files removed, 0 files unresolved
   $ hg stack
   ### target: other (branch)
-  b2@ c_b (current)
-  b1: c_a
+  s2@ c_b (current)
+  s1: c_a
   $ hg phase --public 'branch("other")'
   $ hg up foo
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -71,18 +71,18 @@
   foo
   $ hg stack
   ### target: foo (branch)
-  b4@ c_f (current)
-  b3: c_e
-  b2: c_d
-  b1: c_c
-  b0^ c_b (base)
+  s4@ c_f (current)
+  s3: c_e
+  s2: c_d
+  s1: c_c
+  s0^ c_b (base)
   $ hg stack -v
   ### target: foo (branch)
-  b4(913c298d8b0a)@ c_f (current)
-  b3(4f2a69f6d380): c_e
-  b2(f61adbacd17a): c_d
-  b1(3e9313bc4b71): c_c
-  b0(4a04f1104a27)^ c_b (base)
+  s4(913c298d8b0a)@ c_f (current)
+  s3(4f2a69f6d380): c_e
+  s2(f61adbacd17a): c_d
+  s1(3e9313bc4b71): c_c
+  s0(4a04f1104a27)^ c_b (base)
 
 Test "t#" reference
 -------------------
@@ -92,7 +92,7 @@
   $ hg up foo
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg up b42
-  abort: cannot resolve "b42": branch "foo" has only 4 changesets
+  abort: cannot resolve "b42": branch "foo" has only 4 non-public changesets
   [255]
   $ hg up b2
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
@@ -126,20 +126,20 @@
   
   $ hg stack
   ### target: foo (branch)
-  b4$ c_f (unstable)
-  b3$ c_e (unstable)
-  b2@ c_d (current)
-  b1: c_c
-  b0^ c_b (base)
+  s4$ c_f (unstable)
+  s3$ c_e (unstable)
+  s2@ c_d (current)
+  s1: c_c
+  s0^ c_b (base)
   $ hg up b3
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg stack
   ### target: foo (branch)
-  b4$ c_f (unstable)
-  b3$ c_e (current unstable)
-  b2: c_d
-  b1: c_c
-  b0^ c_b (base)
+  s4$ c_f (unstable)
+  s3$ c_e (current unstable)
+  s2: c_d
+  s1: c_c
+  s0^ c_b (base)
   $ hg up b2
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
 
@@ -207,14 +207,14 @@
 
   $ hg stack
   ### target: foo (branch) (2 heads)
-  b6: c_f
-  b5: c_e
-  b2^ c_d (base)
-  b4@ c_h (current)
-  b3: c_g
-  b2: c_d
-  b1: c_c
-  b0^ c_b (base)
+  s6: c_f
+  s5: c_e
+  s2^ c_d (base)
+  s4@ c_h (current)
+  s3: c_g
+  s2: c_d
+  s1: c_c
+  s0^ c_b (base)
 
 Case with multiple heads on the topic with unstability involved
 ---------------------------------------------------------------
@@ -250,14 +250,14 @@
 
   $ hg stack
   ### target: foo (branch) (2 heads)
-  b6$ c_f (unstable)
-  b5$ c_e (unstable)
-  b2^ c_D (base current)
-  b4: c_h
-  b3: c_g
-  b2@ c_D (current)
-  b1: c_c
-  b0^ c_b (base)
+  s6$ c_f (unstable)
+  s5$ c_e (unstable)
+  s2^ c_D (base current)
+  s4: c_h
+  s3: c_g
+  s2@ c_D (current)
+  s1: c_c
+  s0^ c_b (base)
 
 Check that stack doesn't show draft changesets on a branch
 ----------------------------------------------------------
@@ -284,24 +284,24 @@
 
   $ hg stack
   ### target: foo (branch) (2 heads)
-  b6$ c_f (unstable)
-  b5$ c_e (unstable)
-  b2^ c_D (base current)
-  b4: c_h
-  b3: c_g
-  b2@ c_D (current)
-  b1: c_c
-  b0^ c_b (base)
+  s6$ c_f (unstable)
+  s5$ c_e (unstable)
+  s2^ c_D (base current)
+  s4: c_h
+  s3: c_g
+  s2@ c_D (current)
+  s1: c_c
+  s0^ c_b (base)
   $ hg phase --public b1
   $ hg stack
   ### target: foo (branch) (2 heads)
-  b5$ c_f (unstable)
-  b4$ c_e (unstable)
-  b1^ c_D (base current)
-  b3: c_h
-  b2: c_g
-  b1@ c_D (current)
-  b0^ c_c (base)
+  s5$ c_f (unstable)
+  s4$ c_e (unstable)
+  s1^ c_D (base current)
+  s3: c_h
+  s2: c_g
+  s1@ c_D (current)
+  s0^ c_c (base)
 
 Check that stack doesn't show changeset with a topic
 ----------------------------------------------------
@@ -310,7 +310,7 @@
   changed topic on 2 changes
   $ hg stack
   ### target: foo (branch)
-  b3: c_h
-  b2: c_g
-  b1@ c_D (current)
-  b0^ c_c (base)
+  s3: c_h
+  s2: c_g
+  s1@ c_D (current)
+  s0^ c_c (base)
--- a/tests/test-topic-fold.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-topic-fold.t	Mon Sep 03 22:09:26 2018 +0200
@@ -55,8 +55,8 @@
   $ hg stack
   ### topic: myfeature
   ### target: default (branch)
-  t1@ folded (current)
-  t0^ add ROOT (base)
+  s1@ folded (current)
+  s0^ add ROOT (base)
   $ logtopic
   @  3:4fd43e5bdc443dc8489edffac19bd8f93ccf1a5c
   |  topics: myfeature
--- a/tests/test-topic-rebase.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-topic-rebase.t	Mon Sep 03 22:09:26 2018 +0200
@@ -47,8 +47,8 @@
   $ hg stack
   ### topic: myfeature
   ### target: default (branch)
-  t1@ add feature1 (current)
-  t0^ add ROOT (base)
+  s1@ add feature1 (current)
+  s0^ add ROOT (base)
   $ logtopic
   @  1:39e7a938055e87615edf675c24a10997ff05bb06
   |  topics: myfeature
@@ -77,8 +77,8 @@
   $ hg stack
   ### topic: myfeature
   ### target: default (branch)
-  t1@ add feature1 (current)
-  t0^ add default (base)
+  s1@ add feature1 (current)
+  s0^ add default (base)
   $ logtopic
   @  3:fc6593661cf3256ba165cbccd6019ead17cc3726
   |  topics: myfeature
@@ -91,8 +91,8 @@
   $ hg stack
   ### topic: myfeature
   ### target: default (branch)
-  t1@ add feature1 (current)
-  t0^ add default (base)
+  s1@ add feature1 (current)
+  s0^ add default (base)
 
 Check that rebase keep the topic in case of merge conflict
 ----------------------------------------------------------
@@ -157,12 +157,12 @@
   $ hg stack
   ### topic: myotherfeature
   ### target: default (branch)
-  t1@ myotherfeature1 (current)
-  t0^ default3 (base)
+  s1@ myotherfeature1 (current)
+  s0^ default3 (base)
   $ hg update --rev 7
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg stack
   ### topic: myotherfeature
   ### target: default (branch)
-  t1@ myotherfeature1 (current)
-  t0^ default3 (base)
+  s1@ myotherfeature1 (current)
+  s0^ default3 (base)
--- a/tests/test-topic-shelve.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-topic-shelve.t	Mon Sep 03 22:09:26 2018 +0200
@@ -26,7 +26,7 @@
   $ hg stack
   ### topic: testing-shelve
   ### target: default (branch)
-  t1@ First commit (current)
+  s1@ First commit (current)
 
 shelve test
 -----------
@@ -39,7 +39,7 @@
   $ hg stack
   ### topic: testing-shelve
   ### target: default (branch)
-  t1@ First commit (current)
+  s1@ First commit (current)
 
 unshelve test
 -------------
@@ -50,4 +50,4 @@
   $ hg stack
   ### topic: testing-shelve
   ### target: default (branch)
-  t1@ First commit (current)
+  s1@ First commit (current)
--- a/tests/test-topic-stack-complex.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-topic-stack-complex.t	Mon Sep 03 22:09:26 2018 +0200
@@ -42,10 +42,10 @@
   $ hg stack
   ### topic: foo
   ### target: default (branch)
-  t3@ Added e and f (current)
-  t2: Added c and d
-  t1: Added a and b
-  t0^ Added foo (base)
+  s3@ Added e and f (current)
+  s2: Added c and d
+  s1: Added a and b
+  s0^ Added foo (base)
   $ hg prev
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   [2] Added c and d
@@ -85,11 +85,11 @@
   $ hg stack
   ### topic: foo
   ### target: default (branch)
-  t4$ Added e and f (unstable)
-  t3@ split2 (current)
-  t2: split1
-  t1: Added a and b
-  t0^ Added foo (base)
+  s4$ Added e and f (unstable)
+  s3@ split2 (current)
+  s2: split1
+  s1: Added a and b
+  s0^ Added foo (base)
 
   $ hg show work
   @  5ccee (foo) split2
@@ -128,8 +128,8 @@
   $ hg stack
   ### topic: foo (2 heads)
   ### target: default (branch), 2 behind
-  t4$ Added e and f (unstable)
-  t3$ split2 (unstable)
-  t2@ split1 (current)
-  t1: Added a and b
-  t0^ Added foo (base)
+  s4$ Added e and f (unstable)
+  s3$ split2 (unstable)
+  s2@ split1 (current)
+  s1: Added a and b
+  s0^ Added foo (base)
--- a/tests/test-topic-stack-data.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-topic-stack-data.t	Mon Sep 03 22:09:26 2018 +0200
@@ -262,39 +262,39 @@
   $ hg stack bar
   ### topic: bar (2 heads)
   ### target: default (branch)
-  t5: add bar_c
-  t2^ add bar_b (base)
-  t4$ add bar_e (unstable)
-  t3: bar1_d
-  t2: add bar_b
-  t1: add bar_a
-  t0^ add base_e (base)
+  s5: add bar_c
+  s2^ add bar_b (base)
+  s4$ add bar_e (unstable)
+  s3: bar1_d
+  s2: add bar_b
+  s1: add bar_a
+  s0^ add base_e (base)
   $ hg stack bar -v
   ### topic: bar (2 heads)
   ### target: default (branch)
-  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)
+  s5(9cbadf11b44d): add bar_c
+  s2(e555c7e8c767)^ add bar_b (base)
+  s4(a920412b5a05)$ add bar_e (unstable)
+  s3(6915989374b1): bar1_d
+  s2(e555c7e8c767): add bar_b
+  s1(a5c2b4e00bbf): add bar_a
+  s0(92f489a6251f)^ add base_e (base)
   $ hg stack baz
   ### topic: baz
   ### target: default (branch), 2 behind
-  t2: add baz_b
-  t1: add baz_a
-  t0^ add base_c (base)
+  s2: add baz_b
+  s1: add baz_a
+  s0^ add base_c (base)
   $ hg stack foo
   ### topic: foo
   ### target: lake (branch), ambiguous rebase destination - branch 'lake' has 2 heads
-  t2@ add foo_b (current)
-  t1: add foo_a
-  t0^ add lake_a (base)
+  s2@ add foo_b (current)
+  s1: add foo_a
+  s0^ add lake_a (base)
   $ hg stack fuz
   ### topic: fuz
   ### target: default (branch), 1 behind
-  t3$ add fuz_c (unstable)
-  t2$ add fuz_b (unstable)
-  t1: fuz1_a
-  t0^ add base_d (base)
+  s3$ add fuz_c (unstable)
+  s2$ add fuz_b (unstable)
+  s1: fuz1_a
+  s0^ add base_d (base)
--- a/tests/test-topic-stack.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-topic-stack.t	Mon Sep 03 22:09:26 2018 +0200
@@ -60,8 +60,8 @@
   $ hg topic --list
   ### topic: other
   ### target: default (branch)
-  t2@ c_b (current)
-  t1: c_a
+  s2@ c_b (current)
+  s1: c_a
   $ hg phase --public 'topic("other")'
   active topic 'other' is now empty
 
@@ -74,7 +74,7 @@
   ### topic: other
   ### target: default (branch)
   (stack is empty)
-  t0^ c_b (base current)
+  s0^ c_b (base current)
 
   $ hg up foo
   switching to topic foo
@@ -90,19 +90,19 @@
   $ hg stack
   ### topic: foo
   ### target: default (branch)
-  t4@ c_f (current)
-  t3: c_e
-  t2: c_d
-  t1: c_c
-  t0^ c_b (base)
+  s4@ c_f (current)
+  s3: c_e
+  s2: c_d
+  s1: c_c
+  s0^ c_b (base)
   $ hg stack -v
   ### topic: foo
   ### target: default (branch)
-  t4(6559e6d93aea)@ c_f (current)
-  t3(0f9ac936c87d): c_e
-  t2(e629654d7050): c_d
-  t1(8522f9e3fee9): c_c
-  t0(ea705abc4f51)^ c_b (base)
+  s4(6559e6d93aea)@ c_f (current)
+  s3(0f9ac936c87d): c_e
+  s2(e629654d7050): c_d
+  s1(8522f9e3fee9): c_c
+  s0(ea705abc4f51)^ c_b (base)
   $ hg stack -Tjson | python -m json.tool
   [
       {
@@ -212,21 +212,21 @@
   $ hg stack
   ### topic: foo
   ### target: default (branch)
-  t4@ c_f (current)
-  t3: c_e
-  t2: c_d
-  t1: c_c
-  t0^ c_b (base)
+  s4@ c_f (current)
+  s3: c_e
+  s2: c_d
+  s1: c_c
+  s0^ c_b (base)
   $ hg topics --config ui.strict=true
    * foo (4 changesets)
   $ hg stack --config ui.strict=true
   ### topic: foo
   ### target: default (branch)
-  t4@ c_f (current)
-  t3: c_e
-  t2: c_d
-  t1: c_c
-  t0^ c_b (base)
+  s4@ c_f (current)
+  s3: c_e
+  s2: c_d
+  s1: c_c
+  s0^ c_b (base)
 
 error case, nothing to list
 
@@ -234,21 +234,24 @@
   $ hg stack
   ### target: default (branch)
   (stack is empty)
-  b0^ c_f (base current)
+  s0^ c_f (base current)
 
 Test "t#" reference
 -------------------
 
 
-  $ hg up t2
-  abort: cannot resolve "t2": no active topic
+  $ hg up s2
+  abort: cannot resolve "s2": branch "default" has only 0 non-public changesets
   [255]
   $ hg topic foo
   marked working directory as topic: foo
   $ hg up t42
   abort: cannot resolve "t42": topic "foo" has only 4 changesets
   [255]
-  $ hg up t2
+  $ hg up s42
+  abort: cannot resolve "s42": topic "foo" has only 4 changesets
+  [255]
+  $ hg up s2
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ hg summary
   parent: 3:e629654d7050 
@@ -282,30 +285,30 @@
   $ hg topic --list
   ### topic: foo
   ### target: default (branch)
-  t4$ c_f (unstable)
-  t3$ c_e (unstable)
-  t2@ c_d (current)
-  t1: c_c
-  t0^ c_b (base)
-  $ hg up t3
+  s4$ c_f (unstable)
+  s3$ c_e (unstable)
+  s2@ c_d (current)
+  s1: c_c
+  s0^ c_b (base)
+  $ hg up s3
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg topic --list
   ### topic: foo
   ### target: default (branch)
-  t4$ c_f (unstable)
-  t3$ c_e (current unstable)
-  t2: c_d
-  t1: c_c
-  t0^ c_b (base)
+  s4$ c_f (unstable)
+  s3$ c_e (current unstable)
+  s2: c_d
+  s1: c_c
+  s0^ c_b (base)
   $ hg topic --list --color=debug
   [topic.stack.summary.topic|### topic: [topic.active|foo]]
   [topic.stack.summary.branches|### target: default (branch)]
-  [topic.stack.index topic.stack.index.unstable|t4][topic.stack.state topic.stack.state.unstable|$] [topic.stack.desc topic.stack.desc.unstable|c_f][topic.stack.state topic.stack.state.unstable| (unstable)]
-  [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.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
+  [topic.stack.index topic.stack.index.unstable|s4][topic.stack.state topic.stack.state.unstable|$] [topic.stack.desc topic.stack.desc.unstable|c_f][topic.stack.state topic.stack.state.unstable| (unstable)]
+  [topic.stack.index topic.stack.index.current topic.stack.index.unstable|s3][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|s2][topic.stack.state topic.stack.state.clean|:] [topic.stack.desc topic.stack.desc.clean|c_d]
+  [topic.stack.index topic.stack.index.clean|s1][topic.stack.state topic.stack.state.clean|:] [topic.stack.desc topic.stack.desc.clean|c_c]
+  [topic.stack.index topic.stack.index.base|s0][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 s2
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
 
 Also test the revset:
@@ -380,14 +383,14 @@
   $ hg top -l
   ### topic: foo (2 heads)
   ### target: default (branch)
-  t6: c_f
-  t5: c_e
-  t2^ c_d (base)
-  t4@ c_h (current)
-  t3: c_g
-  t2: c_d
-  t1: c_c
-  t0^ c_b (base)
+  s6: c_f
+  s5: c_e
+  s2^ c_d (base)
+  s4@ c_h (current)
+  s3: c_g
+  s2: c_d
+  s1: c_c
+  s0^ c_b (base)
 
 Case with multiple heads on the topic with unstability involved
 ---------------------------------------------------------------
@@ -424,14 +427,14 @@
   $ hg topic --list
   ### topic: foo (2 heads)
   ### target: default (branch)
-  t6$ c_f (unstable)
-  t5$ c_e (unstable)
-  t2^ c_D (base current)
-  t4: c_h
-  t3: c_g
-  t2@ c_D (current)
-  t1: c_c
-  t0^ c_b (base)
+  s6$ c_f (unstable)
+  s5$ c_e (unstable)
+  s2^ c_D (base current)
+  s4: c_h
+  s3: c_g
+  s2@ c_D (current)
+  s1: c_c
+  s0^ c_b (base)
 
 Trying to list non existing topic
   $ hg stack thisdoesnotexist
@@ -548,21 +551,21 @@
   $ hg stack
   ### topic: foobar
   ### target: default (branch), 3 behind
-  t2@ c_e (current)
+  s2@ c_e (current)
     ^ c_h
-  t1: c_D
-  t0^ c_c (base)
+  s1: c_D
+  s0^ c_c (base)
 
   $ hg stack foo
   ### topic: foo
   ### target: default (branch), ambiguous rebase destination - topic 'foo' has 3 heads
-  t4: c_f
+  s4: c_f
     ^ c_e
-  t3: c_h
-  t2: c_g
+  s3: c_h
+  s2: c_g
     ^ c_D
-  t1: c_c
-  t0^ c_b (base)
+  s1: c_c
+  s0^ c_b (base)
 
 case involving a merge
 ----------------------
@@ -638,24 +641,24 @@
   $ hg stack red
   ### topic: red
   ### target: default (branch), 6 behind
-  t5: c_H
+  s5: c_H
     ^ c_G
     ^ c_D
-  t4: c_C
-  t1^ c_B (base)
-  t3: c_F
-  t2: c_E
-  t1: c_B
-  t0^ c_A (base)
+  s4: c_C
+  s1^ c_B (base)
+  s3: c_F
+  s2: c_E
+  s1: c_B
+  s0^ c_A (base)
   $ hg stack blue
   ### topic: blue
   ### target: default (branch), ambiguous rebase destination - topic 'blue' has 3 heads
-  t3@ c_I (current)
+  s3@ c_I (current)
     ^ c_H
-  t2: c_D
+  s2: c_D
     ^ c_C
-  t1: c_G
-  t0^ c_F (base)
+  s1: c_G
+  s0^ c_F (base)
 
 Even with some obsolete and orphan changesets
 
@@ -703,24 +706,24 @@
   $ hg stack red
   ### topic: red
   ### target: default (branch), ambiguous rebase destination - topic 'red' has 3 heads
-  t5$ c_H (unstable)
+  s5$ c_H (unstable)
     ^ c_G
     ^ c_D
-  t4$ c_C (unstable)
-  t1^ c_B (base)
-  t3$ c_F (unstable)
-  t2$ c_E (unstable)
-  t1: c_B
-  t0^ c_A (base)
+  s4$ c_C (unstable)
+  s1^ c_B (base)
+  s3$ c_F (unstable)
+  s2$ c_E (unstable)
+  s1: c_B
+  s0^ c_A (base)
   $ hg stack blue
   ### topic: blue
   ### target: default (branch), ambiguous rebase destination - topic 'blue' has 3 heads
-  t3$ c_I (unstable)
+  s3$ c_I (unstable)
     ^ c_H
-  t2$ c_G (unstable)
+  s2$ c_G (unstable)
     ^ c_F
-  t1$ c_D (current unstable)
-  t0^ c_C (base unstable)
+  s1$ c_D (current unstable)
+  s0^ c_C (base unstable)
 
 more obsolescence
 
@@ -777,49 +780,49 @@
   $ hg stack red
   ### topic: red
   ### target: default (branch), ambiguous rebase destination - topic 'red' has 3 heads
-  t5$ c_H (unstable)
+  s5$ c_H (unstable)
     ^ c_G
     ^ c_D
-  t4$ c_F (unstable)
-  t3$ c_E (unstable)
-  t1^ c_B (base)
-  t2$ c_C (unstable)
-  t1: c_B
-  t0^ c_A (base)
+  s4$ c_F (unstable)
+  s3$ c_E (unstable)
+  s1^ c_B (base)
+  s2$ c_C (unstable)
+  s1: c_B
+  s0^ c_A (base)
   $ hg stack blue
   ### topic: blue
   ### target: default (branch), ambiguous rebase destination - topic 'blue' has 3 heads
-  t3$ c_I (unstable)
+  s3$ c_I (unstable)
     ^ c_H
-  t2$ c_G (unstable)
+  s2$ c_G (unstable)
     ^ c_F
-  t1$ c_D (current unstable)
-  t0^ c_C (base unstable)
+  s1$ c_D (current unstable)
+  s0^ c_C (base unstable)
 
 Test stack behavior with a split
 --------------------------------
 
 get things linear again
 
-  $ hg rebase -r t1 -d default
+  $ hg rebase -r s1 -d default
   rebasing 16:1d84ec948370 "c_D" (tip)
   switching to topic blue
-  $ hg rebase -r t2 -d t1
+  $ hg rebase -r s2 -d s1
   rebasing 13:3ab2eedae500 "c_G"
-  $ hg rebase -r t3 -d t2
+  $ hg rebase -r s3 -d s2
   rebasing 8:3bfe800e0486 "c_I"
   $ hg stack
   ### topic: blue
   ### target: default (branch)
-  t3: c_I
-  t2: c_G
-  t1@ c_D (current)
-  t0^ c_A (base)
+  s3: c_I
+  s2: c_G
+  s1@ c_D (current)
+  s0^ c_A (base)
 
 making a split
 (first get something to split)
 
-  $ hg up t2
+  $ hg up s2
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg status --change .
   A ggg
@@ -832,10 +835,10 @@
   $ hg stack
   ### topic: blue
   ### target: default (branch)
-  t3$ c_I (unstable)
-  t2@ c_G (current)
-  t1: c_D
-  t0^ c_A (base)
+  s3$ c_I (unstable)
+  s2@ c_G (current)
+  s1: c_D
+  s0^ c_A (base)
   $ hg --config extensions.evolve=  --config ui.interactive=yes split << EOF
   > y
   > y
@@ -912,8 +915,8 @@
   $ hg stack
   ### topic: blue
   ### target: default (branch)
-  t4$ c_I (unstable)
-  t3@ c_G (current)
-  t2: c_G
-  t1: c_D
-  t0^ c_A (base)
+  s4$ c_I (unstable)
+  s3@ c_G (current)
+  s2: c_G
+  s1: c_D
+  s0^ c_A (base)
--- a/tests/test-topic-tutorial.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-topic-tutorial.t	Mon Sep 03 22:09:26 2018 +0200
@@ -252,9 +252,9 @@
   $ hg stack
   ### topic: food
   ### target: default (branch)
-  t2@ adding fruits (current)
-  t1: adding condiments
-  t0^ Shopping list (base)
+  s2@ adding fruits (current)
+  s1: adding condiments
+  s0^ Shopping list (base)
 
 The topic deactivates when we update away from it:
 
@@ -612,7 +612,7 @@
   ### topic: food
   ### target: default (branch)
   (stack is empty)
-  t0^ adding fruits (base current)
+  s0^ adding fruits (base current)
 
   $ hg log --graph
   @  changeset:   5:2d50db8b5b4c
@@ -789,9 +789,9 @@
   $ hg stack
   ### topic: drinks
   ### target: default (branch)
-  t2@ Adding orange juice (current)
-  t1: Adding apple juice
-  t0^ adding fruits (base)
+  s2@ Adding orange juice (current)
+  s1: Adding apple juice
+  s0^ adding fruits (base)
 
   $ hg update tools
   switching to topic tools
@@ -800,10 +800,10 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch)
-  t3@ Adding drill (current)
-  t2: Adding saw
-  t1: Adding hammer
-  t0^ adding fruits (base)
+  s3@ Adding drill (current)
+  s2: Adding saw
+  s1: Adding hammer
+  s0^ adding fruits (base)
 
 They are seen as independent branches by Mercurial. No rebase or merge
 between them will be attempted by default:
@@ -1118,10 +1118,10 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t3@ Adding drill (current)
-  t2: Adding saw
-  t1: Adding hammer
-  t0^ add a pair of shoes (base)
+  s3@ Adding drill (current)
+  s2: Adding saw
+  s1: Adding hammer
+  s0^ add a pair of shoes (base)
 
 Working Within Your Stack
 ===========================
@@ -1137,10 +1137,10 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t3@ Adding drill (current)
-  t2: Adding saw
-  t1: Adding hammer
-  t0^ add a pair of shoes (base)
+  s3@ Adding drill (current)
+  s2: Adding saw
+  s1: Adding hammer
+  s0^ add a pair of shoes (base)
 
 You can navigate in your current stack with `previous` and `next`.
 
@@ -1153,10 +1153,10 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t3: Adding drill
-  t2@ Adding saw (current)
-  t1: Adding hammer
-  t0^ add a pair of shoes (base)
+  s3: Adding drill
+  s2@ Adding saw (current)
+  s1: Adding hammer
+  s0^ add a pair of shoes (base)
 
 `next` will move you forward to the topic head.
 
@@ -1167,23 +1167,23 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t3@ Adding drill (current)
-  t2: Adding saw
-  t1: Adding hammer
-  t0^ add a pair of shoes (base)
+  s3@ Adding drill (current)
+  s2: Adding saw
+  s1: Adding hammer
+  s0^ add a pair of shoes (base)
 
 You can also directly jump to a changeset within your stack with the revset `t#`.
 
-  $ hg update t1
+  $ hg update s1
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t3: Adding drill
-  t2: Adding saw
-  t1@ Adding hammer (current)
-  t0^ add a pair of shoes (base)
+  s3: Adding drill
+  s2: Adding saw
+  s1@ Adding hammer (current)
+  s0^ add a pair of shoes (base)
 
 Editing your work mid-stack
 ---------------------------
@@ -1193,10 +1193,10 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t3: Adding drill
-  t2: Adding saw
-  t1@ Adding hammer (current)
-  t0^ add a pair of shoes (base)
+  s3: Adding drill
+  s2: Adding saw
+  s1@ Adding hammer (current)
+  s0^ add a pair of shoes (base)
 
   $ hg amend -m "Adding hammer to the shopping list"
   2 new unstable changesets
@@ -1204,7 +1204,7 @@
 Understanding the current situation with hg log is not so easy, because
 it shows too many things:
 
-  $ hg log -G -r "t0::"
+  $ hg log -G -r "s0::"
   @  changeset:   18:b7509bd417f8
   |  tag:         tip
   |  topic:       tools
@@ -1252,7 +1252,7 @@
   
 
 #if docgraph-ext
-  $ hg docgraph -r "t0::" --sphinx-directive --rankdir LR #rest-ignore
+  $ hg docgraph -r "s0::" --sphinx-directive --rankdir LR #rest-ignore
   .. graphviz::
   
       strict digraph "Mercurial graph" {
@@ -1355,13 +1355,13 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t3$ Adding drill (unstable)
-  t2$ Adding saw (unstable)
-  t1@ Adding hammer to the shopping list (current)
-  t0^ add a pair of shoes (base)
+  s3$ Adding drill (unstable)
+  s2$ Adding saw (unstable)
+  s1@ Adding hammer to the shopping list (current)
+  s0^ add a pair of shoes (base)
 
 It's easy to stabilize the situation, `next` has an `--evolve` option.  It will
-do the necessary relocation of `t2` and `t3` over the new `t1` without having
+do the necessary relocation of `s2` and `s3` over the new `s1` without having
 to do that rebase by hand.:
 
   $ hg next --evolve
@@ -1372,10 +1372,10 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t3$ Adding drill (unstable)
-  t2@ Adding saw (current)
-  t1: Adding hammer to the shopping list
-  t0^ add a pair of shoes (base)
+  s3$ Adding drill (unstable)
+  s2@ Adding saw (current)
+  s1: Adding hammer to the shopping list
+  s0^ add a pair of shoes (base)
 
 One more to go:
 
@@ -1387,14 +1387,14 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t3@ Adding drill (current)
-  t2: Adding saw
-  t1: Adding hammer to the shopping list
-  t0^ add a pair of shoes (base)
+  s3@ Adding drill (current)
+  s2: Adding saw
+  s1: Adding hammer to the shopping list
+  s0^ add a pair of shoes (base)
 
 Let's take a look at `hg log` once again:
 
-  $ hg log -G -r "t0::"
+  $ hg log -G -r "s0::"
   @  changeset:   20:bae3758e46bf
   |  tag:         tip
   |  topic:       tools
@@ -1433,7 +1433,7 @@
   
 
 #if docgraph-ext
-  $ hg docgraph -r "t0::" --sphinx-directive --rankdir LR #rest-ignore
+  $ hg docgraph -r "s0::" --sphinx-directive --rankdir LR #rest-ignore
   .. graphviz::
   
       strict digraph "Mercurial graph" {
@@ -1518,7 +1518,7 @@
 
 Stack is also very helpful when you have a multi-headed stack:
 
-  $ hg up t1
+  $ hg up s1
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ echo "nails" > new_shopping
@@ -1530,12 +1530,12 @@
   $ hg stack
   ### topic: tools (2 heads)
   ### target: default (branch), 2 behind
-  t4: Adding drill
-  t3: Adding saw
-  t1^ Adding hammer to the shopping list (base)
-  t2@ Adding nails (current)
-  t1: Adding hammer to the shopping list
-  t0^ add a pair of shoes (base)
+  s4: Adding drill
+  s3: Adding saw
+  s1^ Adding hammer to the shopping list (base)
+  s2@ Adding nails (current)
+  s1: Adding hammer to the shopping list
+  s0^ add a pair of shoes (base)
 
 Solving this situation is easy with a topic: use merge or rebase.
 Merge within a multi-headed stack will use the other topic head as
@@ -1768,7 +1768,7 @@
       }
 #endif
 
-  $ hg up t4
+  $ hg up s4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg rebase
@@ -1780,11 +1780,11 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t4@ Adding drill (current)
-  t3: Adding saw
-  t2: Adding nails
-  t1: Adding hammer to the shopping list
-  t0^ add a pair of shoes (base)
+  s4@ Adding drill (current)
+  s3: Adding saw
+  s2: Adding nails
+  s1: Adding hammer to the shopping list
+  s0^ add a pair of shoes (base)
 
 Collaborating through a non-publishing server
 =============================================
@@ -1854,11 +1854,11 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t4@ Adding drill (current)
-  t3: Adding saw
-  t2: Adding nails
-  t1: Adding hammer to the shopping list
-  t0^ add a pair of shoes (base)
+  s4@ Adding drill (current)
+  s3: Adding saw
+  s2: Adding nails
+  s1: Adding hammer to the shopping list
+  s0^ add a pair of shoes (base)
 
 We can also add new changesets and share them:
 
@@ -1893,9 +1893,9 @@
   $ hg stack
   ### topic: tools
   ### target: default (branch), 2 behind
-  t5@ Adding screws (current)
-  t4: Adding drill
-  t3: Adding saw
-  t2: Adding nails
-  t1: Adding hammer to the shopping list
-  t0^ add a pair of shoes (base)
+  s5@ Adding screws (current)
+  s4: Adding drill
+  s3: Adding saw
+  s2: Adding nails
+  s1: Adding hammer to the shopping list
+  s0^ add a pair of shoes (base)
--- a/tests/test-topic.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-topic.t	Mon Sep 03 22:09:26 2018 +0200
@@ -40,8 +40,10 @@
   your current topic.
   
   Topic is offering you aliases reference to changeset in your current topic
-  stack as 't#'. For example, 't1' refers to the root of your stack, 't2' to the
-  second commits, etc. The 'hg stack' command show these number.
+  stack as 's#'. For example, 's1' refers to the root of your stack, 's2' to the
+  second commits, etc. The 'hg stack' command show these number. 's0' can be
+  used to refer to the parent of the topic root. Updating using 'hg up s0' will
+  keep the topic active.
   
   Push behavior will change a bit with topic. When pushing to a publishing
   repository the changesets will turn public and the topic data on them will
@@ -271,7 +273,7 @@
   ### topic: narf
   ### target: default (branch)
   (stack is empty)
-  t0^ Add file delta (base current)
+  s0^ Add file delta (base current)
 
 Add commits to topic
 
@@ -692,7 +694,7 @@
      summary:     Add file alpha
   
 No matches because narf is already closed:
-  $ hg log -r 'topic(narf)' -G
+  $ hg log -r 'topic("narf")' -G
 This regexp should match the topic `fran`:
   $ hg log -r 'topic("re:.ra.")' -G
   o  changeset:   9:0469d521db49
@@ -732,10 +734,39 @@
   summary:     start on fran
   
 
+Using revsets in topic()
+  $ tlog() {
+  >   hg log -T '{rev}: {topic}\n' -r "$1"
+  > }
+
+  $ tlog 'topic(9)'
+  9: fran
+  $ tlog 'topic(8)'
+  $ tlog 'topic(head())'
+  9: fran
+  $ tlog 'topic(:)'
+  9: fran
+  $ tlog 'topic(all())'
+  9: fran
+  $ tlog 'topic(topic(fran))'
+  9: fran
+  $ tlog 'topic(wdir())'
+  9: fran
+  $ tlog 'topic(nonsense)'
+  abort: unknown revision 'nonsense'!
+  [255]
+
+Pattern matching in topic() revset
+  $ tlog 'topic("re:nonsense")'
+  $ tlog 'topic("literal:nonsense")'
+  abort: topic 'nonsense' does not exist!
+  [255]
+
 Deactivate the topic.
   $ hg topics
    * fran (1 changesets)
   $ hg topics --clear
+  $ hg log -r 'topic(wdir())'
   $ echo fran? >> beta
   $ hg ci -m 'fran?'
   created new head
@@ -758,7 +789,7 @@
   $ hg topics
      fran (1 changesets)
 
-Testing for updating to t0
+Testing for updating to s0
 ==========================
 
   $ hg up fran
@@ -767,10 +798,10 @@
   $ hg stack
   ### topic: fran
   ### target: default (branch), ambiguous rebase destination - branch 'default' has 2 heads
-  t1@ start on fran (current)
-  t0^ Add file delta (base)
+  s1@ start on fran (current)
+  s0^ Add file delta (base)
 
-  $ hg up t0
+  $ hg up s0
   preserving the current topic 'fran'
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -779,8 +810,8 @@
   $ hg stack
   ### topic: fran
   ### target: default (branch), ambiguous rebase destination - branch 'default' has 2 heads
-  t1: start on fran
-  t0^ Add file delta (base current)
+  s1: start on fran
+  s0^ Add file delta (base current)
 
   $ hg topics --age
    * fran (1970-01-01 by test)
--- a/tests/test-tutorial.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-tutorial.t	Mon Sep 03 22:09:26 2018 +0200
@@ -636,8 +636,8 @@
 
   $ hg up 'p1(10b8aeaa8cc8)' # going on "bathroom stuff" parent
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg grab fac207dec9f5 # moving "SPAM SPAM" to the working directory parent
-  grabbing 10:fac207dec9f5 "SPAM SPAM"
+  $ hg pick fac207dec9f5 # moving "SPAM SPAM" to the working directory parent
+  picking 10:fac207dec9f5 "SPAM SPAM"
   merging shopping
   $ hg log -G
   @  57e9caedbcb8 (draft): SPAM SPAM
@@ -740,8 +740,8 @@
 
 for simplicity sake we get the bathroom change in line again
 
-  $ hg grab 10b8aeaa8cc8
-  grabbing 9:10b8aeaa8cc8 "bathroom stuff"
+  $ hg pick 10b8aeaa8cc8
+  picking 9:10b8aeaa8cc8 "bathroom stuff"
   merging shopping
   $ hg phase --draft .
   $ hg log -G
--- a/tests/test-wireproto.t	Tue Aug 28 11:25:32 2018 +0200
+++ b/tests/test-wireproto.t	Mon Sep 03 22:09:26 2018 +0200
@@ -191,7 +191,7 @@
   $ cat hg.pid >> $DAEMON_PIDS
 
   $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon batch branchmap bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps changegroupsubset compression=*zlib getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-eol) (glob)
+  _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch branchmap bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps changegroupsubset compression=*zlib getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-eol) (glob)
 
 Check we cannot use pushkey for marker exchange anymore
 
@@ -211,7 +211,6 @@
   pulling from http://localhost:$HGPORT/
   searching for changes
   no changes found
-  obsmarker-exchange: 376 bytes received
 
   $ hg -R client pull http://localhost:$HGPORT/ --config experimental.evolution=createmarkers
   pulling from http://localhost:$HGPORT/