merge with stable
authorPierre-Yves David <pierre-yves.david@octobus.net>
Wed, 17 May 2017 14:49:02 +0200
changeset 2395 5774c27ce927
parent 2394 1b3a797c4586 (diff)
parent 2384 2ddbdc1bab13 (current diff)
child 2396 c1485ebdd6b9
merge with stable
--- a/README	Wed May 17 11:47:14 2017 +0200
+++ b/README	Wed May 17 14:49:02 2017 +0200
@@ -112,6 +112,17 @@
 Changelog
 =========
 
+6.2.0 - in progress
+-------------------
+
+ - add a debugobshistory command to inspect the obs-history of a changeset
+ - topic: have thg display topic name if possible,
+ - obscache: more efficient update in the (rare) case of a transaction adding
+   markers without changesets
+ - obshashrange-cache: update incrementally in the (common) case of a
+   transaction not affecting existing range,
+ - obshashrange-cache: keep the cache mostly warm after each transaction.
+
 6.1.1 - in progress
 -------------------
 
--- a/hgext3rd/evolve/__init__.py	Wed May 17 11:47:14 2017 +0200
+++ b/hgext3rd/evolve/__init__.py	Wed May 17 14:49:02 2017 +0200
@@ -162,6 +162,15 @@
 
 obsexcmsg = utility.obsexcmsg
 
+colortable = {'evolve.short_node': 'yellow',
+              'evolve.user': 'green',
+              'evolve.rev': 'blue',
+              'evolve.short_description': '',
+              'evolve.date': 'cyan',
+              'evolve.current_rev': 'bold',
+              'evolve.verb': '',
+              }
+
 _pack = struct.pack
 _unpack = struct.unpack
 
@@ -3249,3 +3258,109 @@
                 f.write(orig.topic())
 
     return merge.graft(repo, orig, pctx, ['local', 'graft'], True)
+
+@eh.command(
+    '^debugobshistory',
+    [] + commands.formatteropts,
+    _('hg debugobshistory [OPTION]... [REV]'))
+def debugobshistory(ui, repo, *revs, **opts):
+    revs = scmutil.revrange(repo, revs)
+    fm = ui.formatter('debugobshistory', opts)
+
+    revs.reverse()
+    _debugobshistorysingle(fm, repo, revs)
+
+    fm.end()
+
+def _debugobshistorysingle(fm, repo, revs):
+    """ Display the obsolescence history for a single revision
+    """
+    precursors = repo.obsstore.precursors
+    successors = repo.obsstore.successors
+    nodec = repo.changelog.node
+    nodes = [nodec(r) for r in revs]
+
+    seen = set(nodes)
+
+    while nodes:
+        ctxnode = nodes.pop()
+
+        _debugobshistorydisplaynode(fm, repo, ctxnode)
+
+        succs = successors.get(ctxnode, ())
+
+        markerfm = fm.nested("debugobshistory.markers")
+        for successor in sorted(succs):
+            _debugobshistorydisplaymarker(markerfm, repo, successor)
+        markerfm.end()
+
+        precs = precursors.get(ctxnode, ())
+        for p in sorted(precs):
+            # Only show nodes once
+            if p[0] not in seen:
+                seen.add(p[0])
+                nodes.append(p[0])
+
+def _debugobshistorydisplaynode(fm, repo, node):
+    if node in repo.unfiltered():
+        _debugobshistorydisplayctx(fm, repo.unfiltered()[node])
+    else:
+        _debugobshistorydisplaymissingctx(fm, node)
+
+def _debugobshistorydisplayctx(fm, ctx):
+    shortdescription = ctx.description().splitlines()[0]
+
+    fm.startitem()
+    fm.write('debugobshistory.node', '%s', str(ctx),
+             label="evolve.short_node")
+    fm.plain(' ')
+
+    fm.write('debugobshistory.rev', '(%d)', int(ctx),
+             label="evolve.rev")
+    fm.plain(' ')
+
+    fm.write('debugobshistory.shortdescription', '%s', shortdescription,
+             label="evolve.short_description")
+    fm.plain('\n')
+
+def _debugobshistorydisplaymissingctx(fm, nodewithoutctx):
+    hexnode = node.short(nodewithoutctx)
+    fm.startitem()
+    fm.write('debugobshistory.node', '%s', hexnode,
+             label="evolve.short_node evolve.missing_change_ctx")
+    fm.plain('\n')
+
+def _debugobshistorydisplaymarker(fm, repo, marker):
+    succnodes = marker[1]
+    date = marker[4]
+    metadata = dict(marker[3])
+
+    fm.startitem()
+    fm.plain('  ')
+
+    # Detect pruned revisions
+    if len(succnodes) == 0:
+        verb = 'pruned'
+    else:
+        verb = 'rewritten'
+
+    fm.write('debugobshistory.verb', '%s', verb,
+             label="evolve.verb")
+    fm.plain(' by ')
+
+    fm.write('debugobshistory.marker_user', '%s', metadata['user'],
+             label="evolve.user")
+    fm.plain(' ')
+
+    fm.write('debugobshistory.marker_date', '(%s)', fm.formatdate(date),
+             label="evolve.date")
+
+    if len(succnodes) > 0:
+        fm.plain(' as ')
+
+        shortsnodes = (node.short(succnode) for succnode in sorted(succnodes))
+        nodes = fm.formatlist(shortsnodes, 'debugobshistory.succnodes', sep=', ')
+        fm.write('debugobshistory.succnodes', '%s', nodes,
+                 label="evolve.short_node")
+
+    fm.plain("\n")
--- a/hgext3rd/evolve/obscache.py	Wed May 17 11:47:14 2017 +0200
+++ b/hgext3rd/evolve/obscache.py	Wed May 17 14:49:02 2017 +0200
@@ -13,6 +13,7 @@
 import errno
 
 from mercurial import (
+    error,
     localrepo,
     obsolete,
     phases,
@@ -20,6 +21,8 @@
     util,
 )
 
+from mercurial.i18n import _
+
 from . import (
     exthelper,
 )
@@ -76,91 +79,235 @@
             except (OSError, IOError) as e:
                 if e.errno != errno.ENOENT:
                     raise
-            key = hashlib.sha1(keydata).digest()
+            if keydata:
+                key = hashlib.sha1(keydata).digest()
+            else:
+                # reusing an existing "empty" value make it easier to define a
+                # default cachekey for 'no data'.
+                key = node.nullid
             return obsstoresize, key
 
     obsstore.__class__ = cachekeyobsstore
 
     return obsstore
 
-emptykey = (node.nullrev, node.nullid, 0, 0, node.nullid)
-
-def getcachekey(repo):
-    """get a cache key covering the changesets and obsmarkers content
-
-    IT contains the following data. Combined with 'upgradeneeded' it allows to
-    do iterative upgrade for cache depending of theses two data.
+# XXX copied as is from Mercurial 4.2 and added the "offset" parameters
+@util.nogc
+def _readmarkers(data, offset=None):
+    """Read and enumerate markers from raw data"""
+    off = 0
+    diskversion = struct.unpack('>B', data[off:off + 1])[0]
+    if offset is None:
+        off += 1
+    else:
+        assert 1 <= offset
+        off = offset
+    if diskversion not in obsolete.formats:
+        raise error.Abort(_('parsing obsolete marker: unknown version %r')
+                          % diskversion)
+    return diskversion, obsolete.formats[diskversion][0](data, off)
 
-    The cache key parts are"
-    - tip-rev,
-    - tip-node,
-    - obsstore-length (nb markers),
-    - obsstore-file-size (in bytes),
-    - obsstore "cache key"
-    """
-    assert repo.filtername is None
-    cl = repo.changelog
-    index, key = repo.obsstore.cachekey()
-    tiprev = len(cl) - 1
-    return (tiprev,
-            cl.node(tiprev),
-            len(repo.obsstore),
-            index,
-            key)
+def markersfrom(obsstore, byteoffset, firstmarker):
+    if not firstmarker:
+        return list(obsstore)
+    elif '_all' in vars(obsstore):
+        # if the data are in memory, just use that
+        return obsstore._all[firstmarker:]
+    else:
+        obsdata = obsstore.svfs.tryread('obsstore')
+        return _readmarkers(obsdata, byteoffset)[1]
 
-def upgradeneeded(repo, key):
-    """return (valid, start-rev, start-obs-idx)
 
-    'valid': is "False" if older cache value needs invalidation,
+class dualsourcecache(object):
+    """An abstract class for cache that needs both changelog and obsstore
 
