test-compat: merge mercurial-4.6 into mercurial-4.5 mercurial-4.5
authorPierre-Yves David <pierre-yves.david@octobus.net>
Fri, 12 Oct 2018 15:15:07 +0200
branchmercurial-4.5
changeset 4172 35c21ab64420
parent 4171 e541acefbaa5 (diff)
parent 4112 dcef0e3577a3 (current diff)
child 4173 e268f7fd7839
child 4205 6be09fb2c08c
test-compat: merge mercurial-4.6 into mercurial-4.5
tests/test-discovery-obshashrange.t
tests/test-pullbundle.t
tests/test-topic-tutorial.t
tests/test-topic.t
tests/test-wireproto.t
--- a/.hgtags	Fri Sep 14 10:51:42 2018 +0200
+++ b/.hgtags	Fri Oct 12 15:15:07 2018 +0200
@@ -71,3 +71,4 @@
 e7abf863e1130e14cd4d65e53467a199d267b4fd 8.1.1
 f1cde4c97806fc6d6cc4c1e09ea2f4081a3ebaec 8.1.2
 8d8f08245f9715adf48d6f0f59772b04fd7de1f7 8.2.0
+c6362c4abd695fb96e2fd63c150c051852303c7e 8.2.1
--- a/CHANGELOG	Fri Sep 14 10:51:42 2018 +0200
+++ b/CHANGELOG	Fri Oct 12 15:15:07 2018 +0200
@@ -1,7 +1,17 @@
 Changelog
 =========
 
-8.2.1 - in progress
+8.3.0 - in progress
+-------------------
+
+  * evolve: avoid redundant output when handling linear orphans
+  * evolve: use stack alias s# in `hg evolve` messages
+  * next, prev: use stack alias s# when relevant
+  * rewind: add an undo alias
+  * caches: skip warming the stablerange cache on strip in "auto" mode
+  * topic: properly register the '{topicidx}' for mercurial <= 4.5
+
+8.2.1 -- 2018-09-14
 -------------------
 
   * obshashrange: issue the "long stable cache" update message only once
--- a/debian/changelog	Fri Sep 14 10:51:42 2018 +0200
+++ b/debian/changelog	Fri Oct 12 15:15:07 2018 +0200
@@ -1,8 +1,8 @@
-mercurial-evolve (8.2.0-1) UNRELEASED; urgency=medium
+mercurial-evolve (8.2.1-1) unstable; urgency=medium
 
-  * New upstrean release
+  * new upstream release
 
- -- Pierre-Yves David <pierre-yves.david@ens-lyon.org>  Mon, 03 Sep 2018 23:16:46 +0200
+ -- Pierre-Yves David <pierre-yves.david@ens-lyon.org>  Fri, 14 Sep 2018 12:16:07 +0200
 
 mercurial-evolve (8.1.2-1) unstable; urgency=medium
 
--- a/hgext3rd/evolve/__init__.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/evolve/__init__.py	Fri Oct 12 15:15:07 2018 +0200
@@ -1076,9 +1076,14 @@
                     exc.hint = _('do you want --merge?')
                     raise
 
-        displayer = compat.changesetdisplayer(ui, repo,
-                                              {'template': shorttemplate})
         topic = not opts.get("no_topic", False)
+        hastopic = bool(_getcurrenttopic(repo))
+
+        template = shorttemplate
+        if topic and hastopic:
+            template = utility.stacktemplate
+
+        displayer = compat.changesetdisplayer(ui, repo, {'template': template})
 
         target, bookmark = _findprevtarget(repo, displayer,
                                            opts.get('move_bookmark'), topic)
@@ -1143,11 +1148,12 @@
         children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()]
         topic = _getcurrenttopic(repo)
         filtered = set()
+        template = shorttemplate
         if topic and not opts.get("no_topic", False):
             filtered = set(ctx for ctx in children if ctx.topic() != topic)
             children = [ctx for ctx in children if ctx not in filtered]
-        displayer = compat.changesetdisplayer(ui, repo,
-                                              {'template': shorttemplate})
+            template = utility.stacktemplate
+        displayer = compat.changesetdisplayer(ui, repo, {'template': template})
         if len(children) == 1:
             c = children[0]
             return _updatetonext(ui, repo, c, displayer, opts)
--- a/hgext3rd/evolve/cmdrewrite.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/evolve/cmdrewrite.py	Fri Oct 12 15:15:07 2018 +0200
@@ -182,11 +182,11 @@
         bookmarkupdater(newnode)
         tr.close()
     finally:
-        tr.release()
+        if tr is not None:
+            tr.release()
         lockmod.release(lock, wlock)
 
 def _editandapply(ui, repo, pats, old, p1, fp, diffopts):
-    RETRYCHOICE = _('try to fix the patch (yn)?$$ &Yes $$ &No')
     newnode = None
     while newnode is None:
         fp.seek(0)
@@ -220,7 +220,8 @@
             defaultchoice = 0 # yes
             if not ui.interactive:
                 defaultchoice = 1 # no
-            if ui.promptchoice(RETRYCHOICE, default=defaultchoice):
+            retrychoice = _('try to fix the patch (yn)?$$ &Yes $$ &No')
+            if ui.promptchoice(retrychoice, default=defaultchoice):
                 raise error.Abort(_("Could not apply amended path"))
             else:
                 # consider a third choice where we restore the original patch
--- a/hgext3rd/evolve/evolvecmd.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/evolve/evolvecmd.py	Fri Oct 12 15:15:07 2018 +0200
@@ -44,6 +44,7 @@
 
 TROUBLES = compat.TROUBLES
 shorttemplate = utility.shorttemplate
+stacktemplate = utility.stacktemplate
 _bookmarksupdater = rewriteutil.bookmarksupdater
 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
 
@@ -53,7 +54,7 @@
 abortmessage = _("see `hg help evolve.interrupted`\n")
 
 def _solveone(ui, repo, ctx, evolvestate, dryrun, confirm,
-              progresscb, category):
+              progresscb, category, lastsolved=None, stacktmplt=False):
     """Resolve the troubles affecting one revision
 
     returns a tuple (bool, newnode) where,
@@ -62,19 +63,28 @@
                  formed. newnode can be node, when resolution led to no new
                  commit. If bool is False, this is ''.
     """
+    displayer = None
+    if stacktmplt:
+        displayer = compat.changesetdisplayer(ui, repo,
+                                              {'template': stacktemplate})
+    else:
+        displayer = compat.changesetdisplayer(ui, repo,
+                                              {'template': shorttemplate})
     wlock = lock = tr = None
     try:
         wlock = repo.wlock()
         lock = repo.lock()
         tr = repo.transaction("evolve")
         if 'orphan' == category:
-            result = _solveunstable(ui, repo, ctx, evolvestate,
-                                    dryrun, confirm, progresscb)
+            result = _solveunstable(ui, repo, ctx, evolvestate, displayer,
+                                    dryrun, confirm, progresscb,
+                                    lastsolved=lastsolved)
         elif 'phasedivergent' == category:
             result = _solvephasedivergence(ui, repo, ctx, evolvestate,
-                                           dryrun, confirm, progresscb)
+                                           displayer, dryrun, confirm,
+                                           progresscb)
         elif 'contentdivergent' == category:
-            result = _solvedivergent(ui, repo, ctx, evolvestate,
+            result = _solvedivergent(ui, repo, ctx, evolvestate, displayer,
                                      dryrun, confirm, progresscb)
         else:
             assert False, "unknown trouble category: %s" % (category)
@@ -83,8 +93,8 @@
     finally:
         lockmod.release(tr, lock, wlock)
 
-def _solveunstable(ui, repo, orig, evolvestate, dryrun=False, confirm=False,
-                   progresscb=None):
+def _solveunstable(ui, repo, orig, evolvestate, displayer, dryrun=False,
+                   confirm=False, progresscb=None, lastsolved=None):
     """ Tries to stabilize the changeset orig which is orphan.
 
     returns a tuple (bool, newnode) where,
@@ -153,13 +163,13 @@
             target = repo[heads.first()]
     else:
         target = targets[0]
-    displayer = compat.changesetdisplayer(ui, repo, {'template': shorttemplate})
     target = repo[target]
     if not ui.quiet or confirm:
         repo.ui.write(_('move:'), label='evolve.operation')
         displayer.show(orig)
-        repo.ui.write(_('atop:'))
-        displayer.show(target)
+        if lastsolved is None or target != repo[lastsolved]:
+            repo.ui.write(_('atop:'))
+            displayer.show(target)
     if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
             raise error.Abort(_('evolve aborted by user'))
     if progresscb:
@@ -183,8 +193,8 @@
             raise error.InterventionRequired(_("fix conflicts and see `hg help "
                                                "evolve.interrupted`"))
 
-def _solvephasedivergence(ui, repo, bumped, evolvestate, dryrun=False,
-                          confirm=False, progresscb=None):
+def _solvephasedivergence(ui, repo, bumped, evolvestate, displayer,
+                          dryrun=False, confirm=False, progresscb=None):
     """Stabilize a phase divergent changeset
 
     returns a tuple (bool, newnode) where,
@@ -208,7 +218,6 @@
         ui.write_err(msg)
         return (False, '')
 
-    displayer = compat.changesetdisplayer(ui, repo, {'template': shorttemplate})
     if not ui.quiet or confirm:
         repo.ui.write(_('recreate:'), label='evolve.operation')
         displayer.show(bumped)
@@ -326,7 +335,7 @@
         repo.dirstate.setparents(newid, nodemod.nullid)
     return (True, replacementnode)
 
-def _solvedivergent(ui, repo, divergent, evolvestate, dryrun=False,
+def _solvedivergent(ui, repo, divergent, evolvestate, displayer, dryrun=False,
                     confirm=False, progresscb=None):
     """tries to solve content-divergence of a changeset
 
@@ -443,7 +452,6 @@
         ui.write_err(hint)
         return (False, '')
 
-    displayer = compat.changesetdisplayer(ui, repo, {'template': shorttemplate})
     if not ui.quiet or confirm:
         ui.write(_('merge:'), label='evolve.operation')
         displayer.show(divergent)
@@ -1566,14 +1574,30 @@
                      'command': 'evolve', 'orphanmerge': False,
                      'bookmarkchanges': [], 'temprevs': [], 'obsmarkers': []}
         evolvestate.addopts(stateopts)
+        # lastsolved: keep track of successor of last troubled cset we evolved
+        # to confirm that if atop msg should be suppressed to remove redundancy
+        lastsolved = None
+
+        # check if revs to be evolved are in active topic to make sure that we
+        # can use stack aliases s# in evolve msgs.
+        activetopic = getattr(repo, 'currenttopic', '')
+        rev = revs[0]
+        revtopic = getattr(repo[rev], 'topic', '')
+        if revtopic:
+            revtopic = revtopic()
+        stacktmplt = False
+        if activetopic and revtopic and (activetopic == revtopic):
+            stacktmplt = True
         for rev in revs:
             curctx = repo[rev]
             progresscb()
-            ret = _solveone(ui, repo, curctx, evolvestate, dryrunopt, confirmopt,
-                            progresscb, targetcat)
+            ret = _solveone(ui, repo, curctx, evolvestate, dryrunopt,
+                            confirmopt, progresscb, targetcat,
+                            lastsolved=lastsolved, stacktmplt=stacktmplt)
             seen += 1
             if ret[0]:
                 evolvestate['replacements'][curctx.node()] = ret[1]
+                lastsolved = ret[1]
             else:
                 evolvestate['skippedrevs'].append(curctx.node())
 
@@ -1581,7 +1605,8 @@
                 # we were processing an orphan merge with both parents obsolete,
                 # stabilized for second parent, re-stabilize for the first parent
                 ret = _solveone(ui, repo, repo[ret[1]], evolvestate, dryrunopt,
-                                confirmopt, progresscb, targetcat)
+                                confirmopt, progresscb, targetcat,
+                                stacktmplt=stacktmplt)
                 if ret[0]:
                     evolvestate['replacements'][curctx.node()] = ret[1]
                 else:
@@ -1718,15 +1743,21 @@
             category = evolvestate['category']
             confirm = evolvestate['confirm']
             unfi = repo.unfiltered()
+            # lastsolved: keep track of successor of last troubled cset we
+            # evolved to confirm that if atop msg should be suppressed to remove
+            # redundancy
+            lastsolved = None
             for rev in evolvestate['revs']:
                 # XXX: prevent this lookup by storing nodes instead of revnums
                 curctx = unfi[rev]
                 if (curctx.node() not in evolvestate['replacements'] and
                     curctx.node() not in evolvestate['skippedrevs']):
                     newnode = _solveone(ui, repo, curctx, evolvestate, False,
-                                        confirm, progresscb, category)
+                                        confirm, progresscb, category,
+                                        lastsolved=lastsolved)
                     if newnode[0]:
                         evolvestate['replacements'][curctx.node()] = newnode[1]
+                        lastsolved = newnode[1]
                     else:
                         evolvestate['skippedrevs'].append(curctx.node())
         return
--- a/hgext3rd/evolve/metadata.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/evolve/metadata.py	Fri Oct 12 15:15:07 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.2.1.dev'
+__version__ = '8.3.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/obsdiscovery.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/evolve/obsdiscovery.py	Fri Oct 12 15:15:07 2018 +0200
@@ -24,6 +24,7 @@
 
 import hashlib
 import heapq
+import inspect
 import sqlite3
 import struct
 import weakref
@@ -110,7 +111,13 @@
         if len(undecided) < fullsamplesize:
             sample = set(undecided)
         else:
-            sample = _takefullsample(dag, undecided, size=fullsamplesize)
+            # Mercurial 4.8 changed calling convention.
+            if len(inspect.getargspec(_takefullsample)[0]) == 4:
+                sample = _takefullsample(local, None, undecided,
+                                         size=fullsamplesize)
+            else:
+                # hg <= 4.7 version
+                sample = _takefullsample(dag, undecided, size=fullsamplesize)
 
         roundtrips += 1
         ui.progress(_("comparing with other"), totalnb - len(undecided),
@@ -689,7 +696,14 @@
         def destroyed(self):
             if 'obsstore' in vars(self):
                 self.obsstore.rangeobshashcache.clear()
-            super(obshashrepo, self).destroyed()
+            toplevel = not util.safehasattr(self, '_destroying')
+            if toplevel:
+                self._destroying = True
+            try:
+                super(obshashrepo, self).destroyed()
+            finally:
+                if toplevel:
+                    del self._destroying
 
         @localrepo.unfilteredmethod
         def updatecaches(self, tr=None, **kwargs):
--- a/hgext3rd/evolve/rewind.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/evolve/rewind.py	Fri Oct 12 15:15:07 2018 +0200
@@ -26,7 +26,7 @@
 identicalflag = 4
 
 @eh.command(
-    '^rewind',
+    '^rewind|undo',
     [('', 'to', [], _("rewind to these revisions")),
      ('', 'as-divergence', None, _("preserve current latest successors")),
      ('', 'exact', None, _("only rewind explicitly selected revisions")),
--- a/hgext3rd/evolve/stablerange.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/evolve/stablerange.py	Fri Oct 12 15:15:07 2018 +0200
@@ -195,7 +195,6 @@
             assert standard_start < rangedepth
             slicepoint = standard_start
         return slicepoint
-
 class stablerangebasic(abstractstablerange):
     """a very dummy implementation of stablerange
 
@@ -391,6 +390,12 @@
         # find were we need to slice
         slicepoint = self._slicepoint(repo, rangeid)
 
+        ret = self._slicesrangeat(repo, rangeid, slicepoint)
+
+        return ret
+
+    def _slicesrangeat(self, repo, rangeid, slicepoint):
+        headrev, initial_index = rangeid
         self._warmcachefor(repo, rangeid, slicepoint)
 
         stable_parent_data = self._parentrange(repo, rangeid)
@@ -412,7 +417,20 @@
             # The parent is above the slice point,
             # it's lower subrange will be the same so we just get them,
             # (and the top range is always the same)
-            subranges = self.subranges(repo, stable_parent_range)[:-1]
+            subranges = self.subranges(repo, stable_parent_range)[:]
+            parenttop = subranges.pop()
+            lenparenttop = self.rangelength(repo, parenttop)
+            skimfromparent = stable_parent_depth - slicepoint
+            if lenparenttop < skimfromparent:
+                # dropping the first subrange of the stable parent range is not
+                # enough to skip what we need to skip, change in approach is needed
+                subranges = self._slicesrangeat(repo, stable_parent_range, slicepoint)
+                subranges.pop()
+            elif lenparenttop > skimfromparent:
+                # The first subrange of the parent is longer that what we want
+                # to drop, we need to keep some of it.
+                midranges = self._slicesrangeat(repo, parenttop, slicepoint)
+                subranges.extend(midranges[:-1])
             subranges.append(top_range)
         elif initial_index < stable_parent_depth < slicepoint:
             # the parent is below the range we are considering, we need to
@@ -429,6 +447,13 @@
                                                     slicepoint))
             subranges.append(top_range)
 
+        ### slow code block to validated the slicing works as expected
+        # toprevs = self.revsfromrange(repo, rangeid)
+        # subrevs = []
+        # for s in subranges:
+        #     subrevs.extend(self.revsfromrange(repo, s))
+        # assert toprevs == subrevs, (rangeid, slicepoint, stable_parent_range, stable_parent_depth, toprevs, subrevs)
+
         return subranges
 
     def _unique_subranges(self, repo, headrev, initial_index, slicepoint):
--- a/hgext3rd/evolve/stablerangecache.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/evolve/stablerangecache.py	Fri Oct 12 15:15:07 2018 +0200
@@ -388,7 +388,7 @@
 
 class mergepointsql(stablerangesql, stablerange.stablerange_mergepoint):
 
-    _schemaversion = 2
+    _schemaversion = 3
     _cachefile = 'cache/evoext_stablerange_v2.sqlite'
     _cachename = 'evo-ext-stablerange-mergepoint'
 
--- a/hgext3rd/evolve/utility.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/evolve/utility.py	Fri Oct 12 15:15:07 2018 +0200
@@ -14,6 +14,7 @@
 from mercurial.node import nullrev
 
 shorttemplate = "[{label('evolve.rev', rev)}] {desc|firstline}\n"