-    'start-rev': first revision not in the cache. None if cache is up to date,
-
-    'start-obs-idx': index of the first obs-markers not in the cache. None is
-                     up to date.
+    This class handle the tracking of changelog and obsstore update. It provide
+    data to performs incremental update (see the 'updatefrom' function for
+    details).  This class can also detect stripping of the changelog or the
+    obsstore and can reset the cache in this cache (see the 'clear' function
+    for details).
     """
 
-    # XXX ideally, this function would return a bounded amount of changeset and
-    # obsmarkers and the associated new cache key. Otherwise we are exposed to
-    # a race condition between the time the cache is updated and the new cache
-    # key is computed. (however, we do not want to compute the full new cache
-    # key in all case because we want to skip reading the obsstore content. We
-    # could have a smarter implementation here.
+    # default key used for an empty cache
+    #
+    # The cache key covering the changesets and obsmarkers content
     #
-    # In pratice the cache is only updated after each transaction within a
-    # lock. So we should be fine. We could enforce this with a new repository
-    # requirement (or fix the race, that is not too hard).
-    invalid = (False, 0, 0)
-    if key is None:
-        return invalid
+    # The cache key parts are:
+    # - tip-rev,
+    # - tip-node,
+    # - obsstore-length (nb markers),
+    # - obsstore-file-size (in bytes),
+    # - obsstore "cache key"
+    emptykey = (node.nullrev, node.nullid, 0, 0, node.nullid)
+    _cachename = None # used for error message
+
+    def __init__(self):
+        super(dualsourcecache, self).__init__()
+        self._cachekey = None
+
+    def _updatefrom(self, repo, revs, obsmarkers):
+        """override this method to update your cache data incrementally
+
+        revs:      list of new revision in the changelog
+        obsmarker: list of new obsmarkers in the obsstore
+        """
+        raise NotImplementedError
+
+    def clear(self, reset=False):
+        """invalidate the cache content
+
+        if 'reset' is passed, we detected a strip and the cache will have to be
+        recomputed.
+        """
+        # /!\ IMPORTANT /!\
+        # You must overide this method to actually
+        if reset:
+            self._cachekey = self.emptykey if reset else None
+        else:
+            self._cachekey = None
+
+    def load(self, repo):
+        """Load data from disk
+
+        Do not forget to restore the "cachekey" attribute while doing so.
+        """
+        raise NotImplementedError
+
+    # Useful public function (no need to override them)
+
+    def uptodate(self, repo):
+        """return True if the cache content is up to date False otherwise
+
+        This method can be used to detect of the cache is lagging behind new
+        data in either changelog or obsstore.
+        """
+        if self._cachekey is None:
+            self.load(repo)
+        status = self._checkkey(repo.changelog, repo.obsstore)
+        return (status is not None
+                and status[0] == self._cachekey[0] # tiprev
+                and status[1] == self._cachekey[3]) # obssize
+
+    def update(self, repo):
+        """update the cache with new repository data
+
+        The update will be incremental when possible"""
+        repo = repo.unfiltered()
+        # If we do not have any data, try loading from disk
+        if self._cachekey is None:
+            self.load(repo)
+
+        assert repo.filtername is None
+        cl = repo.changelog
+
+        upgrade = self._upgradeneeded(repo)
+        if upgrade is None:
+            return
+
+        reset, revs, obsmarkers, obskeypair = upgrade
+        if reset or self._cachekey is None:
+            repo.ui.log('evoext-cache', 'strip detected, %s cache reset\n' % self._cachename)
+            self.clear(reset=True)
+
+        starttime = util.timer()
+        self._updatefrom(repo, revs, obsmarkers)
+        duration = util.timer() - starttime
+        repo.ui.log('evoext-cache', 'updated %s in %.4f seconds (%sr, %so)\n',
+                    self._cachename, duration, len(revs), len(obsmarkers))
 
-    ### Is the cache valid ?
-    keytiprev, keytipnode, keyobslength, keyobssize, keyobskey = key
-    # check for changelog strip
-    cl = repo.changelog
-    tiprev = len(cl) - 1
-    if (tiprev < keytiprev
-            or cl.node(keytiprev) != keytipnode):
-        return invalid
-    # check for obsstore strip
-    obssize, obskey = repo.obsstore.cachekey(index=keyobssize)
-    if obskey != keyobskey:
-        return invalid
+        # update the key from the new data
+        key = list(self._cachekey)
+        if revs:
+            key[0] = len(cl) - 1
+            key[1] = cl.node(key[0])
+        if obsmarkers:
+            key[2] += len(obsmarkers)
+            key[3], key[4] = obskeypair
+        self._cachekey = tuple(key)
+
+    # from here, there are internal function only
+
+    def _checkkey(self, changelog, obsstore):
+        """internal function"""
+        key = self._cachekey
+        if key is None:
+            return None
+
+        ### Is the cache valid ?
+        keytiprev, keytipnode, keyobslength, keyobssize, keyobskey = key
+        # check for changelog strip
+        tiprev = len(changelog) - 1
+        if (tiprev < keytiprev
+                or changelog.node(keytiprev) != keytipnode):
+            return None
+        # check for obsstore strip
+        obssize, obskey = obsstore.cachekey(index=keyobssize)
+        if obskey != keyobskey:
+            return None
+        if obssize != keyobssize:
+            # we want to return the obskey for the new size
+            __, obskey = obsstore.cachekey(index=obssize)
+        return tiprev, obssize, obskey
+
+    def _upgradeneeded(self, repo):
+        """return (valid, start-rev, start-obs-idx)
+
+        'valid': is "False" if older cache value needs invalidation,
+
+        'start-rev': first revision not in the cache. None if cache is up to date,
+
+        'start-obs-idx': index of the first obs-markers not in the cache. None is
+                         up to date.
+        """
 
-    ### cache is valid, is there anything to update
+        # We need to ensure we use the same changelog and obsstore through the
+        # processing. Otherwise some invalidation could update the object and their
+        # content after we computed the cache key.
+        cl = repo.changelog
+        obsstore = repo.obsstore
+        key = self._cachekey
+
+        reset = False
 
-    # any new changesets ?
-    startrev = None
-    if keytiprev < tiprev:
-        startrev = keytiprev + 1
+        status = self._checkkey(cl, obsstore)
+        if status is None:
+            reset = True
+            key = self.emptykey
+            obssize, obskey = obsstore.cachekey()
+            tiprev = len(cl) - 1
+        else:
+            tiprev, obssize, obskey = status
+
+        keytiprev, keytipnode, keyobslength, keyobssize, keyobskey = key
+
+        if not reset and keytiprev == tiprev and keyobssize == obssize:
+            return None # nothing to upgrade
 
-    # any new markers
-    startidx = None
-    if keyobssize < obssize:
-        startidx = keyobslength
+        ### cache is valid, is there anything to update
+
+        # any new changesets ?
+        revs = ()
+        if keytiprev < tiprev:
+            revs = list(cl.revs(start=keytiprev + 1, stop=tiprev))
 
-    return True, startrev, startidx
+        # any new markers
+        markers = ()
+        if keyobssize < obssize:
+            # XXX Three are a small race change here. Since the obsstore might have
+            # move forward between the time we computed the cache key and we access
+            # the data. To fix this we need so "up to" argument when fetching the
+            # markers here. Otherwise we might return more markers than covered by
+            # the cache key.
+            #
+            # In pratice the cache is only updated after each transaction within a
+            # lock. So we should be fine. We could enforce this with a new repository
+            # requirement (or fix the race, that is not too hard).
+            markers = markersfrom(obsstore, keyobssize, keyobslength)
 
-class obscache(object):
+        return reset, revs, markers, (obssize, obskey)
+
+
+class obscache(dualsourcecache):
     """cache the "does a rev" is the precursors of some obsmarkers data
 
     This is not directly holding the "is this revision obsolete" information,
@@ -197,16 +344,12 @@
     _filepath = 'cache/evoext-obscache-00'
     _headerformat = '>q20sQQ20s'
 
+    _cachename = 'evo-ext-obscache' # used for error message
+
     def __init__(self, repo):
+        super(obscache, self).__init__()
+        self._ondiskkey = None
         self._vfs = repo.vfs
-        # The cache key parts are"
-        # - tip-rev,
-        # - tip-node,
-        # - obsstore-length (nb markers),
-        # - obsstore-file-size (in bytes),
-        # - obsstore "cache key"
-        self._cachekey = None
-        self._ondiskkey = None
         self._data = bytearray()
 
     def get(self, rev):
@@ -215,90 +358,56 @@
         Make sure the cache has been updated to match the repository content before using it"""
         return self._data[rev]
 
-    def clear(self):
+    def clear(self, reset=False):
         """invalidate the cache content"""
-        self._cachekey = None
+        super(obscache, self).clear(reset=reset)
         self._data = bytearray()
 
-    def uptodate(self, repo):
-        if self._cachekey is None:
-            self.load(repo)
-        valid, startrev, startidx = upgradeneeded(repo, self._cachekey)
-        return (valid and startrev is None and startidx is None)
+    def _updatefrom(self, repo, revs, obsmarkers):
+        if revs:
+            self._updaterevs(repo, revs)
+        if obsmarkers:
+            self._updatemarkers(repo, obsmarkers)
+
+    def _updaterevs(self, repo, revs):
+        """update the cache with new revisions
+
+        Newly added changeset might be affected by obsolescence markers
+        we already have locally. So we needs to have some global
+        knowledge about the markers to handle that question.
+
+        Right now this requires parsing all markers in the obsstore. We could
+        imagine using various optimisation (eg: another cache, network
+        exchange, etc).
 
-    def update(self, repo):
-        """Iteratively update the cache with new repository data"""
-        # If we do not have any data, try loading from disk
-        if self._cachekey is None:
-            self.load(repo)
+        A possible approach to this is to build a set of all node used as
+        precursors in `obsstore._obscandidate`. If markers are not loaded yet,
+        we could initialize it by doing a quick scan through the obsstore data
+        and filling a (pre-sized) set. Doing so would be much faster than
+        parsing all the obsmarkers since we would access less data, not create
+        any object beside the nodes and not have to decode any complex data.
 
-        valid, startrev, startidx = upgradeneeded(repo, self._cachekey)
-        if not valid:
-            self.clear()
-
-        if startrev is None and startidx is None:
-            return
-
-        # process the new changesets
+        For now we stick to the simpler approach of paying the
+        performance cost on new changesets.
+        """
+        node = repo.changelog.node
+        succs = repo.obsstore.successors
+        for r in revs:
+            if node(r) in succs:
+                val = 1
+            else:
+                val = 0
+            self._data.append(val)
         cl = repo.changelog
-        if startrev is not None:
-            node = cl.node
-            # Note:
-            #
-            #  Newly added changeset might be affected by obsolescence markers
-            #  we already have locally. So we needs to have soem global
-            #  knowledge about the markers to handle that question. Right this
-            #  requires parsing all markers in the obsstore. However, we could
-            #  imagine using various optimisation (eg: bloom filter, other on
-            #  disk cache) to remove this full parsing.
-            #
-            #  For now we stick to the simpler approach or paying the
-            #  performance cost on new changesets.
-            succs = repo.obsstore.successors
-            for r in cl.revs(startrev):
-                if node(r) in succs:
-                    val = 1
-                else:
-                    val = 0
-                self._data.append(val)
         assert len(self._data) == len(cl), (len(self._data), len(cl))
 
-        # process the new obsmarkers
-        if startidx is not None:
-            rev = cl.nodemap.get
-            markers = repo.obsstore._all
-            # Note:
-            #
-            #   There are no actually needs to load the full obsstore here,
-            #   since we only read the latest ones.  We do it for simplicity in
-            #   the first implementation. Loading the full obsstore has a
-            #   performance cost and should go away in this case too. We have
-            #   two simples options for that:
-            #
-            #   1) provide and API to start reading markers from a byte offset
-            #      (we have that data in the cache key)
-            #
-            #   2) directly update the cache at a lower level, in the code
-            #      responsible for adding a markers.
-            #
-            #   Option 2 is probably a bit more invasive, but more solid on the long run
-
-            for i in xrange(startidx, len(repo.obsstore)):
-                r = rev(markers[i][0])
-                # If markers affect a newly added nodes, it would have been
-                # caught in the previous loop, (so we skip < startrev)
-                if r is not None and (startrev is None or r < startrev):
-                    self._data[r] = 1
-
-        assert repo._currentlock(repo._lockref) is not None
-        # XXX note that there are a potential race condition here, since the
-        # repo "might" have changed side the cache update above. However, this
-        # code will only be running in a lock so we ignore the issue for now.
-        #
-        # To work around this, 'upgradeneeded' should return a bounded amount
-        # of changeset and markers to read with their associated cachekey. see
-        # 'upgradeneeded' for detail.
-        self._cachekey = getcachekey(repo)
+    def _updatemarkers(self, repo, obsmarkers):
+        """update the cache with new markers"""
+        rev = repo.changelog.nodemap.get
+        for m in obsmarkers:
+            r = rev(m[0])
+            if r is not None:
+                self._data[r] = 1
 
     def save(self, repo):
         """save the data to disk"""
@@ -319,7 +428,7 @@
 
         data = repo.vfs.tryread(self._filepath)
         if not data:
-            self._cachekey = emptykey
+            self._cachekey = self.emptykey
             self._data = bytearray()
         else:
             headersize = struct.calcsize(self._headerformat)
@@ -339,7 +448,7 @@
     if notpublic:
         obscache = repo.obsstore.obscache
         # Since we warm the cache at the end of every transaction, the cache
-        # should be up to date. However a non-enabled client might have touced
+        # should be up to date. However a non-enabled client might have touched
         # the repository.
         #
         # Updating the cache without a lock is sloppy, so we fallback to the
@@ -348,17 +457,18 @@
         #
         # With the current implementation updating the cache will requires to
         # load the obsstore anyway. Once loaded, hitting the obsstore directly
-        # will be about as fast..
+        # will be about as fast...
         if not obscache.uptodate(repo):
             if repo.currenttransaction() is None:
-                repo.ui.log('evoext-obscache',
+                repo.ui.log('evoext-cache',
                             'obscache is out of date, '
                             'falling back to slower obsstore version\n')
                 repo.ui.debug('obscache is out of date')
                 return orig(repo)
             else:
-                # If a transaction is open, it is worthwhile to update and use the
-                # cache as it will be written on disk when the transaction close.
+                # If a transaction is open, it is worthwhile to update and use
+                # the cache, the lock prevent race and it will be written on
+                # disk when the transaction close.
                 obscache.update(repo)
         isobs = obscache.get
     for r in notpublic:
@@ -392,11 +502,10 @@
                 if repo is None:
                     return
                 repo = repo.unfiltered()
-                # As pointed in 'obscache.update', we could have the
-                # changelog and the obsstore in charge of updating the
-                # cache when new items goes it. The tranaction logic would
-                # then only be involved for the 'pending' and final saving
-                # logic.
+                # As pointed in 'obscache.update', we could have the changelog
+                # and the obsstore in charge of updating the cache when new
+                # items goes it. The tranaction logic would then only be
+                # involved for the 'pending' and final writing on disk.
                 self.obsstore.obscache.update(repo)
                 self.obsstore.obscache.save(repo)
 
--- a/hgext3rd/evolve/obsdiscovery.py	Wed May 17 11:47:14 2017 +0200
+++ b/hgext3rd/evolve/obsdiscovery.py	Wed May 17 14:49:02 2017 +0200
@@ -46,6 +46,7 @@
 
 from . import (
     exthelper,
+    obscache,
     utility,
     stablerange,
 )
@@ -241,6 +242,7 @@
         entry = (h, 0)
         addentry(entry)
 
+    local.obsstore.rangeobshashcache.update(local)
     querycount = 0
     ui.progress(_("comparing obsmarker with other"), querycount)
     overflow = []
@@ -342,6 +344,7 @@
     linetemplate = '%12d %12s %12d %12d %12d %12s\n'
     headertemplate = linetemplate.replace('d', 's')
     ui.status(headertemplate % headers)
+    repo.obsstore.rangeobshashcache.update(repo)
     for r in ranges:
         d = (r[0],
              s(cl.node(r[0])),
@@ -392,10 +395,11 @@
 
 _sqliteschema = [
     """CREATE TABLE meta(schemaversion INTEGER NOT NULL,
+                         tiprev        INTEGER NOT NULL,
+                         tipnode       BLOB    NOT NULL,
                          nbobsmarker   INTEGER NOT NULL,
-                         obstipdata    BLOB    NOT NULL,
-                         tiprev        INTEGER NOT NULL,
-                         tipnode       BLOB    NOT NULL
+                         obssize       BLOB    NOT NULL,
+                         obskey        BLOB    NOT NULL
                         );""",
     """CREATE TABLE obshashrange(rev     INTEGER NOT NULL,
                                  idx     INTEGER NOT NULL,
@@ -404,53 +408,121 @@
     "CREATE INDEX range_index ON obshashrange(rev, idx);",
 ]
 _queryexist = "SELECT name FROM sqlite_master WHERE type='table' AND name='meta';"
-_newmeta = """INSERT INTO meta (schemaversion, nbobsmarker, obstipdata, tiprev, tipnode)
-            VALUES (?,?,?,?,?);"""
+_clearmeta = """DELETE FROM meta;"""
+_newmeta = """INSERT INTO meta (schemaversion, tiprev, tipnode, nbobsmarker, obssize, obskey)
+            VALUES (?,?,?,?,?,?);"""
 _updateobshash = "INSERT INTO obshashrange(rev, idx, obshash) VALUES (?,?,?);"
-_querymeta = "SELECT schemaversion, nbobsmarker, obstipdata, tiprev, tipnode FROM meta;"
+_querymeta = "SELECT schemaversion, tiprev, tipnode, nbobsmarker, obssize, obskey FROM meta;"
 _queryobshash = "SELECT obshash FROM obshashrange WHERE (rev = ? AND idx = ?);"
 
-class _obshashcache(dict):
+_reset = "DELETE FROM obshashrange;"
+
+class _obshashcache(obscache.dualsourcecache):
 
-    _schemaversion = 0
+    _schemaversion = 1
+
+    _cachename = 'evo-ext-obshashrange' # used for error message
 
     def __init__(self, repo):
         super(_obshashcache, self).__init__()
-        self._path = repo.vfs.join('cache/evoext_obshashrange_v0.sqlite')
+        self._path = repo.vfs.join('cache/evoext_obshashrange_v1.sqlite')
         self._new = set()
         self._valid = True
         self._repo = weakref.ref(repo.unfiltered())
         # cache status
         self._ondiskcachekey = None
+        self._data = {}
 
-    def clear(self):
-        self._valid = False
-        super(_obshashcache, self).clear()
+    def clear(self, reset=False):
+        super(_obshashcache, self).clear(reset=reset)
+        self._data.clear()
         self._new.clear()
+        if reset:
+            self._valid = False
+        if '_con' in vars(self):
+            del self._con
 
     def get(self, rangeid):
-        value = super(_obshashcache, self).get(rangeid)
+        # revision should be covered by the tiprev
+        #
+        # XXX there are issue with cache warming, we hack around it for now
+        if not getattr(self, '_updating', False):
+            if self._cachekey[0] < rangeid[0]:
+                msg = ('using unwarmed obshashrangecache (%s %s)'
+                       % (rangeid[0], self._cachekey[0]))
+                raise error.ProgrammingError(msg)
+
+        value = self._data.get(rangeid)
         if value is None and self._con is not None:
             nrange = (rangeid[0], rangeid[1])
             obshash = self._con.execute(_queryobshash, nrange).fetchone()
             if obshash is not None:
                 value = obshash[0]
+            self._data[rangeid] = value
         return value
 
     def __setitem__(self, rangeid, obshash):
         self._new.add(rangeid)