+stacktemplate = "[s{label('evolve.rev', topicidx)}] {desc|firstline}\n"
 
 def obsexcmsg(ui, message, important=False):
     verbose = ui.configbool('experimental', 'verbose-obsolescence-exchange',
@@ -49,7 +50,7 @@
     desc = getattr(tr, 'desc', '')
 
     autocase = False
-    if tr is None:
+    if tr is None and not getattr(repo, '_destroying', False):
         autocase = True
     elif desc.startswith('serve'):
         autocase = True
@@ -63,7 +64,6 @@
     else:
         # note: we should not get to the default case
         warm = configbool('experimental', 'obshashrange.warm-cache', True)
-
     if not configbool('experimental', 'obshashrange', True):
         return False
     if not warm:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/pullbundle.py	Fri Oct 12 15:15:07 2018 +0200
@@ -0,0 +1,625 @@
+# Extension to provide automatic caching of bundle server for pull
+#
+# Copyright 2018 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""pullbundle: automatic server side bundle caching
+
+General principle
+=================
+
+This extension provides a means for server to use pre-computed bundle for
+serving arbitrary pulls. If missing, the necessary pre-computed bundle will be
+generated on demand.
+
+To maximize usage of existing cached bundle, each pull will be served through
+multiple bundles. The bundle will be created using "standard range" from the
+"stablerange" principle. The "stablerange" concept if already used for
+obsmarkers discovery in the evolve extensions.
+
+Using pull Bundle
+=================
+
+All configuration is only required server side.
+
+The "stablerange" code currently still live in the evolve extensions, so for
+now enabling that extensions is required:
+
+You need at minimum the following configuration:
+
+    [extensions]
+    evolve=yes
+    pullbundle=yes
+    [experimental]
+    obshashrange.warm-cache = yes
+
+If you do not want to use evolution server side, you should disable obsmarkers exchange:
+
+    [experimental]
+    evolution.exchange=no
+
+Extra Configuration
+===================
+
+  [pullbundle]
+  # By default bundles are stored `.hg/cache/pullbundles/.
+  # This can be changed with the following config:
+  cache-directory=/absolute/path
+
+Implementation status
+=====================
+
+Both for stablerange and pullbundle use "simple" initial implementations.
+Theses implemenations focus on testing the algorithms and proving the features
+works. Yet they are already useful and used in production.
+
+Performances are expected to greatly improved in the final implementation,
+especially if some of it end up being compiled code.
+
+This first implementation lacks the ability to server the cached bundle from a
+CDN. We'll want this limitation to be lifted quickly.
+
+The way mercurial core report progress is designed for the receival of a single
+changegroup. So currently using pullbundle means flooding the user with output.
+This will have to be fixed.
+
+Why is does this live in the same repository as evolve
+======================================================
+
+There is no fundamental reasons for live in the same repository. However, the
+stablerange data-structure lives in evolve, so it was simpler to put this new
+extensions next to it. As soon as stable range have been upstreamed, we won't
+need the dependency to the evolve extension anymore.
+"""
+
+import collections
+import errno
+import random
+import os
+
+from mercurial import (
+    changegroup,
+    discovery,
+    error,
+    exchange,
+    narrowspec,
+    node as nodemod,
+    registrar,
+    scmutil,
+    util,
+)
+
+from mercurial.i18n import _
+
+__version__ = '0.1.0.dev'
+testedwith = '4.7.1'
+# minimumhgversion = ''
+buglink = 'https://bz.mercurial-scm.org/'
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('pullbundle', 'cache-directory',
+           default=None,
+)
+
+# generic wrapping
+
+def uisetup(ui):
+    exchange.getbundle2partsmapping['changegroup'] = _getbundlechangegrouppart
+
+def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
+                              b2caps=None, heads=None, common=None, **kwargs):
+    """add a changegroup part to the requested bundle"""
+    if not kwargs.get(r'cg', True):
+        return
+
+    version = '01'
+    cgversions = b2caps.get('changegroup')
+    if cgversions:  # 3.1 and 3.2 ship with an empty value
+        cgversions = [v for v in cgversions
+                      if v in changegroup.supportedoutgoingversions(repo)]
+        if not cgversions:
+            raise ValueError(_('no common changegroup version'))
+        version = max(cgversions)
+
+    outgoing = exchange._computeoutgoing(repo, heads, common)
+    if not outgoing.missing:
+        return
+
+    if kwargs.get(r'narrow', False):
+        include = sorted(filter(bool, kwargs.get(r'includepats', [])))
+        exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
+        filematcher = narrowspec.match(repo.root, include=include,
+                                       exclude=exclude)
+    else:
+        filematcher = None
+
+    # START OF ALTERED PART
+    makeallcgpart(bundler.newpart, repo, outgoing, version, source, bundlecaps,
+                  filematcher, cgversions)
+    # END OF ALTERED PART
+
+    if kwargs.get(r'narrow', False) and (include or exclude):
+        narrowspecpart = bundler.newpart('narrow:spec')
+        if include:
+            narrowspecpart.addparam(
+                'include', '\n'.join(include), mandatory=True)
+        if exclude:
+            narrowspecpart.addparam(
+                'exclude', '\n'.join(exclude), mandatory=True)
+
+def makeallcgpart(newpart, repo, outgoing, version, source,
+                  bundlecaps, filematcher, cgversions):
+
+    pullbundle = not filematcher
+    if pullbundle and not util.safehasattr(repo, 'stablerange'):
+        repo.ui.warn('pullbundle: required extension "evolve" are missing, skipping pullbundle\n')
+        pullbundle = False
+    if filematcher:
+        makeonecgpart(newpart, repo, None, outgoing, version, source, bundlecaps,
+                      filematcher, cgversions)
+    else:
+        start = util.timer()
+        slices = sliceoutgoing(repo, outgoing)
+        end = util.timer()
+        msg = _('pullbundle-cache: "missing" set sliced into %d subranges '
+                'in %s seconds\n')
+        repo.ui.write(msg % (len(slices), end - start))
+        for sliceid, sliceout in slices:
+            makeonecgpart(newpart, repo, sliceid, sliceout, version, source, bundlecaps,
+                          filematcher, cgversions)
+
+# stable range slicing
+
+DEBUG = False
+
+def sliceoutgoing(repo, outgoing):
+    cl = repo.changelog
+    rev = cl.nodemap.get
+    node = cl.node
+    revsort = repo.stablesort
+
+    missingrevs = set(rev(n) for n in outgoing.missing)
+    if DEBUG:
+        ms = missingrevs.copy()
+        ss = []
+    allslices = []
+    missingheads = [rev(n) for n in sorted(outgoing.missingheads, reverse=True)]
+    for head in missingheads:
+        localslices = []
+        localmissing = set(repo.revs('%ld and ::%d', missingrevs, head))
+        thisrunmissing = localmissing.copy()
+        while localmissing:
+            slicerevs = []
+            for r in revsort.walkfrom(repo, head):
+                if r not in thisrunmissing:
+                    break
+                slicerevs.append(r)
+            slicenodes = [node(r) for r in slicerevs]
+            localslices.append(canonicalslices(repo, slicenodes))
+            if DEBUG:
+                ss.append(slicerevs)
+            missingrevs.difference_update(slicerevs)
+            localmissing.difference_update(slicerevs)
+            if localmissing:
+                heads = list(repo.revs('heads(%ld)', localmissing))
+                heads.sort(key=node)
+                head = heads.pop()
+                if heads:
+                    thisrunmissing = repo.revs('%ld and only(%d, %ld)',
+                                               localmissing,
+                                               head,
+                                               heads)
+                else:
+                    thisrunmissing = localmissing.copy()
+        if DEBUG:
+            for s in reversed(ss):
+                ms -= set(s)
+                missingbase = repo.revs('parents(%ld) and %ld', s, ms)
+                if missingbase:
+                    repo.ui.write_err('!!! rev bundled while parents missing\n')
+                    repo.ui.write_err('    parent: %s\n' % list(missingbase))
+                    pb = repo.revs('%ld and children(%ld)', s, missingbase)
+                    repo.ui.write_err('    children: %s\n' % list(pb))
+                    h = repo.revs('heads(%ld)', s)
+                    repo.ui.write_err('    heads: %s\n' % list(h))
+                    raise error.ProgrammingError('issuing a range before its parents')
+
+        for s in reversed(localslices):
+            allslices.extend(s)
+    # unknown subrange might had to be computed
+    repo.stablerange.save(repo)
+    return [(rangeid, outgoingfromnodes(repo, nodes))
+            for rangeid, nodes in allslices]
+
+def canonicalslices(repo, nodes):
+    depth = repo.depthcache.get
+    stablerange = repo.stablerange
+    rangelength = lambda x: stablerange.rangelength(repo, x)
+    headrev = repo.changelog.rev(nodes[0])
+    nbrevs = len(nodes)
+    headdepth = depth(headrev)
+    skipped = headdepth - nbrevs
+    rangeid = (headrev, skipped)
+
+    subranges = canonicalsubranges(repo, stablerange, rangeid)
+    idx = 0
+    slices = []
+    nodes.reverse()
+    for rangeid in subranges:
+        size = rangelength(rangeid)
+        slices.append((rangeid, nodes[idx:idx + size]))
+        idx += size
+    ### slow code block to validate ranges content
+    # rev = repo.changelog.nodemap.get
+    # for ri, ns in slices:
+    #     a = set(rev(n) for n in ns)
+    #     b = set(repo.stablerange.revsfromrange(repo, ri))
+    #     l = repo.stablerange.rangelength(repo, ri)
+    #     repo.ui.write('range-length: %d-%d %s %s\n' % (ri[0], ri[1], l, len(a)))
+    #     if a != b:
+    #         d =  (ri[0], ri[1], b - a, a - b)
+    #         repo.ui.write("mismatching content: %d-%d -%s +%s\n" % d)
+    return slices
+
+def canonicalsubranges(repo, stablerange, rangeid):
+    """slice a size of nodes into most reusable subranges
+
+    We try to slice a range into a set of "largest" and "canonical" stable
+    range.
+
+    It might make sense to move this function as a 'stablerange' method.
+    """
+    headrev, skip = rangeid
+    rangedepth = stablerange.depthrev(repo, rangeid[0])
+    canonicals = []
+
+    # 0. find the first power of 2 higher than this range depth
+    cursor = 1
+    while cursor <= rangedepth:
+        cursor *= 2
+
+    # 1. find first cupt
+    precut = cut = 0
+    while True:
+        if skip <= cut:
+            break
+        if cut + cursor < rangedepth:
+            precut = cut
+            cut += cursor
+        if cursor == 1:
+            break
+        cursor //= 2
+
+    # 2. optimise, bottom part
+    if skip != cut:
+        currentsize = tailsize = cut - skip
+        assert 0 < tailsize, tailsize
+
+        # we need to take several "standard cut" in the bottom part
+        #
+        # This is similar to what we will do for the top part, we reusing the
+        # existing structure is a bit more complex.
+        allcuts = list(reversed(standardcut(tailsize)))
+        prerange = (headrev, precut)
+        ### slow code block to check we operate on the right data
+        # rev = repo.changelog.nodemap.get
+        # allrevs = [rev(n) for n in nodes]
+        # allrevs.reverse()
+        # prerevs = repo.stablerange.revsfromrange(repo, prerange)
+        # assert allrevs == prerevs[(len(prerevs) - len(allrevs)):]
+        # end of check
+        sub = list(stablerange.subranges(repo, prerange)[:-1])
+
+        bottomranges = []
+        # XXX we might be able to reuse core stable-range logic instead of
+        # redoing this manually
+        currentrange = sub.pop()
+        currentsize = stablerange.rangelength(repo, currentrange)
+        currentcut = None
+        while allcuts or currentcut is not None:
+            # get the next cut if needed
+            if currentcut is None:
+                currentcut = allcuts.pop()
+            # deal attemp a cut
+            if currentsize == currentcut:
+                bottomranges.append(currentrange)
+                currentcut = None
+            elif currentsize < currentcut:
+                bottomranges.append(currentrange)
+                currentcut -= currentsize
+            else: # currentsize > currentcut
+                newskip = currentrange[1] + (currentsize - currentcut)
+                currentsub = stablerange._slicesrangeat(repo, currentrange, newskip)
+                bottomranges.append(currentsub.pop())
+                sub.extend(currentsub)
+                currentcut = None
+            currentrange = sub.pop()
+            currentsize = stablerange.rangelength(repo, currentrange)
+        bottomranges.reverse()
+        canonicals.extend(bottomranges)
+
+    # 3. take recursive subrange until we get to a power of two size?
+    current = (headrev, cut)
+    while not poweroftwo(stablerange.rangelength(repo, current)):
+        sub = stablerange.subranges(repo, current)
+        canonicals.extend(sub[:-1])
+        current = sub[-1]
+    canonicals.append(current)
+
+    return canonicals
+
+def standardcut(size):
+    assert 0 < size
+    # 0. find the first power of 2 higher than this range depth
+    cut = 1
+    while cut <= size:
+        cut *= 2
+
+    allcuts = []
+    # 1. find all standard expected cut
+    while 1 < cut and size:
+        cut //= 2
+        if cut <= size:
+            allcuts.append(cut)
+            size -= cut
+    return allcuts
+
+def poweroftwo(num):
+    return num and not num & (num - 1)
+
+def outgoingfromnodes(repo, nodes):
+    return discovery.outgoing(repo,
+                              missingroots=nodes,
+                              missingheads=nodes)
+
+# changegroup part construction
+
+def _changegroupinfo(repo, nodes, source):
+    if repo.ui.verbose or source == 'bundle':
+        repo.ui.status(_("%d changesets found\n") % len(nodes))
+
+def _makenewstream(newpart, repo, outgoing, version, source,
+                   bundlecaps, filematcher, cgversions):
+    old = changegroup._changegroupinfo
+    try:
+        changegroup._changegroupinfo = _changegroupinfo
+        if filematcher is not None:
+            cgstream = changegroup.makestream(repo, outgoing, version, source,
+                                              bundlecaps=bundlecaps,
+                                              filematcher=filematcher)
+        else:
+            cgstream = changegroup.makestream(repo, outgoing, version, source,
+                                              bundlecaps=bundlecaps)
+    finally:
+        changegroup._changegroupinfo = old
+
+    nbchanges = len(outgoing.missing)
+    pversion = None
+    if cgversions:
+        pversion = version
+    return (cgstream, nbchanges, pversion)
+
+def _makepartfromstream(newpart, repo, cgstream, nbchanges, version):
+    # same as upstream code
+
+    part = newpart('changegroup', data=cgstream)
+    if version:
+        part.addparam('version', version)
+
+    part.addparam('nbchanges', '%d' % nbchanges,
+                  mandatory=False)
+
+    if 'treemanifest' in repo.requirements:
+        part.addparam('treemanifest', '1')
+
+# cache management
+
+def cachedir(repo):
+    cachedir = repo.ui.config('pullbundle', 'cache-directory')
+    if cachedir is not None:
+        return cachedir
+    return repo.cachevfs.join('pullbundles')
+
+def getcache(repo, bundlename):
+    cdir = cachedir(repo)
+    bundlepath = os.path.join(cdir, bundlename)
+    if not os.path.exists(bundlepath):
+        return None
+    # delay file opening as much as possible this introduce a small race
+    # condition if someone remove the file before we actually use it. However
+    # opening too many file will not work.
+
+    def data():
+        with open(bundlepath, 'rb') as fd:
+            for chunk in util.filechunkiter(fd):
+                yield chunk
+    return data()
+
+def cachewriter(repo, bundlename, stream):
+    cdir = cachedir(repo)
+    bundlepath = os.path.join(cdir, bundlename)
+    try:
+        os.makedirs(cdir)
+    except OSError as exc:
+        if exc.errno == errno.EEXIST:
+            pass
+    with util.atomictempfile(bundlepath) as cachefile:
+        for chunk in stream:
+            cachefile.write(chunk)
+            yield chunk
+
+BUNDLEMASK = "%s-%s-%010iskip-%010isize.hg"
+
+def makeonecgpart(newpart, repo, rangeid, outgoing, version, source,
+                  bundlecaps, filematcher, cgversions):
+    bundlename = cachedata = None
+    if rangeid is not None:
+        nbchanges = repo.stablerange.rangelength(repo, rangeid)
+        headnode = nodemod.hex(repo.changelog.node(rangeid[0]))
+        # XXX do we need to use cgversion in there?
+        bundlename = BUNDLEMASK % (version, headnode, rangeid[1], nbchanges)
+        cachedata = getcache(repo, bundlename)
+    if cachedata is None:
+        partdata = _makenewstream(newpart, repo, outgoing, version, source,
+                                  bundlecaps, filematcher, cgversions)
+        if bundlename is not None:
+            cgstream = cachewriter(repo, bundlename, partdata[0])
+            partdata = (cgstream,) + partdata[1:]
+    else:
+        if repo.ui.verbose or source == 'bundle':
+            repo.ui.status(_("%d changesets found in caches\n") % nbchanges)
+        pversion = None
+        if cgversions:
+            pversion = version
+        partdata = (cachedata, nbchanges, pversion)
+    return _makepartfromstream(newpart, repo, *partdata)
+
+@command('^debugpullbundlecacheoverlap',
+         [('', 'count', 100, _('of "client" pulling')),
+          ('', 'min-cache', 1, _('minimum size of cached bundle')),
+         ],
+         _('hg debugpullbundlecacheoverlap [--client 100] REVSET'))
+def debugpullbundlecacheoverlap(ui, repo, *revs, **opts):
+    '''Display statistic on bundle cache hit
+
+    This command "simulate pulls from multiple clients. Each using a random
+    subset of revisions defined by REVSET. And display statistic about the
+    overlap in bundle necessary to serve them.
+    '''
+    actionrevs = scmutil.revrange(repo, revs)
+    if not revs:
+        raise error.Abort('No revision selected')
+    count = opts['count']
+    min_cache = opts['min_cache']
+
+    bundlehits = collections.defaultdict(lambda: 0)
+    pullstats = []
+
+    rlen = lambda rangeid: repo.stablerange.rangelength(repo, rangeid)
+
+    repo.ui.write("gathering %d sample pulls within %d revisions\n"
+                  % (count, len(actionrevs)))
+    if 1 < min_cache:
+        repo.ui.write("  not caching ranges smaller than %d changesets\n" % min_cache)
+    for i in xrange(count):
+        repo.ui.progress('gathering data', i, total=count)
+        outgoing = takeonesample(repo, actionrevs)
+        ranges = sliceoutgoing(repo, outgoing)
+        hitranges = 0
+        hitchanges = 0
+        totalchanges = 0
+        largeranges = []
+        for rangeid, __ in ranges:
+            length = rlen(rangeid)
+            totalchanges += length
+            if bundlehits[rangeid]:
+                hitranges += 1
+                hitchanges += rlen(rangeid)
+            if min_cache <= length:
+                bundlehits[rangeid] += 1
+                largeranges.append(rangeid)
+
+        stats = (len(outgoing.missing),
+                 totalchanges,
+                 hitchanges,
+                 len(largeranges),
+                 hitranges,
+                 )
+        pullstats.append(stats)
+    repo.ui.progress('gathering data', None)
+
+    sizes = []
+    changesmissing = []
+    totalchanges = 0
+    totalcached = 0
+    changesratio = []
+    rangesratio = []
+    bundlecount = []
+    for entry in pullstats:
+        sizes.append(entry[0])
+        changesmissing.append(entry[1] - entry[2])
+        changesratio.append(entry[2] / float(entry[1]))
+        if entry[3]:
+            rangesratio.append(entry[4] / float(entry[3]))
+        else:
+            rangesratio.append(1)
+        bundlecount.append(entry[3])
+        totalchanges += entry[1]
+        totalcached += entry[2]
+
+    cachedsizes = []
+    cachedhits = []
+    for rangeid, hits in bundlehits.items():
+        if hits <= 0:
+            continue
+        length = rlen(rangeid)
+        cachedsizes.append(length)
+        cachedhits.append(hits)
+
+    sizesdist = distribution(sizes)
+    repo.ui.write(fmtdist('pull size', sizesdist))
+
+    changesmissingdist = distribution(changesmissing)
+    repo.ui.write(fmtdist('non-cached changesets', changesmissingdist))
+
+    changesratiodist = distribution(changesratio)
+    repo.ui.write(fmtdist('ratio of cached changesets', changesratiodist))
+
+    bundlecountdist = distribution(bundlecount)
+    repo.ui.write(fmtdist('bundle count', bundlecountdist))
+
+    rangesratiodist = distribution(rangesratio)
+    repo.ui.write(fmtdist('ratio of cached bundles', rangesratiodist))
+
+    repo.ui.write('changesets served:\n')
+    repo.ui.write('  total:      %7d\n' % totalchanges)
+    repo.ui.write('  from cache: %7d (%2d%%)\n'
+                  % (totalcached, (totalcached * 100 // totalchanges)))
+    repo.ui.write('  bundle:     %7d\n' % sum(bundlecount))
+
+    cachedsizesdist = distribution(cachedsizes)
+    repo.ui.write(fmtdist('size of cached bundles', cachedsizesdist))
+
+    cachedhitsdist = distribution(cachedhits)
+    repo.ui.write(fmtdist('hit on cached bundles', cachedhitsdist))
+
+def takeonesample(repo, revs):
+    node = repo.changelog.node
+    pulled = random.sample(revs, max(4, len(revs) // 1000))
+    pulled = repo.revs('%ld::%ld', pulled, pulled)
+    nodes = [node(r) for r in pulled]
+    return outgoingfromnodes(repo, nodes)
+
+def distribution(data):
+    data.sort()
+    length = len(data)
+    return {
+        'min': data[0],
+        '10%': data[length // 10],
+        '25%': data[length // 4],
+        '50%': data[length // 2],
+        '75%': data[(length // 4) * 3],
+        '90%': data[(length // 10) * 9],
+        '95%': data[(length // 20) * 19],
+        'max': data[-1],
+    }
+
+STATSFORMAT = """{name}:
+  min: {min}
+  10%: {10%}
+  25%: {25%}
+  50%: {50%}
+  75%: {75%}
+  90%: {90%}
+  95%: {95%}
+  max: {max}
+"""
+
+def fmtdist(name, data):
+    return STATSFORMAT.format(name=name, **data)
--- a/hgext3rd/topic/__init__.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/topic/__init__.py	Fri Oct 12 15:15:07 2018 +0200
@@ -178,7 +178,7 @@
               'topic.active': 'green',
              }
 
-__version__ = '0.11.1.dev'
+__version__ = '0.12.0.dev'
 
 testedwith = '4.3.3 4.4.2 4.5.2 4.6.2 4.7'
 minimumhgversion = '4.3'
@@ -355,6 +355,7 @@
 
     if not post45template:
         templatekw.keywords['topic'] = topickw
+        templatekw.keywords['topicidx'] = topicidxkw
     # Wrap workingctx extra to return the topic name
     extensions.wrapfunction(context.workingctx, '__init__', wrapinit)
     # Wrap changelog.add to drop empty topic
@@ -454,25 +455,38 @@
 
             reporef = weakref.ref(self)
             if self.ui.configbool('experimental', 'enforce-single-head'):
-                origvalidator = tr.validator
+                if util.safehasattr(tr, 'validator'): # hg <= 4.7
+                    origvalidator = tr.validator
+                else:
+                    origvalidator = tr._validator
 
                 def validator(tr2):
                     repo = reporef()
                     flow.enforcesinglehead(repo, tr2)
                     origvalidator(tr2)
-                tr.validator = validator
+
+                if util.safehasattr(tr, 'validator'): # hg <= 4.7
+                    tr.validator = validator
+                else:
+                    tr._validator = validator
 
             topicmodeserver = self.ui.config('experimental',
                                              'topic-mode.server', 'ignore')
             ispush = (desc.startswith('push') or desc.startswith('serve'))
             if (topicmodeserver != 'ignore' and ispush):
-                origvalidator = tr.validator
+                if util.safehasattr(tr, 'validator'): # hg <= 4.7
+                    origvalidator = tr.validator
+                else:
+                    origvalidator = tr._validator
 
                 def validator(tr2):
                     repo = reporef()
                     flow.rejectuntopicedchangeset(repo, tr2)
                     return origvalidator(tr2)
-                tr.validator = validator
+                if util.safehasattr(tr, 'validator'): # hg <= 4.7
+                    tr.validator = validator
+                else:
+                    tr._validator = validator
 
             elif (self.ui.configbool('experimental', 'topic.publish-bare-branch')
                     and (desc.startswith('push')
@@ -503,9 +517,10 @@
                 empty = csetcount == 0
                 if empty and not ctwasempty:
                     ui.status('active topic %r is now empty\n' % ct)
-                    if ('phase' in getattr(tr, 'names', ())
+                    trnames = getattr(tr, 'names', getattr(tr, '_names', ()))
+                    if ('phase' in trnames
                             or any(n.startswith('push-response')
-                                   for n in getattr(tr, 'names', ()))):
+                                   for n in trnames)):
                         ui.status(_("(use 'hg topic --clear' to clear it if needed)\n"))
                 hint = _("(see 'hg help topics' for more information)\n")
                 if ctwasempty and not empty:
@@ -532,18 +547,29 @@
         """:topic: String. The topic of the changeset"""
         ctx = context.resource(mapping, 'ctx')
         return ctx.topic()
+
+    @templatekeyword('topicidx', requires={'ctx'})
+    def topicidxkw(context, mapping):
+        """:topicidx: Integer. Index of the changeset as a stack alias"""
+        ctx = context.resource(mapping, 'ctx')
+        return ctx.topicidx()
 else:
     def topickw(**args):
         """:topic: String. The topic of the changeset"""
         return args['ctx'].topic()
 
+    def topicidxkw(**args):
+        """:topicidx: Integer. Index of the changeset as a stack alias"""
+        return args['ctx'].topicidx()
+
 def wrapinit(orig, self, repo, *args, **kwargs):
     orig(self, repo, *args, **kwargs)
-    if getattr(repo, 'currenttopic', ''):
-        self._extra[constants.extrakey] = repo.currenttopic
-    else:
-        # Empty key will be dropped from extra by another hack at the changegroup level
-        self._extra[constants.extrakey] = ''
+    if constants.extrakey not in self._extra:
+        if getattr(repo, 'currenttopic', ''):
+            self._extra[constants.extrakey] = repo.currenttopic
+        else:
+            # Empty key will be dropped from extra by another hack at the changegroup level
+            self._extra[constants.extrakey] = ''
 
 def wrapadd(orig, cl, manifest, files, desc, transaction, p1, p2, user,
             date=None, extra=None):
--- a/hgext3rd/topic/discovery.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/topic/discovery.py	Fri Oct 12 15:15:07 2018 +0200
@@ -153,7 +153,10 @@
         return
     tr._prepushheads = _nbheads(op.repo)
     reporef = weakref.ref(op.repo)
-    oldvalidator = tr.validator
+    if util.safehasattr(tr, 'validator'): # hg <= 4.7
+        oldvalidator = tr.validator
+    else:
+        oldvalidator = tr._validator
 
     def validator(tr):
         repo = reporef()
@@ -171,7 +174,10 @@
                             % branch)
                     raise error.Abort(msg)
         return oldvalidator(tr)
-    tr.validator = validator
+    if util.safehasattr(tr, 'validator'): # hg <= 4.7
+        tr.validator = validator
+    else:
+        tr._validator = validator
 handlecheckheads.params = frozenset()
 
 def _pushb2phases(orig, pushop, bundler):
--- a/hgext3rd/topic/revset.py	Fri Sep 14 10:51:42 2018 +0200
+++ b/hgext3rd/topic/revset.py	Fri Oct 12 15:15:07 2018 +0200
@@ -24,7 +24,7 @@
 revsetpredicate = registrar.revsetpredicate()
 
 def getstringstrict(x, err):
-    if x and (x[0] == 'string'):
+    if x and x[0] == 'string':
         return x[1]
     raise error.ParseError(err)
 
@@ -51,25 +51,19 @@
     else:
         kind, pattern, matcher = mkmatcher(topic)
 
+        if topic.startswith('literal:') and pattern not in repo.topics:
+            raise error.RepoLookupError("topic '%s' does not exist" % pattern)
+
         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
-
-            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)
+        return (subset & mutable).filter(matches)
 
     s = revset.getset(repo, revset.fullreposet(repo), x)
-    topics = set(repo[r].topic() for r in s)
+    topics = {repo[r].topic() for r in s}
     topics.discard('')
 
     def matches(r):
--- a/tests/test-discovery-obshashrange.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-discovery-obshashrange.t	Fri Oct 12 15:15:07 2018 +0200
@@ -1066,16 +1066,6 @@
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-obscache cache reset (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-obscache in *.???? seconds (5r, 11o) (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-obscache in *.???? seconds (3r, 0o) (glob)
-  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-firstmerge cache reset (glob)
-  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-firstmerge in *.???? seconds (8r) (glob)
-  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-depthcache cache reset (glob)
-  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-depthcache in *.???? seconds (8r) (glob)
-  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-stablesort cache reset (glob)
-  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-stablesort in *.???? seconds (8r) (glob)
-  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-stablerange-mergepoint cache reset (glob)
-  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-stablerange-mergepoint in *.???? seconds (8r) (glob)
-  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-obshashrange cache reset (glob)
-  1970/01/01 00:00:00 * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-obshashrange in *.???? seconds (8r, 11o) (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated base branch cache in *.???? seconds (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> wrote base branch cache with 1 labels and 1 nodes (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> 3 incoming changes - new heads: 4de32a90b66c (glob)
@@ -1084,6 +1074,16 @@
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> writing .hg/cache/tags2-visible with 0 tags (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> log -G exited 0 after *.?? seconds (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> pull (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-stablerange-mergepoint cache reset (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-depthcache cache reset (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-depthcache in *.???? seconds (8r) (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-stablerange-mergepoint in *.???? seconds (8r) (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-obshashrange cache reset (glob)
+  1970/01/01 00:00:00 * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-obshashrange in *.???? seconds (8r, 11o) (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-stablesort cache reset (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-stablesort in *.???? seconds (8r) (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-firstmerge cache reset (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-firstmerge in *.???? seconds (8r) (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> obsdiscovery, 1/8 mismatch - 1 obshashrange queries in *.???? seconds (glob)
   1970/01/01 00:00:00 * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated base branch cache in *.???? seconds (glob)
   1970/01/01 00:00:00 * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> wrote base branch cache with 1 labels and 2 nodes (glob)
@@ -1144,3 +1144,11 @@
   .hg/cache/evoext-stablesortcache-00: size=100
   .hg/cache/evoext_obshashrange_v2.sqlite: size=??* (glob)
   .hg/cache/evoext_stablerange_v2.sqlite: size=??* (glob)
+
+  $ rm -f .hg/cache/evoext*
+  $ ls -1 .hg/cache/ | grep evoext
+  [1]
+  $ hg strip -r 5 --config extensions.strip=
+  saved backup bundle to $TESTTMP/client/.hg/strip-backup/c8d03c1b5e94-b257442b-backup.hg
+  $ f -s .hg/cache/evoext*
+  .hg/cache/evoext-obscache-00: size=70
--- a/tests/test-evolve-abort-orphan.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-evolve-abort-orphan.t	Fri Oct 12 15:15:07 2018 +0200
@@ -157,7 +157,6 @@
   move:[2] added b
   atop:[7] added a
   move:[5] added c
-  atop:[8] added b
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   fix conflicts and see `hg help evolve.interrupted`
@@ -485,7 +484,6 @@
   move:[2] added b
   atop:[4] added a
   move:[3] added c
-  atop:[5] added b
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   fix conflicts and see `hg help evolve.interrupted`
--- a/tests/test-evolve-content-divergence.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-evolve-content-divergence.t	Fri Oct 12 15:15:07 2018 +0200
@@ -378,7 +378,6 @@
   move:[3] added c
   atop:[8] added b
   move:[4] added d
-  atop:[9] added c
   working directory is now at 4ae4427ee9f8
   $ hg glog
   @  10:4ae4427ee9f8 added d
@@ -819,9 +818,7 @@
   move:[2] added b
   atop:[6] watbar to a
   move:[3] added c
-  atop:[7] added b
   move:[4] added d
-  atop:[8] added c
   working directory is now at 15c781f93cac
   $ hg glog
   @  9:15c781f93cac added d
@@ -845,9 +842,7 @@
   move:[2] added b
   atop:[5] watbar to a
   move:[3] added c
-  atop:[6] added b
   move:[4] added d
-  atop:[7] added c
   working directory is now at c72d2885eb51
   $ hg glog
   @  8:c72d2885eb51 added d
--- a/tests/test-evolve-continue.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-evolve-continue.t	Fri Oct 12 15:15:07 2018 +0200
@@ -166,7 +166,6 @@
   move:[5] added c
   atop:[10] added b
   move:[8] added d
-  atop:[11] added c
   working directory is now at 6642d2c9176e
 
   $ hg glog
@@ -237,7 +236,6 @@
   move:[12] added d
   atop:[16] added c
   move:[13] added f
-  atop:[17] added d
   merging f
   warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
   fix conflicts and see `hg help evolve.interrupted`
@@ -252,7 +250,6 @@
   move:[14] added g
   atop:[18] added f
   move:[15] added h
-  atop:[19] added g
   merging h
   warning: conflicts while merging h! (edit, then use 'hg resolve --mark')
   fix conflicts and see `hg help evolve.interrupted`
--- a/tests/test-evolve-issue5832.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-evolve-issue5832.t	Fri Oct 12 15:15:07 2018 +0200
@@ -117,7 +117,6 @@
   move:[2] added b
   atop:[5] added a
   move:[4] merge commit
-  atop:[8] added b
   ancestor '7235ef625ea3' split over multiple topological branches.
   choose an evolve destination:
   0: [62fb70414f99] added c
@@ -259,7 +258,6 @@
   move:[2] added b
   atop:[6] added a
   move:[4] merge commit
-  atop:[9] added b
   ancestor 'cdf2ea1b9312' split over multiple topological branches.
   choose an evolve destination:
   0: [62fb70414f99] added c
--- a/tests/test-evolve-noupdate.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-evolve-noupdate.t	Fri Oct 12 15:15:07 2018 +0200
@@ -64,7 +64,6 @@
   move:[3] added c
   atop:[5] added b
   move:[4] added d
-  atop:[6] added c
 
   $ hg glog
   o  7:b6b20b8eefdc added d
@@ -108,9 +107,7 @@
   move:[5] added b
   atop:[8] added a
   move:[6] added c
-  atop:[9] added b
   move:[7] added d
-  atop:[10] added c
   working directory is now at 12c720cb3782
 
   $ hg glog
--- a/tests/test-evolve-order.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-evolve-order.t	Fri Oct 12 15:15:07 2018 +0200
@@ -62,7 +62,6 @@
   move:[2] add _b
   atop:[4] add _a
   move:[3] add _c
-  atop:[5] add _b
   working directory is now at 52b8f9b04f83
 
 evolve --rev reorders the rev to solve instability. Harder case, obsolescence
@@ -106,9 +105,7 @@
   move:[10] bprime
   atop:[11] asecond
   move:[6] add _c
-  atop:[12] bprime
   move:[7] add _d
-  atop:[13] add _c
   working directory is now at 739f18ac1d03
   $ hg log -G
   @  14:739f18ac1d03@default(draft) add _d
@@ -214,7 +211,6 @@
   move:[17] add c3_
   atop:[28] add c2prime
   move:[18] add c4_
-  atop:[30] add c3_
   working directory is now at 35e7b797ace5
   $ hg log -G -r "desc(_d)::"
   @  31:35e7b797ace5@default(draft) add c4_
--- a/tests/test-evolve-orphan-merge.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-evolve-orphan-merge.t	Fri Oct 12 15:15:07 2018 +0200
@@ -350,7 +350,6 @@
   move:[16] added m
   atop:[20] added l
   move:[19] merge commit
-  atop:[21] added m
   working directory is now at a446ad3e6700
 
   $ hg glog
--- a/tests/test-evolve-stop-orphan.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-evolve-stop-orphan.t	Fri Oct 12 15:15:07 2018 +0200
@@ -242,9 +242,7 @@
   move:[1] added a
   atop:[7] added hgignore
   move:[2] added b
-  atop:[8] added a
   move:[5] added c
-  atop:[9] added b
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   fix conflicts and see `hg help evolve.interrupted`
@@ -357,7 +355,6 @@
   move:[9] added b
   atop:[12] added a
   move:[10] added c
-  atop:[13] added b
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   fix conflicts and see `hg help evolve.interrupted`
--- a/tests/test-evolve-topic.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-evolve-topic.t	Fri Oct 12 15:15:07 2018 +0200
@@ -123,11 +123,19 @@
 
 Run evolve --all
 
+  $ hg stack
+  ### topic: foo
+  ### target: default (branch)
+  s4$ add fff (current unstable)
+  s3$ add eee (unstable)
+  s2: add ddd
+  s1: add ccc
+  s0^ add bbb (base)
+
   $ hg evolve --all
-  move:[4] add eee
-  atop:[10] add ddd
-  move:[11] add fff
-  atop:[12] add eee
+  move:[s3] add eee
+  atop:[s2] add ddd
+  move:[s4] add fff
   working directory is now at 070c5573d8f9
   $ hg log -G
   @  13 - {foo} 070c5573d8f9 add fff (draft)
@@ -164,11 +172,8 @@
   move:[6] add ggg
   atop:[13] add fff
   move:[7] add hhh
-  atop:[14] add ggg
   move:[8] add iii
-  atop:[15] add hhh
   move:[9] add jjj
-  atop:[16] add iii
   working directory is now at 9bf430c106b7
   $ hg log -G
   @  17 - {bar} 9bf430c106b7 add jjj (draft)
@@ -202,10 +207,10 @@
   0 files updated, 0 files merged, 4 files removed, 0 files unresolved
   $ hg prev
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  [12] add eee
+  [s3] add eee
   $ hg next
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  [13] add fff
+  [s4] add fff
   $ hg next
   no children on topic "foo"
   do you want --no-topic
@@ -217,7 +222,7 @@
   $ hg prev
   preserving the current topic 'bar'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  [13] add fff
+  [s4] add fff
   $ hg prev
   no parent in topic "bar"
   (do you want --no-topic)
@@ -269,7 +274,7 @@
 
   $ hg prev
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  [18] add fff
+  [s1] add fff
 
 Testing issue 5708 when we are on obsolete changeset and there is active topic
 ------------------------------------------------------------------------------
@@ -327,7 +332,7 @@
 
   $ hg prev
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  [14] add ggg
+  [s2] add ggg
 
 When the current topic and successors topic are same, but obsolete cset has
 different topic
@@ -375,4 +380,4 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg prev
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  [12] add eee
+  [s3] add eee
--- a/tests/test-evolve.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-evolve.t	Fri Oct 12 15:15:07 2018 +0200
@@ -468,7 +468,6 @@
   atop:[10] dansk!
   merging main-file-1
   move:[9] dansk 3!
-  atop:[11] dansk 2!
   merging main-file-1
   working directory is now at 96abb1319a47
   $ hg log -G
@@ -1358,7 +1357,6 @@
   move:[20] add j2
   atop:[23] add j1
   move:[21] add j3
-  atop:[24] add j2
   working directory is now at 0d9203b74542
   $ glog -r "0cf3707e8971::"
   @  25:0d9203b74542@default(draft) add j3
@@ -1532,7 +1530,6 @@
   move:[35] will be evolved safely
   atop:[37] amended
   move:[36] will cause conflict at evolve
-  atop:[38] will be evolved safely
   merging newfile
   warning: conflicts while merging newfile! (edit, then use 'hg resolve --mark')
   fix conflicts and see `hg help evolve.interrupted`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-pullbundle.t	Fri Oct 12 15:15:07 2018 +0200
@@ -0,0 +1,1276 @@
+  $ . "$TESTDIR/testlib/pythonpath.sh"
+
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > # evolve is providing the stable range code
+  > evolve=
+  > pullbundle=
+  > [experimental]
+  > obshashrange.warm-cache=yes
+  > EOF
+
+basic setup
+
+  $ hg init server
+  $ hg -R server debugbuilddag '.+898:branchpoint+352:mergepoint+267<branchpoint+145/mergepoint+467'
+  $ hg init client
+  $ hg init client2
+  $ hg init client3
+  $ hg init client4
+
+simple initial pull
+-------------------
+
+  $ hg -R server log -G -T '{rev}:{node}\n' -r '0+1234+(::1234 and (merge() or branchpoint()))'
+  o  1234:f864bc82f6a2f2ecb49b83722e0895f9d657b0dd
+  :
+  o  898:1388f909cd2b0685efd4e2ce076d198bce20922c
+  :
+  o  0:1ea73414a91b0920940797d8fc6a11e447f8ea1e
+  
+
+  $ hg -R client pull server -r 1234 --debug --config devel.bundle2.debug=yes | grep -v 'add changeset'
+  pulling from server
+  query 1; heads
+  pullbundle-cache: "missing" set sliced into 6 subranges in *.* seconds (glob)
+  1024 changesets found
+  128 changesets found
+  64 changesets found
+  16 changesets found
+  2 changesets found
+  1 changesets found
+  listing keys for "bookmarks"
+  bundle2-output-bundle: "HG20", 8 parts total
+  bundle2-output: start emission of HG20 stream
+  bundle2-output: bundle parameter: 
+  bundle2-output: start of parts
+  bundle2-output: bundle part: "changegroup"
+  bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
+  bundle2-output: part 0: "CHANGEGROUP"
+  bundle2-output: header chunk size: 44
+  bundle2-output: payload chunk size: 32768
+  bundle2-output: payload chunk size: 32768
+  bundle2-output: payload chunk size: 32768
+  bundle2-output: payload chunk size: 32768
+  bundle2-output: payload chunk size: 32768
+  bundle2-output: payload chunk size: 22368
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "changegroup"
+  bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
+  bundle2-output: part 1: "CHANGEGROUP"
+  bundle2-output: header chunk size: 43
+  bundle2-output: payload chunk size: 23564
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "changegroup"
+  bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
+  bundle2-output: part 2: "CHANGEGROUP"
+  bundle2-output: header chunk size: 42
+  bundle2-output: payload chunk size: 11788
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "changegroup"
+  bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
+  bundle2-output: part 3: "CHANGEGROUP"
+  bundle2-output: header chunk size: 42
+  bundle2-output: payload chunk size: 2956
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "changegroup"
+  bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
+  bundle2-output: part 4: "CHANGEGROUP"
+  bundle2-output: header chunk size: 41
+  bundle2-output: payload chunk size: 380
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "changegroup"
+  bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
+  bundle2-output: part 5: "CHANGEGROUP"
+  bundle2-output: header chunk size: 41
+  bundle2-output: payload chunk size: 196
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "listkeys"
+  bundle2-output-part: "listkeys" (params: 1 mandatory) empty payload
+  bundle2-output: part 6: "LISTKEYS"
+  bundle2-output: header chunk size: 35
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "phase-heads"
+  bundle2-output-part: "phase-heads" 24 bytes payload
+  bundle2-output: part 7: "PHASE-HEADS"
+  bundle2-output: header chunk size: 18
+  bundle2-output: payload chunk size: 24
+  bundle2-output: closing payload chunk
+  bundle2-output: end of bundle
+  bundle2-input: start processing of HG20 stream
+  bundle2-input: reading bundle2 stream parameters
+  bundle2-input-bundle: with-transaction
+  bundle2-input: start extraction of bundle2 parts
+  bundle2-input: part header size: 44
+  bundle2-input: part type: "CHANGEGROUP"
+  bundle2-input: part id: "0"
+  bundle2-input: part parameters: 2
+  bundle2-input: found a handler for part changegroup
+  bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
+  adding changesets
+  bundle2-input: payload chunk size: 32768
+  bundle2-input: payload chunk size: 32768
+  bundle2-input: payload chunk size: 32768
+  bundle2-input: payload chunk size: 32768
+  bundle2-input: payload chunk size: 32768
+  bundle2-input: payload chunk size: 22368
+  bundle2-input: payload chunk size: 0
+  adding manifests
+  adding file changes
+  added 1024 changesets with 0 changes to 0 files
+  bundle2-input-part: total payload size 186208
+  bundle2-input: part header size: 43
+  bundle2-input: part type: "CHANGEGROUP"
+  bundle2-input: part id: "1"
+  bundle2-input: part parameters: 2
+  bundle2-input: found a handler for part changegroup
+  bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
+  adding changesets
+  bundle2-input: payload chunk size: 23564
+  bundle2-input: payload chunk size: 0
+  adding manifests
+  adding file changes
+  added 128 changesets with 0 changes to 0 files
+  bundle2-input-part: total payload size 23564
+  bundle2-input: part header size: 42
+  bundle2-input: part type: "CHANGEGROUP"
+  bundle2-input: part id: "2"
+  bundle2-input: part parameters: 2
+  bundle2-input: found a handler for part changegroup
+  bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
+  adding changesets
+  bundle2-input: payload chunk size: 11788
+  bundle2-input: payload chunk size: 0
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  bundle2-input-part: total payload size 11788
+  bundle2-input: part header size: 42
+  bundle2-input: part type: "CHANGEGROUP"
+  bundle2-input: part id: "3"
+  bundle2-input: part parameters: 2
+  bundle2-input: found a handler for part changegroup
+  bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
+  adding changesets
+  bundle2-input: payload chunk size: 2956
+  bundle2-input: payload chunk size: 0
+  adding manifests
+  adding file changes
+  added 16 changesets with 0 changes to 0 files
+  bundle2-input-part: total payload size 2956
+  bundle2-input: part header size: 41
+  bundle2-input: part type: "CHANGEGROUP"
+  bundle2-input: part id: "4"
+  bundle2-input: part parameters: 2
+  bundle2-input: found a handler for part changegroup
+  bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
+  adding changesets
+  bundle2-input: payload chunk size: 380
+  bundle2-input: payload chunk size: 0
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 0 files
+  bundle2-input-part: total payload size 380
+  bundle2-input: part header size: 41
+  bundle2-input: part type: "CHANGEGROUP"
+  bundle2-input: part id: "5"
+  bundle2-input: part parameters: 2
+  bundle2-input: found a handler for part changegroup
+  bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
+  adding changesets
+  bundle2-input: payload chunk size: 196
+  bundle2-input: payload chunk size: 0
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files
+  bundle2-input-part: total payload size 196
+  bundle2-input: part header size: 35
+  bundle2-input: part type: "LISTKEYS"
+  bundle2-input: part id: "6"
+  bundle2-input: part parameters: 1
+  bundle2-input: found a handler for part listkeys
+  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
+  bundle2-input: payload chunk size: 0
+  bundle2-input: part header size: 18
+  bundle2-input: part type: "PHASE-HEADS"
+  bundle2-input: part id: "7"
+  bundle2-input: part parameters: 0
+  bundle2-input: found a handler for part phase-heads
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input: payload chunk size: 24
+  bundle2-input: payload chunk size: 0
+  bundle2-input-part: total payload size 24
+  bundle2-input: part header size: 0
+  bundle2-input: end of bundle2 stream
+  bundle2-input-bundle: 7 parts total
+  checking for updated bookmarks
+  updating the branch cache
+  new changesets 1ea73414a91b:f864bc82f6a2
+  (run 'hg update' to get a working copy)
+
+  $ touch oldbundles
+  $ ls -1 server/.hg/cache/pullbundles > newbundles
+  $ diff -u oldbundles newbundles
+  --- oldbundles	* (glob)
+  +++ newbundles	* (glob)
+  @@ -0,0 +1,6 @@
+  +02-467b6e370e816747e27de0d0b9237f4090a33656-0000001152skip-0000000064size.hg
+  +02-540f762640ee62ca597ece26af725e6357e82805-0000000000skip-0000001024size.hg
+  +02-63ded94ceab180ac2fa13e1f0beeb4d2265998a3-0000001232skip-0000000002size.hg
+  +02-7f3a79522d6e904d52aea07c71e6cb612667e8f4-0000001216skip-0000000016size.hg
+  +02-ee2deecf044fa5583f66188c9177b0f13332adc2-0000001024skip-0000000128size.hg
+  +02-f864bc82f6a2f2ecb49b83722e0895f9d657b0dd-0000001234skip-0000000001size.hg
+  [1]
+
+pull the other missing entries (multiple heads pulled)
+------------------------------------------------------
+
+  $ hg -R server log -G -T '{rev}:{node}\n' -r '1234+head()+(only(head(), 1234) and (merge() or branchpoint()))'
+  o  2130:0f376356904fc8c1c6ceaac27990f2fd79b1f8c1
+  :
+  o    1663:1710092b3ab17a6d2ecad664580991a608537749
+  |\
+  | ~
+  | o  1517:1dded5aafa0f8d548f6357cc2f8882dcc4489fbf
+  |/
+  o  1250:d83212ecaa436c80d6113cf915ba35e2db787e79
+  :
+  o  1234:f864bc82f6a2f2ecb49b83722e0895f9d657b0dd
+  |
+  ~
+  $ hg -R client pull server --verbose
+  pulling from server
+  searching for changes
+  all local heads known remotely
+  pullbundle-cache: "missing" set sliced into 18 subranges in *.* seconds (glob)
+  1 changesets found
+  4 changesets found
+  8 changesets found
+  32 changesets found
+  128 changesets found
+  64 changesets found
+  32 changesets found
+  8 changesets found
+  4 changesets found
+  2 changesets found
+  1 changesets found
+  4 changesets found
+  8 changesets found
+  16 changesets found
+  256 changesets found
+  256 changesets found
+  64 changesets found
+  8 changesets found
+  uncompressed size of bundle content:
+       188 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       740 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      1476 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      5892 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+     23556 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+     11780 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      5892 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      1476 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       740 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       372 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       188 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       740 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      1476 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      2948 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+     47108 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+     47108 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+     11780 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      1476 (changelog)
+         4 (manifests)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 32 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 128 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 32 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 16 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  new changesets 17185c1c22f1:0f376356904f
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+
+  $ mv newbundles oldbundles
+  $ ls -1 server/.hg/cache/pullbundles > newbundles
+  $ diff -u oldbundles newbundles
+  --- oldbundles	* (glob)
+  +++ newbundles	* (glob)
+  @@ -1,6 +1,24 @@
+  +02-0f376356904fc8c1c6ceaac27990f2fd79b1f8c1-0000001856skip-0000000008size.hg
+  +02-17185c1c22f1266b084daf7cfb07b6ebbfbc65ab-0000001235skip-0000000001size.hg
+  +02-1dded5aafa0f8d548f6357cc2f8882dcc4489fbf-0000001516skip-0000000002size.hg
+  +02-2dc4f1ab9029719714b8e0dde8e3725a5bb28472-0000001408skip-0000000064size.hg
+  +02-2f0e261a08964bc1c607c0eda4978364c22a9b94-0000001504skip-0000000008size.hg
+   02-467b6e370e816747e27de0d0b9237f4090a33656-0000001152skip-0000000064size.hg
+  +02-484c46df3e41f371efd0ff74fa5221657527213f-0000001240skip-0000000008size.hg
+  +02-4a6d0f7d07d060b026d9fc690cd89cd26af96e42-0000001248skip-0000000032size.hg
+   02-540f762640ee62ca597ece26af725e6357e82805-0000000000skip-0000001024size.hg
+   02-63ded94ceab180ac2fa13e1f0beeb4d2265998a3-0000001232skip-0000000002size.hg
+  +02-694ef7e5b2984f1ec66c3d960799f4ff2459672c-0000001236skip-0000000004size.hg
+   02-7f3a79522d6e904d52aea07c71e6cb612667e8f4-0000001216skip-0000000016size.hg
+  +02-89fab188d2ce3c4cde6be031f2fc5b9b4ff248e3-0000000900skip-0000000004size.hg
+  +02-97ede4832194ed56894374f2a1cc7a0022b486da-0000000904skip-0000000008size.hg
+  +02-b2d350c94c26edbb783aaa21fc24f1fc65c30e74-0000001536skip-0000000256size.hg
+  +02-bbd293bd171fd5b711d428db46940a72eca7a40f-0000001280skip-0000000128size.hg
+  +02-c72277ff25807eb444fa48a60afb434d78c21f2f-0000000899skip-0000000001size.hg
+  +02-da87a81c5310760f414a933e6550b7e8e60cf241-0000001792skip-0000000064size.hg
+  +02-dba2fddbf3c28198659046674a512afd616a1519-0000001472skip-0000000032size.hg
+  +02-e469a7aa5cce57653b6b02ff46c80b2d94d62629-0000000912skip-0000000016size.hg
+  +02-e74670ea99533967c5d90da3ddbc0318cc1fd502-0000001280skip-0000000256size.hg
+   02-ee2deecf044fa5583f66188c9177b0f13332adc2-0000001024skip-0000000128size.hg
+   02-f864bc82f6a2f2ecb49b83722e0895f9d657b0dd-0000001234skip-0000000001size.hg
+  +02-fb6c210a224903e81e5a8d2ee099cb0c9526ba8c-0000001512skip-0000000004size.hg
+  [1]
+
+Same Pullin with a different client
+-----------------------------------
+  $ hg -R server log -G -T '{rev}:{node}\n' -r '0+1234+(::1234 and (merge() or branchpoint()))'
+  o  1234:f864bc82f6a2f2ecb49b83722e0895f9d657b0dd
+  :
+  o  898:1388f909cd2b0685efd4e2ce076d198bce20922c
+  :
+  o  0:1ea73414a91b0920940797d8fc6a11e447f8ea1e
+  
+
+  $ hg -R client2 pull server -r 1234 --verbose
+  pulling from server
+  pullbundle-cache: "missing" set sliced into 6 subranges in *.* seconds (glob)
+  1024 changesets found in caches
+  128 changesets found in caches
+  64 changesets found in caches
+  16 changesets found in caches
+  2 changesets found in caches
+  1 changesets found in caches
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1024 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 128 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 16 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files
+  new changesets 1ea73414a91b:f864bc82f6a2
+  (run 'hg update' to get a working copy)
+
+  $ mv newbundles oldbundles
+  $ ls -1 server/.hg/cache/pullbundles > newbundles
+  $ diff -u oldbundles newbundles
+
+  $ hg -R server log -G -T '{rev}:{node}\n' -r '1234+head()+(only(head(), 1234) and (merge() or branchpoint()))'
+  o  2130:0f376356904fc8c1c6ceaac27990f2fd79b1f8c1
+  :
+  o    1663:1710092b3ab17a6d2ecad664580991a608537749
+  |\
+  | ~
+  | o  1517:1dded5aafa0f8d548f6357cc2f8882dcc4489fbf
+  |/
+  o  1250:d83212ecaa436c80d6113cf915ba35e2db787e79
+  :
+  o  1234:f864bc82f6a2f2ecb49b83722e0895f9d657b0dd
+  |
+  ~
+
+  $ hg -R client2 pull server --verbose
+  pulling from server
+  searching for changes
+  all local heads known remotely
+  pullbundle-cache: "missing" set sliced into 18 subranges in *.* seconds (glob)
+  1 changesets found in caches
+  4 changesets found in caches
+  8 changesets found in caches
+  32 changesets found in caches
+  128 changesets found in caches
+  64 changesets found in caches
+  32 changesets found in caches
+  8 changesets found in caches
+  4 changesets found in caches
+  2 changesets found in caches
+  1 changesets found in caches
+  4 changesets found in caches
+  8 changesets found in caches
+  16 changesets found in caches
+  256 changesets found in caches
+  256 changesets found in caches
+  64 changesets found in caches
+  8 changesets found in caches
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 32 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 128 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 32 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 16 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  new changesets 17185c1c22f1:0f376356904f
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+
+  $ mv newbundles oldbundles
+  $ ls -1 server/.hg/cache/pullbundles > newbundles
+  $ diff -u oldbundles newbundles
+
+different pull with a different client
+--------------------------------------
+
+  $ hg -R server log -G -T '{rev}:{node}\n' -r '87232049c8d1+0+1789+(::1789 and (merge() or branchpoint()))'
+  o  2085:87232049c8d1f413105bf813b6bfc21da3e26a4f
+  :
+  o  1789:44e80141ad530a2aa085e9bd9b5311b57eff72ff
+  :
+  o    1663:1710092b3ab17a6d2ecad664580991a608537749
+  |\
+  o :  1250:d83212ecaa436c80d6113cf915ba35e2db787e79
+  :/
+  o  898:1388f909cd2b0685efd4e2ce076d198bce20922c
+  :
+  o  0:1ea73414a91b0920940797d8fc6a11e447f8ea1e
+  
+
+  $ hg -R client3 pull server -r 1789 --verbose
+  pulling from server
+  pullbundle-cache: "missing" set sliced into 9 subranges in *.* seconds (glob)
+  1024 changesets found in caches
+  227 changesets found
+  29 changesets found
+  128 changesets found
+  64 changesets found
+  32 changesets found
+  16 changesets found
+  2 changesets found
+  1 changesets found
+  uncompressed size of bundle content:
+     41772 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      5340 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+     23556 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+     11780 (changelog)
+         4 (manifests)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1024 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 227 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 29 changesets with 0 changes to 0 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 128 changesets with 0 changes to 0 files (-1 heads)
+  adding changesets
+  uncompressed size of bundle content:
+      5892 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      2948 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       372 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       188 (changelog)
+         4 (manifests)
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 32 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 16 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files
+  new changesets 1ea73414a91b:44e80141ad53
+  (run 'hg update' to get a working copy)
+
+  $ mv newbundles oldbundles
+  $ ls -1 server/.hg/cache/pullbundles > newbundles
+  $ diff -u oldbundles newbundles
+  --- oldbundles	* (glob)
+  +++ newbundles	* (glob)
+  @@ -3,20 +3,28 @@
+   02-1dded5aafa0f8d548f6357cc2f8882dcc4489fbf-0000001516skip-0000000002size.hg
+   02-2dc4f1ab9029719714b8e0dde8e3725a5bb28472-0000001408skip-0000000064size.hg
+   02-2f0e261a08964bc1c607c0eda4978364c22a9b94-0000001504skip-0000000008size.hg
+  +02-44e80141ad530a2aa085e9bd9b5311b57eff72ff-0000001522skip-0000000001size.hg
+   02-467b6e370e816747e27de0d0b9237f4090a33656-0000001152skip-0000000064size.hg
+   02-484c46df3e41f371efd0ff74fa5221657527213f-0000001240skip-0000000008size.hg
+   02-4a6d0f7d07d060b026d9fc690cd89cd26af96e42-0000001248skip-0000000032size.hg
+   02-540f762640ee62ca597ece26af725e6357e82805-0000000000skip-0000001024size.hg
+  +02-59e60b258b18cde1e931cf30ce4ae62b49e37abd-0000001520skip-0000000002size.hg
+   02-63ded94ceab180ac2fa13e1f0beeb4d2265998a3-0000001232skip-0000000002size.hg
+   02-694ef7e5b2984f1ec66c3d960799f4ff2459672c-0000001236skip-0000000004size.hg
+  +02-7a55a4d5ce324910842c893b56173cf2a847cb9d-0000001472skip-0000000032size.hg
+   02-7f3a79522d6e904d52aea07c71e6cb612667e8f4-0000001216skip-0000000016size.hg
+   02-89fab188d2ce3c4cde6be031f2fc5b9b4ff248e3-0000000900skip-0000000004size.hg
+   02-97ede4832194ed56894374f2a1cc7a0022b486da-0000000904skip-0000000008size.hg
+  +02-a4ab7df9d74053fb819c8a1c6a48ad605cc05f8a-0000001504skip-0000000016size.hg
+   02-b2d350c94c26edbb783aaa21fc24f1fc65c30e74-0000001536skip-0000000256size.hg
+   02-bbd293bd171fd5b711d428db46940a72eca7a40f-0000001280skip-0000000128size.hg
+  +02-c12927fef661d2463043347101b90067c2961333-0000001280skip-0000000128size.hg
+   02-c72277ff25807eb444fa48a60afb434d78c21f2f-0000000899skip-0000000001size.hg
+  +02-d83212ecaa436c80d6113cf915ba35e2db787e79-0000001024skip-0000000227size.hg
+   02-da87a81c5310760f414a933e6550b7e8e60cf241-0000001792skip-0000000064size.hg
+   02-dba2fddbf3c28198659046674a512afd616a1519-0000001472skip-0000000032size.hg
+  +02-dc714c3a5d080165292ba99b097567d0b95e5756-0000001408skip-0000000064size.hg
+  +02-e469a7aa5cce57653b6b02ff46c80b2d94d62629-0000000899skip-0000000029size.hg
+   02-e469a7aa5cce57653b6b02ff46c80b2d94d62629-0000000912skip-0000000016size.hg
+   02-e74670ea99533967c5d90da3ddbc0318cc1fd502-0000001280skip-0000000256size.hg
+   02-ee2deecf044fa5583f66188c9177b0f13332adc2-0000001024skip-0000000128size.hg
+  [1]
+
+  $ hg -R server log -G -T '{rev}:{node}\n' -r '1789+head()+parents(roots(only(head(), 1789)))+(only(head(), 1789) and (merge() or branchpoint()))'
+  o  2130:0f376356904fc8c1c6ceaac27990f2fd79b1f8c1
+  :
+  o  1789:44e80141ad530a2aa085e9bd9b5311b57eff72ff
+  :
+  : o  1517:1dded5aafa0f8d548f6357cc2f8882dcc4489fbf
+  :/
+  o  1250:d83212ecaa436c80d6113cf915ba35e2db787e79
+  |
+  ~
+
+  $ hg -R client3 pull server --verbose
+  pulling from server
+  searching for changes
+  all local heads known remotely
+  pullbundle-cache: "missing" set sliced into 16 subranges in *.* seconds (glob)
+  1 changesets found
+  4 changesets found
+  8 changesets found
+  16 changesets found
+  128 changesets found in caches
+  64 changesets found in caches
+  32 changesets found in caches
+  8 changesets found in caches
+  4 changesets found in caches
+  2 changesets found in caches
+  1 changesets found
+  4 changesets found
+  8 changesets found
+  256 changesets found in caches
+  64 changesets found in caches
+  8 changesets found in caches
+  uncompressed size of bundle content:
+       188 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       740 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      1476 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      2948 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       188 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       740 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      1476 (changelog)
+         4 (manifests)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 16 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 128 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 32 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  new changesets d1807e351389:0f376356904f
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+
+  $ mv newbundles oldbundles
+  $ ls -1 server/.hg/cache/pullbundles > newbundles
+  $ diff -u oldbundles newbundles
+  --- oldbundles	* (glob)
+  +++ newbundles	* (glob)
+  @@ -1,14 +1,17 @@
+   02-0f376356904fc8c1c6ceaac27990f2fd79b1f8c1-0000001856skip-0000000008size.hg
+   02-17185c1c22f1266b084daf7cfb07b6ebbfbc65ab-0000001235skip-0000000001size.hg
+   02-1dded5aafa0f8d548f6357cc2f8882dcc4489fbf-0000001516skip-0000000002size.hg
+  +02-1ed78f99f705cb819a02f1227c217728d008e461-0000001524skip-0000000004size.hg
+   02-2dc4f1ab9029719714b8e0dde8e3725a5bb28472-0000001408skip-0000000064size.hg
+   02-2f0e261a08964bc1c607c0eda4978364c22a9b94-0000001504skip-0000000008size.hg
+   02-44e80141ad530a2aa085e9bd9b5311b57eff72ff-0000001522skip-0000000001size.hg
+   02-467b6e370e816747e27de0d0b9237f4090a33656-0000001152skip-0000000064size.hg
+   02-484c46df3e41f371efd0ff74fa5221657527213f-0000001240skip-0000000008size.hg
+   02-4a6d0f7d07d060b026d9fc690cd89cd26af96e42-0000001248skip-0000000032size.hg
+  +02-4a6d0f7d07d060b026d9fc690cd89cd26af96e42-0000001264skip-0000000016size.hg
+   02-540f762640ee62ca597ece26af725e6357e82805-0000000000skip-0000001024size.hg
+   02-59e60b258b18cde1e931cf30ce4ae62b49e37abd-0000001520skip-0000000002size.hg
+  +02-5eaa75df73c454c1afff722301a4c73e897de94d-0000001256skip-0000000008size.hg
+   02-63ded94ceab180ac2fa13e1f0beeb4d2265998a3-0000001232skip-0000000002size.hg
+   02-694ef7e5b2984f1ec66c3d960799f4ff2459672c-0000001236skip-0000000004size.hg
+   02-7a55a4d5ce324910842c893b56173cf2a847cb9d-0000001472skip-0000000032size.hg
+  @@ -19,7 +22,10 @@
+   02-b2d350c94c26edbb783aaa21fc24f1fc65c30e74-0000001536skip-0000000256size.hg
+   02-bbd293bd171fd5b711d428db46940a72eca7a40f-0000001280skip-0000000128size.hg
+   02-c12927fef661d2463043347101b90067c2961333-0000001280skip-0000000128size.hg
+  +02-c232505f58fdf70bcf5f6ab6a555f23ffc74f761-0000001523skip-0000000001size.hg
+   02-c72277ff25807eb444fa48a60afb434d78c21f2f-0000000899skip-0000000001size.hg
+  +02-ca970a853ea24846035ccb324cc8de49ef768748-0000001252skip-0000000004size.hg
+  +02-d1807e3513890ac71c2e8d10e9dc9a5b58b15d4b-0000001251skip-0000000001size.hg
+   02-d83212ecaa436c80d6113cf915ba35e2db787e79-0000001024skip-0000000227size.hg
+   02-da87a81c5310760f414a933e6550b7e8e60cf241-0000001792skip-0000000064size.hg
+   02-dba2fddbf3c28198659046674a512afd616a1519-0000001472skip-0000000032size.hg
+  @@ -27,6 +33,7 @@
+   02-e469a7aa5cce57653b6b02ff46c80b2d94d62629-0000000899skip-0000000029size.hg
+   02-e469a7aa5cce57653b6b02ff46c80b2d94d62629-0000000912skip-0000000016size.hg
+   02-e74670ea99533967c5d90da3ddbc0318cc1fd502-0000001280skip-0000000256size.hg
+  +02-e74670ea99533967c5d90da3ddbc0318cc1fd502-0000001528skip-0000000008size.hg
+   02-ee2deecf044fa5583f66188c9177b0f13332adc2-0000001024skip-0000000128size.hg
+   02-f864bc82f6a2f2ecb49b83722e0895f9d657b0dd-0000001234skip-0000000001size.hg
+   02-fb6c210a224903e81e5a8d2ee099cb0c9526ba8c-0000001512skip-0000000004size.hg
+  [1]
+
+Single pull coming after various cache warming
+----------------------------------------------
+
+  $ hg -R client4 pull --verbose server | grep -v 'add changeset'
+  pulling from server
+  requesting all changes
+  pullbundle-cache: "missing" set sliced into 16 subranges in *.* seconds (glob)
+  1024 changesets found in caches
+  256 changesets found
+  128 changesets found in caches
+  64 changesets found in caches
+  32 changesets found in caches
+  8 changesets found in caches
+  4 changesets found in caches
+  2 changesets found in caches
+  1 changesets found in caches
+  4 changesets found in caches
+  8 changesets found in caches
+  16 changesets found in caches
+  256 changesets found in caches
+  256 changesets found in caches
+  64 changesets found in caches
+  8 changesets found in caches
+  uncompressed size of bundle content:
+     47108 (changelog)
+         4 (manifests)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1024 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 128 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 32 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 16 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  new changesets 1ea73414a91b:0f376356904f
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+
+  $ mv newbundles oldbundles
+  $ ls -1 server/.hg/cache/pullbundles > newbundles
+  $ diff -u oldbundles newbundles
+  --- oldbundles	* (glob)
+  +++ newbundles	* (glob)
+  @@ -7,6 +7,7 @@
+   02-44e80141ad530a2aa085e9bd9b5311b57eff72ff-0000001522skip-0000000001size.hg
+   02-467b6e370e816747e27de0d0b9237f4090a33656-0000001152skip-0000000064size.hg
+   02-484c46df3e41f371efd0ff74fa5221657527213f-0000001240skip-0000000008size.hg
+  +02-4a6d0f7d07d060b026d9fc690cd89cd26af96e42-0000001024skip-0000000256size.hg
+   02-4a6d0f7d07d060b026d9fc690cd89cd26af96e42-0000001248skip-0000000032size.hg
+   02-4a6d0f7d07d060b026d9fc690cd89cd26af96e42-0000001264skip-0000000016size.hg
+   02-540f762640ee62ca597ece26af725e6357e82805-0000000000skip-0000001024size.hg
+  [1]
+
+Pull with piece "not linear from head"
+--------------------------------------
+
+  $ hg -R server log -G -T '{rev}:{node}\n' -r 'branchpoint() + merge() + head() + children(branchpoint())'
+  o  2130:0f376356904fc8c1c6ceaac27990f2fd79b1f8c1
+  :
+  o    1663:1710092b3ab17a6d2ecad664580991a608537749
+  |\
+  | o  1518:c72277ff25807eb444fa48a60afb434d78c21f2f
+  | |
+  | | o  1517:1dded5aafa0f8d548f6357cc2f8882dcc4489fbf
+  | | :
+  +---o  1251:d1807e3513890ac71c2e8d10e9dc9a5b58b15d4b
+  | |
+  o |  1250:d83212ecaa436c80d6113cf915ba35e2db787e79
+  : |
+  o |  899:c31a4e0cc28d677b8020e46aa3bb2fd5ee5b1a06
+  |/
+  o  898:1388f909cd2b0685efd4e2ce076d198bce20922c
+  |
+  ~
+
+  $ hg init test-local-missing
+  $ hg -R test-local-missing pull server --rev 899 --rev 1518 --verbose
+  pulling from server
+  pullbundle-cache: "missing" set sliced into 5 subranges in *.* seconds (glob)
+  512 changesets found
+  256 changesets found
+  128 changesets found
+  4 changesets found
+  1 changesets found
+  uncompressed size of bundle content:
+     92968 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+     46596 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+     23300 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       734 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+       186 (changelog)
+         4 (manifests)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 512 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 128 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 1ea73414a91b:c31a4e0cc28d
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg -R test-local-missing pull server --verbose
+  pulling from server
+  searching for changes
+  all local heads known remotely
+  pullbundle-cache: "missing" set sliced into 19 subranges in *.* seconds (glob)
+  4 changesets found
+  8 changesets found
+  16 changesets found
+  32 changesets found
+  64 changesets found
+  256 changesets found in caches
+  128 changesets found in caches
+  64 changesets found in caches
+  32 changesets found in caches
+  8 changesets found in caches
+  4 changesets found in caches
+  2 changesets found in caches
+  4 changesets found in caches
+  8 changesets found in caches
+  16 changesets found in caches
+  256 changesets found in caches
+  256 changesets found in caches
+  64 changesets found in caches
+  8 changesets found in caches
+  uncompressed size of bundle content:
+       732 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      1460 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      2916 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+      5828 (changelog)
+         4 (manifests)
+  uncompressed size of bundle content:
+     11700 (changelog)
+         4 (manifests)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 16 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 32 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 128 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 32 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 16 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  new changesets e600b80a2fc8:0f376356904f
+  (run 'hg update' to get a working copy)
+
+Test cache setting
+==================
+
+cache directory
+---------------
+
+  $ mkdir bundle-cache
+  $ cat << EOF >> $HGRCPATH
+  > [pullbundle]
+  > cache-directory=$TESTTMP/bundle-cache
+  > EOF
+
+  $ hg clone --pull server other-cache-directory
+  requesting all changes
+  pullbundle-cache: "missing" set sliced into 16 subranges in *.* seconds (glob)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1024 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 128 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 32 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files (+1 heads)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 16 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 256 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 64 changesets with 0 changes to 0 files
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 0 changes to 0 files
+  new changesets 1ea73414a91b:0f376356904f
+  updating to branch default
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ ls -1 bundle-cache
+  02-0f376356904fc8c1c6ceaac27990f2fd79b1f8c1-0000001856skip-0000000008size.hg
+  02-1dded5aafa0f8d548f6357cc2f8882dcc4489fbf-0000001516skip-0000000002size.hg
+  02-2dc4f1ab9029719714b8e0dde8e3725a5bb28472-0000001408skip-0000000064size.hg
+  02-2f0e261a08964bc1c607c0eda4978364c22a9b94-0000001504skip-0000000008size.hg
+  02-4a6d0f7d07d060b026d9fc690cd89cd26af96e42-0000001024skip-0000000256size.hg
+  02-540f762640ee62ca597ece26af725e6357e82805-0000000000skip-0000001024size.hg
+  02-89fab188d2ce3c4cde6be031f2fc5b9b4ff248e3-0000000900skip-0000000004size.hg
+  02-97ede4832194ed56894374f2a1cc7a0022b486da-0000000904skip-0000000008size.hg
+  02-b2d350c94c26edbb783aaa21fc24f1fc65c30e74-0000001536skip-0000000256size.hg
+  02-bbd293bd171fd5b711d428db46940a72eca7a40f-0000001280skip-0000000128size.hg
+  02-c72277ff25807eb444fa48a60afb434d78c21f2f-0000000899skip-0000000001size.hg
+  02-da87a81c5310760f414a933e6550b7e8e60cf241-0000001792skip-0000000064size.hg
+  02-dba2fddbf3c28198659046674a512afd616a1519-0000001472skip-0000000032size.hg
+  02-e469a7aa5cce57653b6b02ff46c80b2d94d62629-0000000912skip-0000000016size.hg
+  02-e74670ea99533967c5d90da3ddbc0318cc1fd502-0000001280skip-0000000256size.hg
+  02-fb6c210a224903e81e5a8d2ee099cb0c9526ba8c-0000001512skip-0000000004size.hg
--- a/tests/test-split.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-split.t	Fri Oct 12 15:15:07 2018 +0200
@@ -151,7 +151,6 @@
   move:[5] split1
   atop:[7] _cprim
   move:[6] split2
-  atop:[8] split1
   working directory is now at * (glob)
   $ hg log -r "desc(_cprim)" -v -p
   changeset:   7:b434287e665c
@@ -219,7 +218,6 @@
   move:[8] split1
   atop:[11] split4
   move:[9] split2
-  atop:[12] split1
   working directory is now at d74c6715e706
   $ hg log -G
   @  changeset:   13:d74c6715e706
--- a/tests/test-topic-stack-complex.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-topic-stack-complex.t	Fri Oct 12 15:15:07 2018 +0200
@@ -48,7 +48,7 @@
   s0^ Added foo (base)
   $ hg prev
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  [2] Added c and d
+  [s2] Added c and d
 
   $ echo 0 > num
   $ cat > editor.sh << '__EOF__'
@@ -103,7 +103,7 @@
 
   $ hg prev
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  [4] split1
+  [s2] split1
   $ echo foo > c
   $ hg diff
   diff -r f26c1b9addde c
--- a/tests/test-topic-tutorial.t	Fri Sep 14 10:51:42 2018 +0200
+++ b/tests/test-topic-tutorial.t	Fri Oct 12 15:15:07 2018 +0200
@@ -1150,7 +1150,7 @@
 
   $ hg previous
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  [14] Adding saw
+  [s2] Adding saw
 
   $ hg stack
   ### topic: tools
@@ -1164,7 +1164,7 @@
 
   $ hg next
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  [15] Adding drill
+  [s3] Adding drill
 
   $ hg stack
   ### topic: tools