-        super(_obshashcache, self).__setitem__(rangeid, obshash)
+        self._data[rangeid] = obshash
+
+    def _updatefrom(self, repo, revs, obsmarkers):
+        """override this method to update your cache data incrementally
+
+        revs:      list of new revision in the changelog
+        obsmarker: list of new obsmarkers in the obsstore
+        """
+        # XXX for now, we'll not actually update the cache, but we'll be
+        # smarter at invalidating it.
+        #
+        # 1) new revisions does not get their entry updated (not update)
+        # 2) if we detect markers affecting non-new revision we reset the cache
+
+        self._updating = True
 
-    def _cachekey(self, repo):
-        # XXX for now the cache is very volatile, but this is still a win
-        nbobsmarker = len(repo.obsstore._all)
-        if nbobsmarker:
-            tipdata = obsolete._fm1encodeonemarker(repo.obsstore._all[-1])
-        else:
-            tipdata = node.nullid
-        tiprev = len(repo.changelog) - 1
-        tipnode = repo.changelog.node(tiprev)
-        return (self._schemaversion, nbobsmarker, tipdata, tiprev, tipnode)
+        setrevs = set(revs)
+        rev = repo.changelog.nodemap.get
+        # if we have a new markers affecting a node already covered by the
+        # cache, we must abort.
+        for m in obsmarkers:
+            # check successors and parent
+            for l in (m[1], m[5]):
+                if l is None:
+                    continue
+                for p in l:
+                    r = rev(p)
+                    if r is not None and r not in setrevs:
+                        repo.ui.log('evoext-cache', 'obshashcache reset - '
+                                    'new markers affect cached ranges\n')
+                        con = self._con
+                        if con is not None:
+                            con.execute(_reset)
+                        break
+                else:
+                    continue
+                break
+            else:
+                continue
+            break
+
+        # XXX the current reset is too strong we could just drop the affected range
+
+        # XXX if we reset, we should warm the cache for existing heads (draft and public)
+
+        # warm the cache for the new revs
+        for r in revs:
+            _obshashrange(repo, (r, 0))
+
+        del self._updating
+
+    @property
+    def _fullcachekey(self):
+        return (self._schemaversion, ) + self._cachekey
+
+    def load(self, repo):
+        if self._con is None:
+            self._cachekey = self.emptykey
+            self._ondiskcachekey = self.emptykey
+        assert self._cachekey is not None
 
     @util.propertycache
     def _con(self):
@@ -459,7 +531,6 @@
         repo = self._repo()
         if repo is None:
             return None
-        cachekey = self._cachekey(repo)
         con = sqlite3.connect(self._path)
         con.text_factory = str
         cur = con.execute(_queryexist)
@@ -467,17 +538,19 @@
             self._valid = False
             return None
         meta = con.execute(_querymeta).fetchone()
-        if meta != cachekey:
+        if meta is None or meta[0] != self._schemaversion:
             self._valid = False
             return None
-        self._ondiskcachekey = meta
+        self._cachekey = self._ondiskcachekey = meta[1:]
         return con
 
     def save(self, repo):
+        if self._cachekey is None:
+            return
+        if self._cachekey == self._ondiskcachekey and not self._new:
+            return
         repo = repo.unfiltered()
         try:
-            if not self._new:
-                return
             with repo.lock():
                 self._save(repo)
         except error.LockError:
@@ -499,26 +572,29 @@
                 for req in _sqliteschema:
                     con.execute(req)
 
-                con.execute(_newmeta, self._cachekey(repo))
+                con.execute(_newmeta, self._fullcachekey)
         else:
             con = self._con
             if self._ondiskcachekey is not None:
                 meta = con.execute(_querymeta).fetchone()
-                if meta != self._ondiskcachekey:
+                if meta[1:] != self._ondiskcachekey:
                     # drifting is currently an issue because this means another
                     # process might have already added the cache line we are about
                     # to add. This will confuse sqlite
                     msg = _('obshashrange cache: skipping write, '
                             'database drifted under my feet\n')
-                    data = (meta[2], meta[1], self._ondisktiprev, self._ondisktipnode)
+                    data = (meta[2], meta[1], self._ondiskcachekey[0], self._ondiskcachekey[1])
                     repo.ui.warn(msg)
-        data = ((rangeid[0], rangeid[1], self[rangeid]) for rangeid in self._new)
+                    return
+        data = ((rangeid[0], rangeid[1], self.get(rangeid)) for rangeid in self._new)
         con.executemany(_updateobshash, data)
-        cachekey = self._cachekey(repo)
+        cachekey = self._fullcachekey
+        con.execute(_clearmeta) # remove the older entry
         con.execute(_newmeta, cachekey)
         con.commit()
         self._new.clear()
-        self._ondiskcachekey = cachekey
+        self._ondiskcachekey = self._cachekey
+        self._valid = True
 
 @eh.wrapfunction(obsolete.obsstore, '_addmarkers')
 def _addmarkers(orig, obsstore, *args, **kwargs):
@@ -549,10 +625,31 @@
     class obshashrepo(repo.__class__):
         @localrepo.unfilteredmethod
         def destroyed(self):
-            if 'stablerange' in vars(self):
-                del self.stablerange
+            if 'obsstore' in vars(self):
+                self.obsstore.rangeobshashcache.clear()
             super(obshashrepo, self).destroyed()
 
+        def transaction(self, *args, **kwargs):
+            tr = super(obshashrepo, self).transaction(*args, **kwargs)
+            reporef = weakref.ref(self)
+
+            def _warmcache(tr):
+                repo = reporef()
+                if repo is None:
+                    return
+                if not repo.ui.configbool('experimental', 'obshashrange', False):
+                    return
+                repo = repo.unfiltered()
+                # As pointed in 'obscache.update', we could have the changelog
+                # and the obsstore in charge of updating the cache when new
+                # items goes it. The tranaction logic would then only be
+                # involved for the 'pending' and final writing on disk.
+                self.obsstore.rangeobshashcache.update(repo)
+                self.obsstore.rangeobshashcache.save(repo)
+
+            tr.addpostclose('warmcache-20-obscacherange', _warmcache)
+            return tr
+
     repo.__class__ = obshashrepo
 
 ### wire protocol commands
@@ -570,6 +667,7 @@
         if maxrev is not None:
             repo.stablerange.warmup(repo, upto=maxrev)
     result = []
+    repo.obsstore.rangeobshashcache.update(repo)
     for r in ranges:
         if r[0] is None:
             result.append(node.wdirid)
--- a/hgext3rd/evolve/stablerange.py	Wed May 17 11:47:14 2017 +0200
+++ b/hgext3rd/evolve/stablerange.py	Wed May 17 14:49:02 2017 +0200
@@ -263,6 +263,7 @@
         #
         # we use the revnumber as an approximation for depth
         ui = repo.ui
+        starttime = util.timer()
 
         if upto is None:
             upto = len(cl) - 1
@@ -308,6 +309,10 @@
         self._tiprev = upto
         self._tipnode = cl.node(upto)
 
+        duration = util.timer() - starttime
+        repo.ui.log('evoext-cache', 'updated stablerange cache in %.4f seconds\n',
+                    duration)
+
     def depthrev(self, repo, rev):
         repo = repo.unfiltered()
         cl = repo.changelog
@@ -902,7 +907,7 @@
                     # new nodes !
                     repo.stablerange.warmup(repo)
 
-            tr.addpostclose('warmcache-stablerange', _warmcache)
+            tr.addpostclose('warmcache-10-stablerange', _warmcache)
             return tr
 
     repo.__class__ = stablerangerepo
--- a/hgext3rd/topic/__init__.py	Wed May 17 11:47:14 2017 +0200
+++ b/hgext3rd/topic/__init__.py	Wed May 17 14:49:02 2017 +0200
@@ -100,6 +100,10 @@
               'topic.stack.summary.behindcount': 'cyan',
               'topic.stack.summary.behinderror': 'red',
               'topic.stack.summary.headcount.multiple': 'yellow',
+              # default color to help log output and thg
+              # (first pick I could think off, update as needed
+              'log.topic': 'green_background',
+              'topic.active': 'green',
              }
 
 testedwith = '3.9'
@@ -157,6 +161,10 @@
     if not isinstance(repo, localrepo.localrepository):
         return # this can be a peer in the ssh case (puzzling)
 
+    if repo.ui.config('experimental', 'thg.displaynames', None) is None:
+        repo.ui.setconfig('experimental', 'thg.displaynames', 'topics',
+                          source='topic-extension')
+
     class topicrepo(repo.__class__):
 
         def _restrictcapabilities(self, caps):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-check-commit.t	Wed May 17 14:49:02 2017 +0200
@@ -0,0 +1,22 @@
+#require test-repo
+
+Enable obsolescence to avoid the warning issue when obsmarker are found
+
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > evolution=all
+  > EOF
+
+Go back in the hg repo
+
+  $ cd $TESTDIR/..
+
+  $ for node in `hg log --rev 'not public() and ::. and not desc("# no-check-commit")' --template '{node|short}\n'`; do
+  >    hg export $node | ${RUNTESTDIR}/../contrib/check-commit > ${TESTTMP}/check-commit.out
+  >    if [ $? -ne 0 ]; then
+  >        echo "Revision $node does not comply with rules"
+  >        echo '------------------------------------------------------'
+  >        cat ${TESTTMP}/check-commit.out
+  >        echo
+  >   fi
+  > done
--- a/tests/test-discovery-obshashrange.t	Wed May 17 11:47:14 2017 +0200
+++ b/tests/test-discovery-obshashrange.t	Wed May 17 14:49:02 2017 +0200
@@ -6,6 +6,9 @@
   $ cat << EOF >> $HGRCPATH
   > [extensions]
   > hgext3rd.evolve =
+  > blackbox =
+  > [defaults]
+  > blackbox = -l 100
   > [experimental]
   > obshashrange=1
   > verbose-obsolescence-exchange=1
@@ -27,6 +30,31 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd server
   $ hg debugbuilddag '.+7'
+  $ hg blackbox
+  * @0000000000000000000000000000000000000000 (*)> serve --stdio (glob)
+  * @0000000000000000000000000000000000000000 (*)> -R server serve --stdio exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugbuilddag .+7 (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated served branch cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> wrote served branch cache with 1 labels and 1 nodes (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated served branch cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> wrote served branch cache with 1 labels and 1 nodes (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated served branch cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> wrote served branch cache with 1 labels and 1 nodes (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated served branch cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> wrote served branch cache with 1 labels and 1 nodes (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated served branch cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> wrote served branch cache with 1 labels and 1 nodes (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated served branch cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> wrote served branch cache with 1 labels and 1 nodes (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated served branch cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> wrote served branch cache with 1 labels and 1 nodes (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated served branch cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> wrote served branch cache with 1 labels and 1 nodes (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (8r, 0o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (8r, 0o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugbuilddag .+7 exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> blackbox (glob)
+  $ rm .hg/blackbox.log
   $ hg log -G
   o  7 4de32a90b66c r7 tip
   |
@@ -57,6 +85,56 @@
   dddddddddddddddddddddddddddddddddddddddd c8d03c1b5e94af74b772900c58259d2e08917735 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee 4de32a90b66cd083ebf3c00b41277aa7abca51dd 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
 
+  $ hg blackbox
+  * @0000000000000000000000000000000000000000 (*)> log -G (glob)
+  * @0000000000000000000000000000000000000000 (*)> writing .hg/cache/tags2-visible with 0 tags (glob)
+  * @0000000000000000000000000000000000000000 (*)> log -G exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> log --hidden --template '{node}\n' --rev 'desc(r1)' (glob)
+  * @0000000000000000000000000000000000000000 (*)> writing .hg/cache/tags2 with 0 tags (glob)
+  * @0000000000000000000000000000000000000000 (*)> log --hidden --template '{node}\n' --rev 'desc(r1)' exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 66f7d451a68b85ed82ff5fcc254daf50c74144bd (glob)
+  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @0000000000000000000000000000000000000000 (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 66f7d451a68b85ed82ff5fcc254daf50c74144bd exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> log --hidden --template '{node}\n' --rev 'desc(r2)' (glob)
+  * @0000000000000000000000000000000000000000 (*)> log --hidden --template '{node}\n' --rev 'desc(r2)' exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 01241442b3c2bf3211e593b549c655ea65b295e3 (glob)
+  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @0000000000000000000000000000000000000000 (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 01241442b3c2bf3211e593b549c655ea65b295e3 exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> log --hidden --template '{node}\n' --rev 'desc(r4)' (glob)
+  * @0000000000000000000000000000000000000000 (*)> log --hidden --template '{node}\n' --rev 'desc(r4)' exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete cccccccccccccccccccccccccccccccccccccccc bebd167eb94d257ace0e814aeb98e6972ed2970d (glob)
+  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @0000000000000000000000000000000000000000 (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete cccccccccccccccccccccccccccccccccccccccc bebd167eb94d257ace0e814aeb98e6972ed2970d exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> log --hidden --template '{node}\n' --rev 'desc(r5)' (glob)
+  * @0000000000000000000000000000000000000000 (*)> log --hidden --template '{node}\n' --rev 'desc(r5)' exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete dddddddddddddddddddddddddddddddddddddddd c8d03c1b5e94af74b772900c58259d2e08917735 (glob)
+  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @0000000000000000000000000000000000000000 (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete dddddddddddddddddddddddddddddddddddddddd c8d03c1b5e94af74b772900c58259d2e08917735 exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> log --hidden --template '{node}\n' --rev 'desc(r7)' (glob)
+  * @0000000000000000000000000000000000000000 (*)> log --hidden --template '{node}\n' --rev 'desc(r7)' exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee 4de32a90b66cd083ebf3c00b41277aa7abca51dd (glob)
+  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @0000000000000000000000000000000000000000 (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee 4de32a90b66cd083ebf3c00b41277aa7abca51dd exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete (glob)
+  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> blackbox (glob)
+  $ rm .hg/blackbox.log
   $ hg debugobshashrange --subranges --rev tip
            rev         node        index         size        depth      obshash
              7 4de32a90b66c            0            8            8 38d1e7ad86ea
@@ -88,10 +166,30 @@
   added 5 changesets with 0 changes to 0 files
   3 new obsolescence markers
   (run 'hg update' to get a working copy)
+  $ hg -R ../server blackbox
+  * @0000000000000000000000000000000000000000 (*)> debugobshashrange --subranges --rev tip (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated stablerange cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobshashrange --subranges --rev tip exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> serve --stdio (glob)
+  * @0000000000000000000000000000000000000000 (*)> -R server serve --stdio exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> blackbox (glob)
+  $ rm ../server/.hg/blackbox.log
   $ hg -R ../server/ debugobsolete --rev ::4 | sort
   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 66f7d451a68b85ed82ff5fcc254daf50c74144bd 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 01241442b3c2bf3211e593b549c655ea65b295e3 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   cccccccccccccccccccccccccccccccccccccccc bebd167eb94d257ace0e814aeb98e6972ed2970d 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  $ rm ../server/.hg/blackbox.log
+  $ hg blackbox
+  * @0000000000000000000000000000000000000000 (*)> pull --rev 4 (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated served branch cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> wrote served branch cache with 1 labels and 1 nodes (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated stablerange cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (5r, 3o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (5r, 3o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> 5 incoming changes - new heads: bebd167eb94d (glob)
+  * @0000000000000000000000000000000000000000 (*)> pull --rev 4 exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> blackbox (glob)
+  $ rm .hg/blackbox.log
   $ hg debugobsolete | sort
   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 66f7d451a68b85ed82ff5fcc254daf50c74144bd 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 01241442b3c2bf3211e593b549c655ea65b295e3 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
@@ -117,6 +215,18 @@
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files (+1 heads)
   remote: 1 new obsolescence markers
+  $ hg -R ../server blackbox
+  * @0000000000000000000000000000000000000000 (*)> serve --stdio (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (1r, 0o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated served branch cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> wrote served branch cache with 1 labels and 2 nodes (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated stablerange cache in *.???? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (1r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> 1 incoming changes - new heads: 45f8b879de92 (glob)
+  * @0000000000000000000000000000000000000000 (*)> -R server serve --stdio exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> blackbox (glob)
+  $ rm ../server/.hg/blackbox.log
 
 testing push with extra local markers
 =====================================
@@ -145,6 +255,14 @@
   no changes found
   remote: 2 new obsolescence markers
   [1]
+  $ hg -R ../server blackbox
+  * @0000000000000000000000000000000000000000 (*)> serve --stdio (glob)
+  * @0000000000000000000000000000000000000000 (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 2o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 2o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> -R server serve --stdio exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> blackbox (glob)
+  $ rm ../server/.hg/blackbox.log
   $ hg -R ../server/ debugobsolete --rev ::tip | sort
   111111111111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   22222222222222222bbbbbbbbbbbbb2222222222 2dc09a01254db841290af0538aa52f6f52c776e3 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
@@ -152,6 +270,55 @@
   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 01241442b3c2bf3211e593b549c655ea65b295e3 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   cccccccccccccccccccccccccccccccccccccccc bebd167eb94d257ace0e814aeb98e6972ed2970d 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   ffffffffffffffffffffffffffffffffffffffff 45f8b879de922f6a6e620ba04205730335b6fc7e 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  $ hg blackbox
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete (glob)
+  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> up (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> up exited 0 after *.?? seconds (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> add foo (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> add foo exited 0 after *.?? seconds (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> commit -m foo (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-obscache in *.???? seconds (1r, 0o) (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated served branch cache in *.???? seconds (glob)
+  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> wrote served branch cache with 1 labels and 1 nodes (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (1r, 0o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> commit -m foo exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev . (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev . exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete ffffffffffffffffffffffffffffffffffffffff 45f8b879de922f6a6e620ba04205730335b6fc7e (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete ffffffffffffffffffffffffffffffffffffffff 45f8b879de922f6a6e620ba04205730335b6fc7e exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> push -f (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated stablerange cache in *.???? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> push -f exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log -G (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> writing .hg/cache/tags2-visible with 0 tags (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log -G exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r1)' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> writing .hg/cache/tags2 with 0 tags (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r1)' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete 111111111111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete 111111111111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r3)' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r3)' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete 22222222222222222bbbbbbbbbbbbb2222222222 2dc09a01254db841290af0538aa52f6f52c776e3 (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete 22222222222222222bbbbbbbbbbbbb2222222222 2dc09a01254db841290af0538aa52f6f52c776e3 exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> push (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> push exited 1 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> blackbox (glob)
+  $ rm .hg/blackbox.log
   $ hg debugobsolete | sort
   111111111111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   22222222222222222bbbbbbbbbbbbb2222222222 2dc09a01254db841290af0538aa52f6f52c776e3 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
@@ -190,6 +357,27 @@
   3 new obsolescence markers
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
+  $ hg -R ../server blackbox
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete --rev '::tip' (glob)
+  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @0000000000000000000000000000000000000000 (*)> writing .hg/cache/tags2-visible with 0 tags (glob)
+  * @0000000000000000000000000000000000000000 (*)> -R ../server/ debugobsolete --rev '::tip' exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete aaaaaaa11111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd (glob)
+  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @0000000000000000000000000000000000000000 (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> -R ../server debugobsolete aaaaaaa11111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> debugobsolete bbbbbbb2222222222bbbbbbbbbbbbb2222222222 bebd167eb94d257ace0e814aeb98e6972ed2970d (glob)
+  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @0000000000000000000000000000000000000000 (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @0000000000000000000000000000000000000000 (*)> -R ../server debugobsolete bbbbbbb2222222222bbbbbbbbbbbbb2222222222 bebd167eb94d257ace0e814aeb98e6972ed2970d exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> serve --stdio (glob)
+  * @0000000000000000000000000000000000000000 (*)> -R server serve --stdio exited 0 after *.?? seconds (glob)
+  * @0000000000000000000000000000000000000000 (*)> blackbox (glob)
+  $ rm ../server/.hg/blackbox.log
   $ hg -R ../server/ debugobsolete --rev '::6' | sort
   111111111111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   22222222222222222bbbbbbbbbbbbb2222222222 2dc09a01254db841290af0538aa52f6f52c776e3 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
@@ -199,6 +387,28 @@
   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 01241442b3c2bf3211e593b549c655ea65b295e3 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   cccccccccccccccccccccccccccccccccccccccc bebd167eb94d257ace0e814aeb98e6972ed2970d 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   dddddddddddddddddddddddddddddddddddddddd c8d03c1b5e94af74b772900c58259d2e08917735 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  $ hg blackbox
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log -G (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log -G exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r1)' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r1)' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r4)' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r4)' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> pull -r 6 (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (2r, 0o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated served branch cache in *.???? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> wrote served branch cache with 1 labels and 2 nodes (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated stablerange cache in *.???? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (2r, 3o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 3o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> 2 incoming changes - new heads: f69452c5b1af (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> pull -r 6 exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> blackbox (glob)
+  $ rm .hg/blackbox.log
   $ hg debugobsolete --rev '::6' | sort
   111111111111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   22222222222222222bbbbbbbbbbbbb2222222222 2dc09a01254db841290af0538aa52f6f52c776e3 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
@@ -209,3 +419,214 @@
   cccccccccccccccccccccccccccccccccccccccc bebd167eb94d257ace0e814aeb98e6972ed2970d 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   dddddddddddddddddddddddddddddddddddddddd c8d03c1b5e94af74b772900c58259d2e08917735 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
 
+Test cache behavior
+===================
+
+Adding markers affecting already used range:
+--------------------------------------------
+
+  $ hg debugobshashrange --subranges --rev 'heads(all())'
+           rev         node        index         size        depth      obshash
+             7 f69452c5b1af            0            7            7 000000000000
+             5 45f8b879de92            0            6            6 1643971dbe2d
+             3 2dc09a01254d            0            4            4 6be48f31976a
+             7 f69452c5b1af            4            3            7 000000000000
+             3 2dc09a01254d            2            2            4 9522069ae085
+             5 45f8b879de92            4            2            6 9c26c72819c0
+             1 66f7d451a68b            0            2            2 853c77a32154
+             6 c8d03c1b5e94            4            2            6 ec8a3e92c525
+             2 01241442b3c2            2            1            3 1ed3c61fb39a
+             0 1ea73414a91b            0            1            1 000000000000
+             3 2dc09a01254d            3            1            4 8a2acf8e1cde
+             5 45f8b879de92            5            1            6 1a0c08180b65
+             1 66f7d451a68b            1            1            2 853c77a32154
+             4 bebd167eb94d            4            1            5 20a2cc572e4b
+             6 c8d03c1b5e94            5            1            6 446c2dc3bce5
+             7 f69452c5b1af            6            1            7 000000000000
+  $ hg -R ../server debugobsolete aaaa333333333aaaaa333a3a3a3a3a3a3a3a3a3a `getid 'desc(r1)'`
+  $ hg -R ../server debugobsolete bb4b4b4b4b4b4b4b44b4b4b4b4b4b4b4b4b4b4b4 `getid 'desc(r3)'`
+  $ hg pull -r `getid 'desc(r6)'`
+  pulling from ssh://user@dummy/server
+  no changes found
+  OBSEXC: looking for common markers in 7 nodes
+  OBSEXC: request obsmarkers for 2 common nodes
+  2 new obsolescence markers
+  $ hg debugobshashrange --subranges --rev 'desc("r3")' -R ../server
+           rev         node        index         size        depth      obshash
+             3 2dc09a01254d            0            4            4 8932bf980bb4
+             3 2dc09a01254d            2            2            4 ce1937ca1278
+             1 66f7d451a68b            0            2            2 327c7dd73d29
+             2 01241442b3c2            2            1            3 1ed3c61fb39a
+             0 1ea73414a91b            0            1            1 000000000000
+             3 2dc09a01254d            3            1            4 26f996446ecb
+             1 66f7d451a68b            1            1            2 327c7dd73d29
+  $ hg debugobshashrange --subranges --rev 'desc("r3")'
+           rev         node        index         size        depth      obshash
+             3 2dc09a01254d            0            4            4 8932bf980bb4
+             3 2dc09a01254d            2            2            4 ce1937ca1278
+             1 66f7d451a68b            0            2            2 327c7dd73d29
+             2 01241442b3c2            2            1            3 1ed3c61fb39a
+             0 1ea73414a91b            0            1            1 000000000000
+             3 2dc09a01254d            3            1            4 26f996446ecb
+             1 66f7d451a68b            1            1            2 327c7dd73d29
+  $ hg blackbox
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete --rev '::6' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> writing .hg/cache/tags2-visible with 0 tags (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete --rev '::6' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobshashrange --subranges --rev 'heads(all())' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobshashrange --subranges --rev 'heads(all())' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r1)' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> writing .hg/cache/tags2 with 0 tags (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r1)' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r3)' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r3)' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r6)' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r6)' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> pull -r f69452c5b1af6cbaaa56ef50cf94fff5bcc6ca23 (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 2o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 2o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> pull -r f69452c5b1af6cbaaa56ef50cf94fff5bcc6ca23 exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobshashrange --subranges --rev 'desc("r3")' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobshashrange --subranges --rev 'desc("r3")' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> blackbox (glob)
+  $ rm .hg/blackbox.log
+
+Adding prune markers on existing changeset
+------------------------------------------
+
+  $ hg -R ../server debugobsolete --record-parents `getid 'desc(foo)'`
+  $ hg pull -r `getid 'desc(r4)'`
+  pulling from ssh://user@dummy/server
+  no changes found
+  OBSEXC: looking for common markers in 5 nodes
+  OBSEXC: request obsmarkers for 1 common nodes
+  1 new obsolescence markers
+  $ hg blackbox
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(foo)' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(foo)' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r4)' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log --hidden --template '{node}\n' --rev 'desc(r4)' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> pull -r bebd167eb94d257ace0e814aeb98e6972ed2970d (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> obshashcache reset - new markers affect cached ranges (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (0r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> pull -r bebd167eb94d257ace0e814aeb98e6972ed2970d exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> blackbox (glob)
+  $ rm .hg/blackbox.log
+  $ hg debugobshashrange --subranges --rev 'heads(all())'
+           rev         node        index         size        depth      obshash
+             7 f69452c5b1af            0            7            7 000000000000
+             5 45f8b879de92            0            6            6 b8a4206b0fc6
+             3 2dc09a01254d            0            4            4 8932bf980bb4
+             7 f69452c5b1af            4            3            7 000000000000
+             3 2dc09a01254d            2            2            4 ce1937ca1278
+             5 45f8b879de92            4            2            6 31fc49d36a59
+             1 66f7d451a68b            0            2            2 327c7dd73d29
+             6 c8d03c1b5e94            4            2            6 89755fd39e6d
+             2 01241442b3c2            2            1            3 1ed3c61fb39a
+             0 1ea73414a91b            0            1            1 000000000000
+             3 2dc09a01254d            3            1            4 26f996446ecb
+             5 45f8b879de92            5            1            6 1a0c08180b65
+             1 66f7d451a68b            1            1            2 327c7dd73d29
+             4 bebd167eb94d            4            1            5 b21465ecb790
+             6 c8d03c1b5e94            5            1            6 446c2dc3bce5
+             7 f69452c5b1af            6            1            7 000000000000
+
+Recover after data stripping
+
+  $ hg pull
+  pulling from ssh://user@dummy/server
+  searching for changes
+  OBSEXC: looking for common markers in 8 nodes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files
+  1 new obsolescence markers
+  (run 'hg update' to get a working copy)
+  $ hg rollback
+  repository tip rolled back to revision 7 (undo pull)
+  $ hg blackbox
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobshashrange --subranges --rev 'heads(all())' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobshashrange --subranges --rev 'heads(all())' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> pull (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (1r, 0o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated served branch cache in *.???? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> wrote served branch cache with 1 labels and 2 nodes (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated stablerange cache in *.???? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (1r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> 1 incoming changes - new heads: 4de32a90b66c (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> pull exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> rollback (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated base branch cache in *.???? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> wrote base branch cache with 1 labels and 2 nodes (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> rollback exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> blackbox (glob)
+  $ rm .hg/blackbox.log
+  $ hg debugobshashrange --subranges --rev 'heads(all())'
+           rev         node        index         size        depth      obshash
+             7 f69452c5b1af            0            7            7 000000000000
+             5 45f8b879de92            0            6            6 b8a4206b0fc6
+             3 2dc09a01254d            0            4            4 8932bf980bb4
+             7 f69452c5b1af            4            3            7 000000000000
+             3 2dc09a01254d            2            2            4 ce1937ca1278
+             5 45f8b879de92            4            2            6 31fc49d36a59
+             1 66f7d451a68b            0            2            2 327c7dd73d29
+             6 c8d03c1b5e94            4            2            6 89755fd39e6d
+             2 01241442b3c2            2            1            3 1ed3c61fb39a
+             0 1ea73414a91b            0            1            1 000000000000
+             3 2dc09a01254d            3            1            4 26f996446ecb
+             5 45f8b879de92            5            1            6 1a0c08180b65
+             1 66f7d451a68b            1            1            2 327c7dd73d29
+             4 bebd167eb94d            4            1            5 b21465ecb790
+             6 c8d03c1b5e94            5            1            6 446c2dc3bce5
+             7 f69452c5b1af            6            1            7 000000000000
+  $ hg pull
+  pulling from ssh://user@dummy/server
+  searching for changes
+  OBSEXC: looking for common markers in 8 nodes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files
+  1 new obsolescence markers
+  (run 'hg update' to get a working copy)
+  $ hg blackbox
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobshashrange --subranges --rev 'heads(all())' (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated stablerange cache in *.???? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> strip detected, evo-ext-obshashrange cache reset (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (8r, 12o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobshashrange --subranges --rev 'heads(all())' exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> pull (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> strip detected, evo-ext-obscache cache reset (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (9r, 12o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated stablerange cache in *.???? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (1r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> 1 incoming changes - new heads: 4de32a90b66c (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> pull exited 0 after *.?? seconds (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> blackbox (glob)
+  $ rm .hg/blackbox.log
+  $ hg debugobshashrange --subranges --rev 'heads(all())'
+           rev         node        index         size        depth      obshash
+             8 4de32a90b66c            0            8            8 c7f1f7e9925b
+             5 45f8b879de92            0            6            6 b8a4206b0fc6
+             3 2dc09a01254d            0            4            4 8932bf980bb4
+             8 4de32a90b66c            4            4            8 c681c3e58c27
+             3 2dc09a01254d            2            2            4 ce1937ca1278
+             5 45f8b879de92            4            2            6 31fc49d36a59
+             8 4de32a90b66c            6            2            8 033544c939f0
+             1 66f7d451a68b            0            2            2 327c7dd73d29
+             6 c8d03c1b5e94            4            2            6 89755fd39e6d
+             2 01241442b3c2            2            1            3 1ed3c61fb39a
+             0 1ea73414a91b            0            1            1 000000000000
+             3 2dc09a01254d            3            1            4 26f996446ecb
+             5 45f8b879de92            5            1            6 1a0c08180b65
+             8 4de32a90b66c            7            1            8 033544c939f0
+             1 66f7d451a68b            1            1            2 327c7dd73d29
+             4 bebd167eb94d            4            1            5 b21465ecb790
+             6 c8d03c1b5e94            5            1            6 446c2dc3bce5
+             7 f69452c5b1af            6            1            7 000000000000
--- a/tests/test-evolve-obshistory.t	Wed May 17 11:47:14 2017 +0200
+++ b/tests/test-evolve-obshistory.t	Wed May 17 14:49:02 2017 +0200
@@ -25,9 +25,11 @@
   $ mkcommit ROOT
   $ mkcommit A0
   $ echo 42 >> A0
-  $ hg amend -m "A1"
+  $ hg amend -m "A1
+  > 
+  > Better commit message"
   $ hg log --hidden -G
-  @  changeset:   3:a468dc9b3633
+  @  changeset:   3:4ae3a4151de9
   |  tag:         tip
   |  parent:      0:ea207398892e
   |  user:        test
@@ -51,15 +53,69 @@
   
 Actual test
 -----------
-
+  $ hg debugobshistory 4ae3a4151de9
+  4ae3a4151de9 (3) A1
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as 4ae3a4151de9 (glob)
+  $ hg debugobshistory 4ae3a4151de9 -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [],
+          "debugobshistory.node": "4ae3a4151de9",
+          "debugobshistory.rev": 3,
+          "debugobshistory.shortdescription": "A1"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "4ae3a4151de9"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471f378eab4c",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
+  $ hg debugobshistory --hidden 471f378eab4c
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as 4ae3a4151de9 (glob)
+  $ hg debugobshistory --hidden 471f378eab4c -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "4ae3a4151de9"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471f378eab4c",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
   $ hg update 471f378eab4c
   abort: hidden revision '471f378eab4c'!
-  (use --hidden to access hidden revisions; successor: a468dc9b3633)
+  (use --hidden to access hidden revisions; successor: 4ae3a4151de9)
   [255]
   $ hg update --hidden "desc(A0)"
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory parent is obsolete! (471f378eab4c)
-  (use 'hg evolve' to update to its successor: a468dc9b3633)
+  (use 'hg evolve' to update to its successor: 4ae3a4151de9)
 
 Test output with pruned commit
 ==============================
@@ -93,10 +149,59 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory now at 471f378eab4c
   1 changesets pruned
+  $ hg log --hidden -G
+  x  changeset:   2:0dec01379d3b
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B0
+  |
+  @  changeset:   1:471f378eab4c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
 
 Actual test
 -----------
 
+  $ hg debugobshistory 'desc(B0)' --hidden
+  0dec01379d3b (2) B0
+    pruned by test (*20*) (glob)
+  $ hg debugobshistory 'desc(B0)' --hidden -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.verb": "pruned"
+              }
+          ],
+          "debugobshistory.node": "0dec01379d3b",
+          "debugobshistory.rev": 2,
+          "debugobshistory.shortdescription": "B0"
+      }
+  ]
+  $ hg debugobshistory 'desc(A0)'
+  471f378eab4c (1) A0
+  $ hg debugobshistory 'desc(A0)' -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [],
+          "debugobshistory.node": "471f378eab4c",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
   $ hg up 1
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg up 0dec01379d3b
@@ -195,6 +300,95 @@
 Actual test
 -----------
 
+  $ hg debugobshistory 471597cad322 --hidden
+  471597cad322 (1) A0
+    rewritten by test (*20*) as 337fec4d2edc, f257fde29c7a (glob)
+  $ hg debugobshistory 471597cad322 --hidden -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "337fec4d2edc",
+                      "f257fde29c7a"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471597cad322",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
+  $ hg debugobshistory 337fec4d2edc
+  337fec4d2edc (2) A0
+  471597cad322 (1) A0
+    rewritten by test (*20*) as 337fec4d2edc, f257fde29c7a (glob)
+  $ hg debugobshistory 337fec4d2edc -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [],
+          "debugobshistory.node": "337fec4d2edc",
+          "debugobshistory.rev": 2,
+          "debugobshistory.shortdescription": "A0"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "337fec4d2edc",
+                      "f257fde29c7a"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471597cad322",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
+  $ hg debugobshistory f257fde29c7a
+  f257fde29c7a (3) A0
+  471597cad322 (1) A0
+    rewritten by test (*20*) as 337fec4d2edc, f257fde29c7a (glob)
+  $ hg debugobshistory f257fde29c7a -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [],
+          "debugobshistory.node": "f257fde29c7a",
+          "debugobshistory.rev": 3,
+          "debugobshistory.shortdescription": "A0"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "337fec4d2edc",
+                      "f257fde29c7a"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471597cad322",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
   $ hg update 471597cad322
   abort: hidden revision '471597cad322'!
   (use --hidden to access hidden revisions; successors: 337fec4d2edc, f257fde29c7a)
@@ -357,6 +551,74 @@
 Actual test
 -----------
 
+  $ hg debugobshistory de7290d8b885 --hidden
+  de7290d8b885 (1) A0
+    rewritten by test (*20*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
+  $ hg debugobshistory de7290d8b885 --hidden -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "1ae8bc733a14",
+                      "337fec4d2edc",
+                      "c7f044602e9b",
+                      "f257fde29c7a"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "de7290d8b885",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
+  $ hg debugobshistory c7f044602e9b
+  c7f044602e9b (5) A0
+  de7290d8b885 (1) A0
+    rewritten by test (*20*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
+  $ hg debugobshistory c7f044602e9b -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [],
+          "debugobshistory.node": "c7f044602e9b",
+          "debugobshistory.rev": 5,
+          "debugobshistory.shortdescription": "A0"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "1ae8bc733a14",
+                      "337fec4d2edc",
+                      "c7f044602e9b",
+                      "f257fde29c7a"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "de7290d8b885",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
+  $ hg debugobshistory 2:5
+  337fec4d2edc (2) A0
+  de7290d8b885 (1) A0
+    rewritten by test (*20*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
+  f257fde29c7a (3) A0
+  1ae8bc733a14 (4) A0
+  c7f044602e9b (5) A0
   $ hg update de7290d8b885
   abort: hidden revision 'de7290d8b885'!
   (use --hidden to access hidden revisions; successors: 337fec4d2edc, f257fde29c7a and 2 more)
@@ -423,6 +685,105 @@
  Actual test
  -----------
 
+  $ hg debugobshistory --hidden 471f378eab4c
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as eb5a0daa2192 (glob)
+  $ hg debugobshistory --hidden 471f378eab4c -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "eb5a0daa2192"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471f378eab4c",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
+  $ hg debugobshistory --hidden 0dec01379d3b
+  0dec01379d3b (2) B0
+    rewritten by test (*20*) as eb5a0daa2192 (glob)
+  $ hg debugobshistory --hidden 0dec01379d3b -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "eb5a0daa2192"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "0dec01379d3b",
+          "debugobshistory.rev": 2,
+          "debugobshistory.shortdescription": "B0"
+      }
+  ]
+  $ hg debugobshistory eb5a0daa2192
+  eb5a0daa2192 (3) C0
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as eb5a0daa2192 (glob)
+  0dec01379d3b (2) B0
+    rewritten by test (*20*) as eb5a0daa2192 (glob)
+  $ hg debugobshistory eb5a0daa2192 -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [],
+          "debugobshistory.node": "eb5a0daa2192",
+          "debugobshistory.rev": 3,
+          "debugobshistory.shortdescription": "C0"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "eb5a0daa2192"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471f378eab4c",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "eb5a0daa2192"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "0dec01379d3b",
+          "debugobshistory.rev": 2,
+          "debugobshistory.shortdescription": "B0"
+      }
+  ]
   $ hg update 471f378eab4c
   abort: hidden revision '471f378eab4c'!
   (use --hidden to access hidden revisions; successor: eb5a0daa2192)
@@ -506,6 +867,128 @@
 Actual test
 -----------
 
+  $ hg debugobshistory --hidden 471f378eab4c
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as 65b757b745b9 (glob)
+    rewritten by test (*20*) as fdf9bde5129a (glob)
+  $ hg debugobshistory --hidden 471f378eab4c -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "65b757b745b9"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              },
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "fdf9bde5129a"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471f378eab4c",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
+  $ hg debugobshistory fdf9bde5129a
+  fdf9bde5129a (2) A1
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as 65b757b745b9 (glob)
+    rewritten by test (*20*) as fdf9bde5129a (glob)
+  $ hg debugobshistory fdf9bde5129a -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [],
+          "debugobshistory.node": "fdf9bde5129a",
+          "debugobshistory.rev": 2,
+          "debugobshistory.shortdescription": "A1"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "65b757b745b9"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              },
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "fdf9bde5129a"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471f378eab4c",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
+  $ hg debugobshistory 65b757b745b9
+  65b757b745b9 (3) A2
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as 65b757b745b9 (glob)
+    rewritten by test (*20*) as fdf9bde5129a (glob)
+  $ hg debugobshistory 65b757b745b9 -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [],
+          "debugobshistory.node": "65b757b745b9",
+          "debugobshistory.rev": 3,
+          "debugobshistory.shortdescription": "A2"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "65b757b745b9"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              },
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "fdf9bde5129a"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471f378eab4c",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
   $ hg update 471f378eab4c
   abort: hidden revision '471f378eab4c'!
   (use --hidden to access hidden revisions; diverged)
@@ -514,3 +997,312 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory parent is obsolete! (471f378eab4c)
   (471f378eab4c has diverged, use 'hg evolve -list --divergent' to resolve the issue)
+
+Test output with amended + folded commit
+========================================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/local-amend-fold
+  $ cd $TESTTMP/local-amend-fold
+  $ mkcommit ROOT
+  $ mkcommit A0
+  $ mkcommit B0
+  $ hg amend -m "B1"
+  $ hg log --hidden -G
+  @  changeset:   3:b7ea6d14e664
+  |  tag:         tip
+  |  parent:      1:471f378eab4c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B1
+  |
+  | x  changeset:   2:0dec01379d3b
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     B0
+  |
+  o  changeset:   1:471f378eab4c
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ hg fold --exact -r 'desc(A0) + desc(B1)' --date "0 0" -m "C0"
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log --hidden -G
+  @  changeset:   4:eb5a0daa2192
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   3:b7ea6d14e664
+  | |  parent:      1:471f378eab4c
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     B1
+  | |
+  | | x  changeset:   2:0dec01379d3b
+  | |/   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+ Actual test
+ -----------
+
+  $ hg debugobshistory --hidden 471f378eab4c
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as eb5a0daa2192 (glob)
+  $ hg debugobshistory --hidden 471f378eab4c -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "eb5a0daa2192"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471f378eab4c",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
+  $ hg debugobshistory --hidden 0dec01379d3b
+  0dec01379d3b (2) B0
+    rewritten by test (*20*) as b7ea6d14e664 (glob)
+  $ hg debugobshistory --hidden 0dec01379d3b -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "b7ea6d14e664"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "0dec01379d3b",
+          "debugobshistory.rev": 2,
+          "debugobshistory.shortdescription": "B0"
+      }
+  ]
+  $ hg debugobshistory eb5a0daa2192
+  eb5a0daa2192 (4) C0
+  b7ea6d14e664 (3) B1
+    rewritten by test (*20*) as eb5a0daa2192 (glob)
+  0dec01379d3b (2) B0
+    rewritten by test (*20*) as b7ea6d14e664 (glob)
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as eb5a0daa2192 (glob)
+  $ hg debugobshistory eb5a0daa2192 -Tjson | python -m json.tool
+  [
+      {
+          "debugobshistory.markers": [],
+          "debugobshistory.node": "eb5a0daa2192",
+          "debugobshistory.rev": 4,
+          "debugobshistory.shortdescription": "C0"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "eb5a0daa2192"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "b7ea6d14e664",
+          "debugobshistory.rev": 3,
+          "debugobshistory.shortdescription": "B1"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "b7ea6d14e664"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "0dec01379d3b",
+          "debugobshistory.rev": 2,
+          "debugobshistory.shortdescription": "B0"
+      },
+      {
+          "debugobshistory.markers": [
+              {
+                  "debugobshistory.marker_date": [
+                      *, (glob)
+                      0 (glob)
+                  ],
+                  "debugobshistory.marker_user": "test",
+                  "debugobshistory.succnodes": [
+                      "eb5a0daa2192"
+                  ],
+                  "debugobshistory.verb": "rewritten"
+              }
+          ],
+          "debugobshistory.node": "471f378eab4c",
+          "debugobshistory.rev": 1,
+          "debugobshistory.shortdescription": "A0"
+      }
+  ]
+  $ hg update 471f378eab4c
+  abort: hidden revision '471f378eab4c'!
+  (use --hidden to access hidden revisions; successor: eb5a0daa2192)
+  [255]
+  $ hg update --hidden 'desc(A0)'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory parent is obsolete! (471f378eab4c)
+  (use 'hg evolve' to update to its successor: eb5a0daa2192)
+  $ hg update --hidden 0dec01379d3b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (0dec01379d3b)
+  (use 'hg evolve' to update to its successor: eb5a0daa2192)
+  $ hg update 0dec01379d3b
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (0dec01379d3b)
+  (use 'hg evolve' to update to its successor: eb5a0daa2192)
+  $ hg update --hidden 'desc(B0)'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory parent is obsolete! (0dec01379d3b)
+  (use 'hg evolve' to update to its successor: eb5a0daa2192)
+
+Test output with pushed and pulled obs markers
+==============================================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/local-remote-markers-1
+  $ cd $TESTTMP/local-remote-markers-1
+  $ mkcommit ROOT
+  $ mkcommit A0
+  $ hg log --hidden -G
+  @  changeset:   1:471f378eab4c
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ hg clone $TESTTMP/local-remote-markers-1 $TESTTMP/local-remote-markers-2
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd $TESTTMP/local-remote-markers-2
+  $ hg log --hidden -G
+  @  changeset:   1:471f378eab4c
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ cd $TESTTMP/local-remote-markers-1
+  $ hg amend -m "A1"
+  $ hg amend -m "A2"
+  $ hg log --hidden -G
+  @  changeset:   3:7a230b46bf61
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | x  changeset:   2:fdf9bde5129a
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A1
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+ Actual test
+ -----------
+
+  $ hg debugobshistory 7a230b46bf61
+  7a230b46bf61 (3) A2
+  fdf9bde5129a (2) A1
+    rewritten by test (*20*) as 7a230b46bf61 (glob)
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as fdf9bde5129a (glob)
+  $ cd $TESTTMP/local-remote-markers-2
+  $ hg pull
+  pulling from $TESTTMP/local-remote-markers-1
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 1 files (+1 heads)
+  2 new obsolescence markers
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  working directory parent is obsolete! (471f378eab4c)
+  (use 'hg evolve' to update to its successor: 7a230b46bf61)
+  $ hg debugobshistory 7a230b46bf61 --traceback
+  7a230b46bf61 (2) A2
+  fdf9bde5129a
+    rewritten by test (*20*) as 7a230b46bf61 (glob)
+  471f378eab4c (1) A0
+    rewritten by test (*20*) as fdf9bde5129a (glob)
+  $ hg debugobshistory 7a230b46bf61 --color=debug
+  [evolve.short_node|7a230b46bf61] [evolve.rev|(2)] [evolve.short_description|A2]
+  [evolve.short_node evolve.missing_change_ctx|fdf9bde5129a]
+    [evolve.verb|rewritten] by [evolve.user|test] [evolve.date|(*20*)] as [evolve.short_node|7a230b46bf61] (glob)
+  [evolve.short_node|471f378eab4c] [evolve.rev|(1)] [evolve.short_description|A0]
+    [evolve.verb|rewritten] by [evolve.user|test] [evolve.date|(*20*)] as [evolve.short_node|fdf9bde5129a] (glob)