changeset 5900:14fe42125a7a mercurial-4.8

test-compat: merge mercurial-4.9 into mercurial-4.8
author Anton Shestakov <av6@dwimlabs.net>
date Sun, 25 Apr 2021 13:18:03 +0800
parents 1f301a3062f5 (current diff) de05aec68280 (diff)
children 8bd3348c4150 4cf020149c4a
files tests/test-discovery-obshashrange.t tests/test-evolve-interrupted.t tests/test-pullbundle.t
diffstat 26 files changed, 801 insertions(+), 140 deletions(-) [+]
line wrap: on
line diff
--- a/.gitlab-ci.yml	Thu Mar 11 14:42:51 2021 +0800
+++ b/.gitlab-ci.yml	Sun Apr 25 13:18:03 2021 +0800
@@ -59,3 +59,14 @@
     artifacts:
         paths:
             - html/*
+
+sdist:
+    stage: .post
+    image: registry.heptapod.net/mercurial/ci-images/py3-hgext3rd
+    script:
+        - python3 setup.py sdist
+    artifacts:
+        paths:
+            - dist/*
+    only:
+        - tags
--- a/.hgtags	Thu Mar 11 14:42:51 2021 +0800
+++ b/.hgtags	Sun Apr 25 13:18:03 2021 +0800
@@ -92,3 +92,4 @@
 782cbadb123fe4991e91a03d367e02d0b5ae969c 10.1.0
 35b883a4ff5a97973eb9e6f00014e71f14cebe70 10.2.0
 eadc1d09f2f567fdae7280aefc8cf4cdc4d78cbc 10.2.0.post1
+c0ed8e57463875414d1c06f0428d550c4480d289 10.3.0
--- a/CHANGELOG	Thu Mar 11 14:42:51 2021 +0800
+++ b/CHANGELOG	Sun Apr 25 13:18:03 2021 +0800
@@ -1,25 +1,40 @@
 Changelog
 =========
 
-10.3.0 - in progress
+10.3.1 - in progress
 --------------------
 
+  * cache: fix corruption issue when mixing 32-bit and 64-bit environments
+  * next: unstable changesets with a different topic are no longer targets for
+    hg next as long as it's invoked without --no-topic flag
+  * next: when some potential targets are unstable, ask user which changeset
+    they want to update to (only mixing stable and unstable when --evolve flag
+    is given, which is the default)
+
+10.3.0 -- 2021-03-11
+--------------------
+
+  * doc: document stack as a substitue for MQ's qseries
+  * doc: document revsets provided by evolve extension
+
   * evolve: add a experimental.evolution.in-memory config for running evolve
     in memory (hg >= 5.6)
   * evolve: improve content-divergence resolution that involves parent changes
   * evolve: preserve wdir parent when using `hg evolve --stop`
+
   * obslog: clarify the command name in the help,
+
   * pdiff, pstatus: drop some irrelevant command flags inherited from `hg diff`
     and `hg status` respectively
+
   * rewind: detect and abort on cases when we rewind to changesets that are
     precessors / successors of each other
   * rewind: when user gives only some parts of a fold, include the other parts
     as well, or abort if they are missing from local repo
 
-10.2.1 - in progress
---------------------
+topic (0.22.0)
 
-  * doc: document stack as a substitue for MQ's qseries
+  * doc: change topic phrase 'disappear' to 'fade out'
 
 10.2.0.post1 -- 2021-02-01
 --------------------------
--- a/Makefile	Thu Mar 11 14:42:51 2021 +0800
+++ b/Makefile	Sun Apr 25 13:18:03 2021 +0800
@@ -1,4 +1,4 @@
-PYTHON ?= python
+PYTHON ?= python3
 VERSION = $(shell python setup.py --version)
 TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
 HGTESTS = $(HGROOT)/tests
--- a/debian/changelog	Thu Mar 11 14:42:51 2021 +0800
+++ b/debian/changelog	Sun Apr 25 13:18:03 2021 +0800
@@ -1,3 +1,9 @@
+mercurial-evolve (10.3.0-1) unstable; urgency=medium
+
+  * new upstream release
+
+ -- Anton Shestakov <av6@dwimlabs.net>  Thu, 11 Mar 2021 18:32:26 +0800
+
 mercurial-evolve (10.2.0.post1-1) unstable; urgency=medium
 
   * identical release
--- a/hgext3rd/evolve/__init__.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/__init__.py	Sun Apr 25 13:18:03 2021 +0800
@@ -743,13 +743,13 @@
     else:
         header = _(b"multiple parents, choose one to update:")
         prevs = [p.rev() for p in parents]
-        choosedrev = utility.revselectionprompt(repo.ui, repo, prevs, header)
-        if choosedrev is None:
+        selectedrev = utility.revselectionprompt(repo.ui, repo, prevs, header)
+        if selectedrev is None:
             for p in parents:
                 display(p)
             repo.ui.warn(_(b'multiple parents, explicitly update to one\n'))
         else:
-            target = repo[choosedrev]
+            target = repo[selectedrev]
     return target, bookmark
 
 @eh.command(
@@ -845,9 +845,9 @@
         topic = _getcurrenttopic(repo)
         filtered = set()
         template = shorttemplate
-        if topic and not opts.get("no_topic", False):
-            filtered = set(ctx for ctx in children if ctx.topic() != topic)
-            children = [ctx for ctx in children if ctx not in filtered]
+        if topic and not opts['no_topic']:
+            filtered = set(ctx.rev() for ctx in children if ctx.topic() != topic)
+            children = [ctx for ctx in children if ctx.rev() not in filtered]
             template = utility.stacktemplate
             opts['stacktemplate'] = True
         display = compat.format_changeset_summary_fn(ui, repo, b'next',
@@ -856,10 +856,10 @@
         # check if we need to evolve while updating to the next child revision
         needevolve = False
         aspchildren = evolvecmd._aspiringchildren(repo, [repo[b'.'].rev()])
-        if topic:
-            filtered.update(repo[c] for c in aspchildren
-                            if repo[c].topic() != topic)
-            aspchildren = [ctx for ctx in aspchildren if ctx not in filtered]
+        if topic and not opts['no_topic']:
+            filtered.update(rev for rev in aspchildren
+                            if repo[rev].topic() != topic)
+            aspchildren = [rev for rev in aspchildren if rev not in filtered]
 
         # To catch and prevent the case when `next` would get confused by split,
         # lets filter those aspiring children which can be stablized on one of
@@ -869,7 +869,7 @@
             possdests = evolvecmd._possibledestination(repo, aspchild)
             if possdests & aspirants:
                 filtered.add(aspchild)
-        aspchildren = [ctx for ctx in aspchildren if ctx not in filtered]
+        aspchildren = [rev for rev in aspchildren if rev not in filtered]
         if aspchildren:
             needevolve = True
 
@@ -885,21 +885,21 @@
             else:
                 cmdutil.bailifchanged(repo, hint=_(b'do you want --merge?'))
 
-        if len(children) == 1:
+        if len(children) == 1 and (not opts['evolve'] or not aspchildren):
             c = children[0]
             return _updatetonext(ui, repo, c, display, opts)
         elif children:
             cheader = _(b"ambiguous next changeset, choose one to update:")
-            crevs = [c.rev() for c in children]
-            choosedrev = utility.revselectionprompt(ui, repo, crevs, cheader)
-            if choosedrev is None:
+            crevs = [c.rev() for c in children] + aspchildren
+            selectedrev = utility.revselectionprompt(ui, repo, crevs, cheader)
+            if selectedrev is None:
                 ui.warn(_(b"ambiguous next changeset:\n"))
-                for c in children:
-                    display(c)
+                for rev in crevs:
+                    display(repo[rev])
                 ui.warn(_(b"explicitly update to one of them\n"))
                 return 1
             else:
-                return _updatetonext(ui, repo, repo[choosedrev], display, opts)
+                return _updatetonext(ui, repo, repo[selectedrev], display, opts)
         else:
             if not opts['evolve'] or not aspchildren:
                 if filtered:
@@ -915,16 +915,16 @@
             elif len(aspchildren) > 1:
                 cheader = _(b"ambiguous next (unstable) changeset, choose one to"
                             b" evolve and update:")
-                choosedrev = utility.revselectionprompt(ui, repo,
-                                                        aspchildren, cheader)
-                if choosedrev is None:
+                selectedrev = utility.revselectionprompt(ui, repo,
+                                                         aspchildren, cheader)
+                if selectedrev is None:
                     ui.warn(_(b"ambiguous next (unstable) changeset:\n"))
                     for c in aspchildren:
                         display(repo[c])
                     ui.warn(_(b"(run 'hg evolve --rev REV' on one of them)\n"))
                     return 1
                 else:
-                    return _nextevolve(ui, repo, repo[choosedrev], opts)
+                    return _nextevolve(ui, repo, repo[selectedrev], opts)
             else:
                 return _nextevolve(ui, repo, aspchildren[0], opts)
     finally:
--- a/hgext3rd/evolve/compat.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/compat.py	Sun Apr 25 13:18:03 2021 +0800
@@ -6,7 +6,6 @@
 Compatibility module
 """
 
-import array
 import contextlib
 
 from mercurial import (
@@ -17,7 +16,6 @@
     logcmdutil,
     merge as mergemod,
     obsolete,
-    pycompat,
     registrar,
     repair,
     scmutil,
@@ -25,13 +23,6 @@
     ui as uimod,
 )
 
-if pycompat.ispy3:
-    arraytobytes = array.array.tobytes
-    arrayfrombytes = array.array.frombytes
-else:
-    arraytobytes = array.array.tostring
-    arrayfrombytes = array.array.fromstring
-
 # hg <= 5.2 (c21aca51b392)
 try:
     from mercurial import pathutil
--- a/hgext3rd/evolve/depthcache.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/depthcache.py	Sun Apr 25 13:18:03 2021 +0800
@@ -11,6 +11,7 @@
 
 import array
 
+from mercurial.i18n import _
 from mercurial import (
     localrepo,
     scmutil,
@@ -26,8 +27,6 @@
     utility,
 )
 
-from mercurial.i18n import _
-
 filterparents = utility.filterparents
 
 eh = exthelper.exthelper()
@@ -117,7 +116,7 @@
         def progress(pos, rev=None):
             revstr = b'' if rev is None else (b'rev %d' % rev)
             compat.progress(repo.ui, b'updating depth cache',
-                            pos, revstr, unit=b'revision', total=total)
+                            pos, revstr, unit=_(b'changesets'), total=total)
         progress(0)
         for idx, rev in enumerate(data, 1):
             assert rev == len(self._data), (rev, len(self._data))
@@ -182,20 +181,23 @@
         assert repo.filtername is None
 
         data = repo.cachevfs.tryread(self._filepath)
+        self._cachekey = self.emptykey
         self._data = array.array(r'l')
-        if not data:
-            self._cachekey = self.emptykey
-        else:
+        if data:
             headerdata = data[:self._cachekeysize]
-            self._cachekey = self._deserializecachekey(headerdata)
-            compat.arrayfrombytes(self._data, data[self._cachekeysize:])
+            cachekey = self._deserializecachekey(headerdata)
+            expected = self._datastruct.size * (cachekey[0] + 1)
+            data = data[self._cachekeysize:]
+            if len(data) == expected:
+                self._data.extend(self._deserializedata(data))
+                self._cachekey = cachekey
+            else:
+                repo.ui.debug(b'depthcache file seems to be corrupted, '
+                              b'it will be rebuilt from scratch\n')
         self._ondiskkey = self._cachekey
 
     def save(self, repo):
         """save the data to disk
-
-        Format is pretty simple, we serialise the cache key and then drop the
-        bytearray.
         """
         if self._cachekey is None or self._cachekey == self._ondiskkey:
             return
@@ -204,7 +206,7 @@
             cachefile = repo.cachevfs(self._filepath, b'w', atomictemp=True)
             headerdata = self._serializecachekey()
             cachefile.write(headerdata)
-            cachefile.write(compat.arraytobytes(self._data))
+            cachefile.write(self._serializedata(self._data))
             cachefile.close()
             self._ondiskkey = self._cachekey
         except (IOError, OSError) as exc:
--- a/hgext3rd/evolve/firstmergecache.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/firstmergecache.py	Sun Apr 25 13:18:03 2021 +0800
@@ -11,6 +11,7 @@
 
 import array
 
+from mercurial.i18n import _
 from mercurial import (
     localrepo,
     node as nodemod,
@@ -30,6 +31,15 @@
 
 eh = exthelper.exthelper()
 
+@eh.command(b'debugfirstmergecache', [])
+def debugfirstmergecache(ui, repo, **opts):
+    """display the contents of firstmergecache"""
+    cache = repo.firstmergecache
+    cache.save(repo)
+    for r in repo:
+        ctx = repo[r]
+        ui.write(b'%s %d\n' % (ctx, cache.get(r)))
+
 @eh.reposetup
 def setupcache(ui, repo):
 
@@ -43,7 +53,7 @@
 
         @localrepo.unfilteredmethod
         def destroyed(self):
-            if r'firstmergecach' in vars(self):
+            if r'firstmergecache' in vars(self):
                 self.firstmergecache.clear()
             super(firstmergecacherepo, self).destroyed()
 
@@ -79,7 +89,7 @@
         def progress(pos, rev=None):
             revstr = b'' if rev is None else (b'rev %d' % rev)
             compat.progress(repo.ui, b'updating firstmerge cache',
-                            pos, revstr, unit=b'revision', total=total)
+                            pos, revstr, unit=_(b'changesets'), total=total)
         progress(0)
         for idx, rev in enumerate(data, 1):
             assert rev == len(self._data), (rev, len(self._data))
@@ -119,20 +129,23 @@
         assert repo.filtername is None
 
         data = repo.cachevfs.tryread(self._filepath)
+        self._cachekey = self.emptykey
         self._data = array.array(r'l')
-        if not data:
-            self._cachekey = self.emptykey
-        else:
+        if data:
             headerdata = data[:self._cachekeysize]
-            self._cachekey = self._deserializecachekey(headerdata)
-            compat.arrayfrombytes(self._data, data[self._cachekeysize:])
+            cachekey = self._deserializecachekey(headerdata)
+            expected = self._datastruct.size * (cachekey[0] + 1)
+            data = data[self._cachekeysize:]
+            if len(data) == expected:
+                self._data.extend(self._deserializedata(data))
+                self._cachekey = cachekey
+            else:
+                repo.ui.debug(b'firstmergecache file seems to be corrupted, '
+                              b'it will be rebuilt from scratch\n')
         self._ondiskkey = self._cachekey
 
     def save(self, repo):
         """save the data to disk
-
-        Format is pretty simple, we serialise the cache key and then drop the
-        bytearray.
         """
         if self._cachekey is None or self._cachekey == self._ondiskkey:
             return
@@ -141,7 +154,7 @@
             cachefile = repo.cachevfs(self._filepath, b'w', atomictemp=True)
             headerdata = self._serializecachekey()
             cachefile.write(headerdata)
-            cachefile.write(compat.arraytobytes(self._data))
+            cachefile.write(self._serializedata(self._data))
             cachefile.close()
             self._ondiskkey = self._cachekey
         except (IOError, OSError) as exc:
--- a/hgext3rd/evolve/genericcaches.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/genericcaches.py	Sun Apr 25 13:18:03 2021 +0800
@@ -34,6 +34,8 @@
     _cachekeyspec = b'' # used for serialization
     _cachename = None # used for debug message
 
+    _datastruct = struct.Struct('<q')
+
     @abc.abstractmethod
     def __init__(self):
         super(incrementalcachebase, self).__init__()
@@ -67,7 +69,7 @@
         Subclasses MUST overide this method to actually affect the cache data.
         """
         if reset:
-            self._cachekey = self.emptykey if reset else None
+            self._cachekey = self.emptykey
         else:
             self._cachekey = None
 
@@ -133,6 +135,17 @@
         """read the cachekey from bytes"""
         return self._cachekeystruct.unpack(data)
 
+    def _serializedata(self, data):
+        """turn data into binary form"""
+        return b''.join(self._datastruct.pack(item) for item in data)
+
+    def _deserializedata(self, data):
+        """turn binary into data"""
+        return (
+            self._datastruct.unpack_from(data, i)[0]
+            for i in range(0, len(data), self._datastruct.size)
+        )
+
 class changelogsourcebase(incrementalcachebase):  # pytype: disable=ignored-metaclass
     """an abstract class for cache sourcing data from the changelog
 
@@ -143,7 +156,7 @@
     __metaclass__ = abc.ABCMeta
 
     # default key used for an empty cache
-    emptykey = (0, node.nullid)
+    emptykey = (node.nullrev, node.nullid)
     _cachekeyspec = b'i20s'
     _cachename = None # used for debug message
 
@@ -153,10 +166,9 @@
         """use a cachekey to fetch incremental data
 
         Exists as its own method to help subclass to reuse it."""
-        tiprev = len(cl) - 1
+        tiprev = cl.tiprev()
         tipnode = cl.node(tiprev)
         newkey = (tiprev, tipnode)
-        tiprev = len(cl) - 1
         if newkey == cachekey:
             return False, [], newkey
         keyrev, keynode = cachekey
--- a/hgext3rd/evolve/metadata.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/metadata.py	Sun Apr 25 13:18:03 2021 +0800
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-__version__ = b'10.3.0.dev'
+__version__ = b'10.3.1.dev'
 testedwith = b'4.6.2 4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7'
 minimumhgversion = b'4.6'
 buglink = b'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obscache.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/obscache.py	Sun Apr 25 13:18:03 2021 +0800
@@ -137,7 +137,7 @@
         # /!\ IMPORTANT /!\
         # You must overide this method to actually
         if reset:
-            self._cachekey = self.emptykey if reset else None
+            self._cachekey = self.emptykey
         else:
             self._cachekey = None
 
@@ -195,7 +195,7 @@
         # update the key from the new data
         key = list(self._cachekey)
         if revs:
-            key[0] = len(cl) - 1
+            key[0] = cl.tiprev()
             key[1] = cl.node(key[0])
         if obsmarkers:
             key[2] += len(obsmarkers)
@@ -213,7 +213,7 @@
         ### Is the cache valid ?
         keytiprev, keytipnode, keyobslength, keyobssize, keyobskey = key
         # check for changelog strip
-        tiprev = len(changelog) - 1
+        tiprev = changelog.tiprev()
         if (tiprev < keytiprev
                 or changelog.node(keytiprev) != keytipnode):
             return None
@@ -251,7 +251,7 @@
             reset = True
             key = self.emptykey
             obssize, obskey = obsstore.cachekey()
-            tiprev = len(cl) - 1
+            tiprev = cl.tiprev()
         else:
             tiprev, obssize, obskey = status
 
--- a/hgext3rd/evolve/obsdiscovery.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/obsdiscovery.py	Sun Apr 25 13:18:03 2021 +0800
@@ -22,6 +22,7 @@
 import struct
 import weakref
 
+from mercurial.i18n import _
 from mercurial import (
     encoding,
     error,
@@ -34,7 +35,6 @@
     store,
     util,
 )
-from mercurial.i18n import _
 
 from mercurial.utils.stringutil import forcebytestr
 
@@ -472,7 +472,7 @@
         def progress(pos, rev=None):
             revstr = b'' if rev is None else (b'rev %d' % rev)
             compat.progress(repo.ui, b'updating obshashrange cache',
-                            pos, revstr, unit=b'revision', total=total)
+                            pos, revstr, unit=_(b'changesets'), total=total)
         # warm the cache for the new revs
         progress(0)
         for idx, r in enumerate(revs):
--- a/hgext3rd/evolve/stablerange.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/stablerange.py	Sun Apr 25 13:18:03 2021 +0800
@@ -953,7 +953,7 @@
         starttime = util.timer()
 
         if upto is None:
-            upto = len(cl) - 1
+            upto = cl.tiprev()
         if self._tiprev is None:
             revs = cl.revs(stop=upto)
             nbrevs = upto + 1
--- a/hgext3rd/evolve/stablerangecache.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/stablerangecache.py	Sun Apr 25 13:18:03 2021 +0800
@@ -14,6 +14,7 @@
 import sqlite3
 import time
 
+from mercurial.i18n import _
 from mercurial import (
     commands,
     encoding,
@@ -34,8 +35,6 @@
     utility,
 )
 
-from mercurial.i18n import _
-
 eh = exthelper.exthelper()
 
 LONG_WARNING_TIME = 60
--- a/hgext3rd/evolve/stablesort.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/evolve/stablesort.py	Sun Apr 25 13:18:03 2021 +0800
@@ -250,6 +250,7 @@
 import collections
 import struct
 
+from mercurial.i18n import _
 from mercurial import (
     commands,
     error,
@@ -262,8 +263,6 @@
 
 from mercurial.utils.stringutil import forcebytestr
 
-from mercurial.i18n import _
-
 from . import (
     compat,
     depthcache,
@@ -337,11 +336,12 @@
     unfi = repo.unfiltered()
     revs = unfi.revs('all()')
     nbrevs = len(revs)
-    ui.write('number of revisions: %12d\n' % nbrevs)
+    ui.write(b'number of revisions: %12d\n' % nbrevs)
     merge = unfi.revs('merge()')
     nbmerge = len(merge)
     cache = unfi.stablesort
-    ui.write('number of merge:     %12d\n' % nbmerge)
+    cache.save(repo)
+    ui.write(b'number of merge:     %12d\n' % nbmerge)
     alljumps = []
     alljumpssize = []
     for r in merge:
@@ -352,20 +352,20 @@
         alljumps.append(jumps)
         alljumpssize.append(len(jumps))
     nbjumps = sum(alljumpssize)
-    ui.write('number of jumps:     %12d\n' % nbjumps)
+    ui.write(b'number of jumps:     %12d\n' % nbjumps)
     if not nbjumps:
         return 0
     avgjumps = nbjumps / float(len(alljumpssize))
-    ui.write('average jumps:                 %6.3f\n' % avgjumps)
+    ui.write(b'average jumps:                 %6.3f\n' % avgjumps)
     alljumpssize.sort()
     medianjumps = alljumpssize[len(alljumpssize) // 2]
-    ui.write('median jumps:        %12d\n' % medianjumps)
+    ui.write(b'median jumps:        %12d\n' % medianjumps)
     tensjumps = alljumpssize[len(alljumpssize) * 9 // 10]
-    ui.write('90%% jumps:           %12d\n' % tensjumps)
+    ui.write(b'90%% jumps:           %12d\n' % tensjumps)
     centsjumps = alljumpssize[len(alljumpssize) * 99 // 100]
-    ui.write('99%% jumps:           %12d\n' % centsjumps)
-    ui.write('max jumps:           %12d\n' % max(alljumpssize))
-    ui.write('jump cache size:     %12d bytes\n' % (nbjumps * 12))
+    ui.write(b'99%% jumps:           %12d\n' % centsjumps)
+    ui.write(b'max jumps:           %12d\n' % max(alljumpssize))
+    ui.write(b'jump cache size:     %12d bytes\n' % (nbjumps * 12))
 
 def stablesort_branchpoint(repo, revs, mergecallback=None):
     """return '::revs' topologically sorted in "stable" order
@@ -864,7 +864,7 @@
         def progress(pos, rev=None):
             revstr = b'' if rev is None else (b'rev %d' % rev)
             compat.progress(repo.ui, b'updating stablesort cache',
-                            pos, revstr, unit=b'revision', total=total)
+                            pos, revstr, unit=_(b'changesets'), total=total)
 
         progress(0)
         for idx, rev in enumerate(data):
@@ -895,20 +895,31 @@
         assert repo.filtername is None
 
         data = repo.cachevfs.tryread(self._filepath)
+        self._cachekey = self.emptykey
         self._index = array.array(r'l')
         self._data = array.array(r'l')
-        if not data:
-            self._cachekey = self.emptykey
-        else:
+        if data:
             headerdata = data[:self._cachekeysize]
-            self._cachekey = self._deserializecachekey(headerdata)
+            cachekey = self._deserializecachekey(headerdata)
             offset = self._cachekeysize
             indexsizedata = data[offset:offset + S_INDEXSIZE.size]
             indexsize = S_INDEXSIZE.unpack(indexsizedata)[0]
             offset += S_INDEXSIZE.size
-            compat.arrayfrombytes(self._index, data[offset:offset + indexsize])
-            offset += indexsize
-            compat.arrayfrombytes(self._data, data[offset:])
+            if indexsize % self._datastruct.size == 0 and len(data) - offset >= indexsize:
+                index = list(self._deserializedata(data[offset:offset + indexsize]))
+                offset += indexsize
+                expected = index[-1] * self._datastruct.size * 3
+                data = data[offset:]
+            else:
+                # index cannot be read, so we need to abort somehow
+                expected = None
+            if len(data) == expected:
+                self._index.extend(index)
+                self._data.extend(self._deserializedata(data))
+                self._cachekey = cachekey
+            else:
+                repo.ui.debug(b'stablesortcache file seems to be corrupted, '
+                              b'it will be rebuilt from scratch\n')
         self._ondiskkey = self._cachekey
         pass
 
@@ -924,8 +935,8 @@
 
             # data to write
             headerdata = self._serializecachekey()
-            indexdata = compat.arraytobytes(self._index)
-            data = compat.arraytobytes(self._data)
+            indexdata = self._serializedata(self._index)
+            data = self._serializedata(self._data)
             indexsize = S_INDEXSIZE.pack(len(indexdata))
 
             # writing
--- a/hgext3rd/serverminitopic.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/serverminitopic.py	Sun Apr 25 13:18:03 2021 +0800
@@ -167,8 +167,12 @@
         else:
             # hg <= 4.9 (624d6683c705+b137a6793c51)
             _entries = self
-        new = self.__class__(_entries, self.tipnode, self.tiprev,
-                             self.filteredhash, self._closednodes)
+        args = (_entries, self.tipnode, self.tiprev, self.filteredhash,
+                self._closednodes)
+        if util.safehasattr(self, '_repo'):
+            # hg <= 5.7 (6266d19556ad)
+            args = (self._repo,) + args
+        new = self.__class__(*args)
         new.phaseshash = self.phaseshash
         return new
 
--- a/hgext3rd/topic/__init__.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/topic/__init__.py	Sun Apr 25 13:18:03 2021 +0800
@@ -232,7 +232,7 @@
               b'topic.active': b'green',
               }
 
-__version__ = b'0.22.0.dev'
+__version__ = b'0.22.1.dev'
 
 testedwith = b'4.6.2 4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7'
 minimumhgversion = b'4.6'
--- a/hgext3rd/topic/topicmap.py	Thu Mar 11 14:42:51 2021 +0800
+++ b/hgext3rd/topic/topicmap.py	Sun Apr 25 13:18:03 2021 +0800
@@ -173,8 +173,12 @@
         else:
             # hg <= 4.9 (624d6683c705+b137a6793c51)
             _entries = self
-        new = self.__class__(_entries, self.tipnode, self.tiprev,
-                             self.filteredhash, self._closednodes)
+        args = (_entries, self.tipnode, self.tiprev, self.filteredhash,
+                self._closednodes)
+        if util.safehasattr(self, '_repo'):
+            # hg <= 5.7 (6266d19556ad)
+            args = (self._repo,) + args
+        new = self.__class__(*args)
         new.phaseshash = self.phaseshash
         return new
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-cache-corruption.t	Sun Apr 25 13:18:03 2021 +0800
@@ -0,0 +1,360 @@
+Testing cache corruption and recovery
+https://bz.mercurial-scm.org/show_bug.cgi?id=6354
+
+  $ . $TESTDIR/testlib/pythonpath.sh
+
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > evolve =
+  > [experimental]
+  > obshashrange = 1
+  > obshashrange.warm-cache = yes
+  > [ui]
+  > logtemplate = "{rev} {node|short} {desc} {tags}\n"
+  > EOF
+
+  $ cat >> repack.py << EOF
+  > import struct
+  > import sys
+  > # imitating array.array().tobytes() with a platform-dependent item size
+  > sixtyfour = struct.Struct('<q') # as seen on 64-bit platforms
+  > thirtytwo = struct.Struct('<l') # as seen on 32-bit platforms
+  > iss = struct.Struct('<I') # for rewriting indexsize of stablesortcache
+  > data = []
+  > with open(sys.argv[1], 'rb') as f:
+  >     header = f.read(24)
+  >     if '--index' in sys.argv:
+  >         indexsize = iss.unpack(f.read(iss.size))[0]
+  >     while True:
+  >         buf = f.read(sixtyfour.size)
+  >         if not buf: break
+  >         data.append(sixtyfour.unpack(buf)[0])
+  > with open(sys.argv[1], 'wb') as f:
+  >     f.write(header)
+  >     if '--index' in sys.argv:
+  >         indexsize = int(indexsize * thirtytwo.size / sixtyfour.size)
+  >         f.write(iss.pack(indexsize))
+  >     for item in data:
+  >         f.write(thirtytwo.pack(item))
+  > EOF
+
+  $ cat >> truncate.py << EOF
+  > import os
+  > import sys
+  > with open(sys.argv[1], 'ab') as fp:
+  >     fp.seek(int(sys.argv[2]), os.SEEK_END)
+  >     fp.truncate()
+  > EOF
+
+Simple linear setup
+
+  $ hg init linear
+  $ cd linear
+
+  $ hg debugbuilddag '+3'
+  $ hg log -G
+  o  2 01241442b3c2 r2 tip
+  |
+  o  1 66f7d451a68b r1
+  |
+  o  0 1ea73414a91b r0
+  
+  $ f -s .hg/cache/evoext-*
+  .hg/cache/evoext-depthcache-00: size=48
+  .hg/cache/evoext-firstmerge-00: size=48
+  .hg/cache/evoext-obscache-00: size=67
+  .hg/cache/evoext-stablesortcache-00: size=52
+
+testing depthcache
+
+  $ f -H .hg/cache/evoext-depthcache-00
+  .hg/cache/evoext-depthcache-00:
+  0000: 00 00 00 02 01 24 14 42 b3 c2 bf 32 11 e5 93 b5 |.....$.B...2....|
+  0010: 49 c6 55 ea 65 b2 95 e3 01 00 00 00 00 00 00 00 |I.U.e...........|
+  0020: 02 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 |................|
+
+  $ hg debugdepth --rev 'all()' --method compare --debug
+  1ea73414a91b 1
+  66f7d451a68b 2
+  01241442b3c2 3
+
+  $ "$PYTHON" ../repack.py .hg/cache/evoext-depthcache-00
+  $ f -H .hg/cache/evoext-depthcache-00
+  .hg/cache/evoext-depthcache-00:
+  0000: 00 00 00 02 01 24 14 42 b3 c2 bf 32 11 e5 93 b5 |.....$.B...2....|
+  0010: 49 c6 55 ea 65 b2 95 e3 01 00 00 00 02 00 00 00 |I.U.e...........|
+  0020: 03 00 00 00                                     |....|
+
+  $ hg debugdepth --rev 'all()' --method compare --debug
+  depthcache file seems to be corrupted, it will be rebuilt from scratch
+  1ea73414a91b 1
+  66f7d451a68b 2
+  01241442b3c2 3
+
+  $ "$PYTHON" ../truncate.py .hg/cache/evoext-depthcache-00 -4
+  $ f -H .hg/cache/evoext-depthcache-00
+  .hg/cache/evoext-depthcache-00:
+  0000: 00 00 00 02 01 24 14 42 b3 c2 bf 32 11 e5 93 b5 |.....$.B...2....|
+  0010: 49 c6 55 ea 65 b2 95 e3 01 00 00 00 00 00 00 00 |I.U.e...........|
+  0020: 02 00 00 00 00 00 00 00 03 00 00 00             |............|
+
+  $ hg debugdepth --rev 'all()' --method compare --debug
+  depthcache file seems to be corrupted, it will be rebuilt from scratch
+  1ea73414a91b 1
+  66f7d451a68b 2
+  01241442b3c2 3
+
+testing firstmergecache
+
+  $ f -H .hg/cache/evoext-firstmerge-00
+  .hg/cache/evoext-firstmerge-00:
+  0000: 00 00 00 02 01 24 14 42 b3 c2 bf 32 11 e5 93 b5 |.....$.B...2....|
+  0010: 49 c6 55 ea 65 b2 95 e3 ff ff ff ff ff ff ff ff |I.U.e...........|
+  0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+
+  $ hg debugfirstmergecache --debug
+  1ea73414a91b -1
+  66f7d451a68b -1
+  01241442b3c2 -1
+
+  $ "$PYTHON" ../repack.py .hg/cache/evoext-firstmerge-00
+  $ f -H .hg/cache/evoext-firstmerge-00
+  .hg/cache/evoext-firstmerge-00:
+  0000: 00 00 00 02 01 24 14 42 b3 c2 bf 32 11 e5 93 b5 |.....$.B...2....|
+  0010: 49 c6 55 ea 65 b2 95 e3 ff ff ff ff ff ff ff ff |I.U.e...........|
+  0020: ff ff ff ff                                     |....|
+
+  $ hg debugfirstmergecache --debug
+  firstmergecache file seems to be corrupted, it will be rebuilt from scratch
+  1ea73414a91b -1
+  66f7d451a68b -1
+  01241442b3c2 -1
+
+  $ "$PYTHON" ../truncate.py .hg/cache/evoext-firstmerge-00 -4
+  $ f -H .hg/cache/evoext-firstmerge-00
+  .hg/cache/evoext-firstmerge-00:
+  0000: 00 00 00 02 01 24 14 42 b3 c2 bf 32 11 e5 93 b5 |.....$.B...2....|
+  0010: 49 c6 55 ea 65 b2 95 e3 ff ff ff ff ff ff ff ff |I.U.e...........|
+  0020: ff ff ff ff ff ff ff ff ff ff ff ff             |............|
+
+  $ hg debugfirstmergecache --debug
+  firstmergecache file seems to be corrupted, it will be rebuilt from scratch
+  1ea73414a91b -1
+  66f7d451a68b -1
+  01241442b3c2 -1
+
+testing stablesortcache
+
+  $ f -H .hg/cache/evoext-stablesortcache-00
+  .hg/cache/evoext-stablesortcache-00:
+  0000: 00 00 00 02 01 24 14 42 b3 c2 bf 32 11 e5 93 b5 |.....$.B...2....|
+  0010: 49 c6 55 ea 65 b2 95 e3 00 00 00 18 00 00 00 00 |I.U.e...........|
+  0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0030: 00 00 00 00                                     |....|
+
+  $ hg debugstablesortcache --debug
+  number of revisions:            3
+  number of merge:                0
+  number of jumps:                0
+
+  $ "$PYTHON" ../repack.py .hg/cache/evoext-stablesortcache-00 --index
+  $ f -H .hg/cache/evoext-stablesortcache-00
+  .hg/cache/evoext-stablesortcache-00:
+  0000: 00 00 00 02 01 24 14 42 b3 c2 bf 32 11 e5 93 b5 |.....$.B...2....|
+  0010: 49 c6 55 ea 65 b2 95 e3 00 00 00 0c 00 00 00 00 |I.U.e...........|
+  0020: 00 00 00 00 00 00 00 00                         |........|
+
+  $ hg debugstablesortcache --debug
+  number of revisions:            3
+  stablesortcache file seems to be corrupted, it will be rebuilt from scratch
+  number of merge:                0
+  number of jumps:                0
+
+  $ "$PYTHON" ../truncate.py .hg/cache/evoext-stablesortcache-00 -4
+  $ f -H .hg/cache/evoext-stablesortcache-00
+  .hg/cache/evoext-stablesortcache-00:
+  0000: 00 00 00 02 01 24 14 42 b3 c2 bf 32 11 e5 93 b5 |.....$.B...2....|
+  0010: 49 c6 55 ea 65 b2 95 e3 00 00 00 18 00 00 00 00 |I.U.e...........|
+  0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+
+  $ hg debugstablesortcache --debug
+  number of revisions:            3
+  stablesortcache file seems to be corrupted, it will be rebuilt from scratch
+  number of merge:                0
+  number of jumps:                0
+
+  $ cd ..
+
+A "diamond" setup with a merge
+
+  $ hg init with-a-merge
+  $ cd with-a-merge
+
+  $ hg debugbuilddag '+2 *2 /2'
+  $ hg log -G
+  o    3 2b6d669947cd r3 tip
+  |\
+  | o  2 fa942426a6fd r2
+  | |
+  o |  1 66f7d451a68b r1
+  |/
+  o  0 1ea73414a91b r0
+  
+  $ f -s .hg/cache/evoext-*
+  .hg/cache/evoext-depthcache-00: size=56
+  .hg/cache/evoext-firstmerge-00: size=56
+  .hg/cache/evoext-obscache-00: size=68
+  .hg/cache/evoext-stablesortcache-00: size=84
+
+testing depthcache
+
+  $ f -H .hg/cache/evoext-depthcache-00
+  .hg/cache/evoext-depthcache-00:
+  0000: 00 00 00 03 2b 6d 66 99 47 cd 52 4d 74 f4 3c 1b |....+mf.G.RMt.<.|
+  0010: 11 c7 84 85 89 70 7e ef 01 00 00 00 00 00 00 00 |.....p~.........|
+  0020: 02 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 |................|
+  0030: 04 00 00 00 00 00 00 00                         |........|
+
+  $ hg debugdepth --rev 'all()' --method compare --debug
+  1ea73414a91b 1
+  66f7d451a68b 2
+  fa942426a6fd 2
+  2b6d669947cd 4
+
+  $ "$PYTHON" ../repack.py .hg/cache/evoext-depthcache-00
+  $ f -H .hg/cache/evoext-depthcache-00
+  .hg/cache/evoext-depthcache-00:
+  0000: 00 00 00 03 2b 6d 66 99 47 cd 52 4d 74 f4 3c 1b |....+mf.G.RMt.<.|
+  0010: 11 c7 84 85 89 70 7e ef 01 00 00 00 02 00 00 00 |.....p~.........|
+  0020: 02 00 00 00 04 00 00 00                         |........|
+
+  $ hg debugdepth --rev 'all()' --method compare --debug
+  depthcache file seems to be corrupted, it will be rebuilt from scratch
+  1ea73414a91b 1
+  66f7d451a68b 2
+  fa942426a6fd 2
+  2b6d669947cd 4
+
+  $ "$PYTHON" ../truncate.py .hg/cache/evoext-depthcache-00 -4
+  $ f -H .hg/cache/evoext-depthcache-00
+  .hg/cache/evoext-depthcache-00:
+  0000: 00 00 00 03 2b 6d 66 99 47 cd 52 4d 74 f4 3c 1b |....+mf.G.RMt.<.|
+  0010: 11 c7 84 85 89 70 7e ef 01 00 00 00 00 00 00 00 |.....p~.........|
+  0020: 02 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 |................|
+  0030: 04 00 00 00                                     |....|
+
+  $ hg debugdepth --rev 'all()' --method compare --debug
+  depthcache file seems to be corrupted, it will be rebuilt from scratch
+  1ea73414a91b 1
+  66f7d451a68b 2
+  fa942426a6fd 2
+  2b6d669947cd 4
+
+testing firstmergecache
+
+  $ f -H .hg/cache/evoext-firstmerge-00
+  .hg/cache/evoext-firstmerge-00:
+  0000: 00 00 00 03 2b 6d 66 99 47 cd 52 4d 74 f4 3c 1b |....+mf.G.RMt.<.|
+  0010: 11 c7 84 85 89 70 7e ef ff ff ff ff ff ff ff ff |.....p~.........|
+  0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0030: 03 00 00 00 00 00 00 00                         |........|
+
+  $ hg debugfirstmergecache --debug
+  1ea73414a91b -1
+  66f7d451a68b -1
+  fa942426a6fd -1
+  2b6d669947cd 3
+
+  $ "$PYTHON" ../repack.py .hg/cache/evoext-firstmerge-00
+  $ f -H .hg/cache/evoext-firstmerge-00
+  .hg/cache/evoext-firstmerge-00:
+  0000: 00 00 00 03 2b 6d 66 99 47 cd 52 4d 74 f4 3c 1b |....+mf.G.RMt.<.|
+  0010: 11 c7 84 85 89 70 7e ef ff ff ff ff ff ff ff ff |.....p~.........|
+  0020: ff ff ff ff 03 00 00 00                         |........|
+
+  $ hg debugfirstmergecache --debug
+  firstmergecache file seems to be corrupted, it will be rebuilt from scratch
+  1ea73414a91b -1
+  66f7d451a68b -1
+  fa942426a6fd -1
+  2b6d669947cd 3
+
+  $ "$PYTHON" ../truncate.py .hg/cache/evoext-firstmerge-00 -4
+  $ f -H .hg/cache/evoext-firstmerge-00
+  .hg/cache/evoext-firstmerge-00:
+  0000: 00 00 00 03 2b 6d 66 99 47 cd 52 4d 74 f4 3c 1b |....+mf.G.RMt.<.|
+  0010: 11 c7 84 85 89 70 7e ef ff ff ff ff ff ff ff ff |.....p~.........|
+  0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0030: 03 00 00 00                                     |....|
+
+  $ hg debugfirstmergecache --debug
+  firstmergecache file seems to be corrupted, it will be rebuilt from scratch
+  1ea73414a91b -1
+  66f7d451a68b -1
+  fa942426a6fd -1
+  2b6d669947cd 3
+
+testing stablesortcache
+
+  $ f -H .hg/cache/evoext-stablesortcache-00
+  .hg/cache/evoext-stablesortcache-00:
+  0000: 00 00 00 03 2b 6d 66 99 47 cd 52 4d 74 f4 3c 1b |....+mf.G.RMt.<.|
+  0010: 11 c7 84 85 89 70 7e ef 00 00 00 20 00 00 00 00 |.....p~.... ....|
+  0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0030: 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 |................|
+  0040: 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 |................|
+  0050: 00 00 00 00                                     |....|
+
+  $ hg debugstablesortcache --debug
+  number of revisions:            4
+  number of merge:                1
+  number of jumps:                1
+  average jumps:                  1.000
+  median jumps:                   1
+  90% jumps:                      1
+  99% jumps:                      1
+  max jumps:                      1
+  jump cache size:               12 bytes
+
+  $ "$PYTHON" ../repack.py .hg/cache/evoext-stablesortcache-00 --index
+  $ f -H .hg/cache/evoext-stablesortcache-00
+  .hg/cache/evoext-stablesortcache-00:
+  0000: 00 00 00 03 2b 6d 66 99 47 cd 52 4d 74 f4 3c 1b |....+mf.G.RMt.<.|
+  0010: 11 c7 84 85 89 70 7e ef 00 00 00 10 00 00 00 00 |.....p~.........|
+  0020: 00 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 |................|
+  0030: 01 00 00 00 02 00 00 00                         |........|
+
+  $ hg debugstablesortcache --debug
+  number of revisions:            4
+  stablesortcache file seems to be corrupted, it will be rebuilt from scratch
+  number of merge:                1
+  number of jumps:                1
+  average jumps:                  1.000
+  median jumps:                   1
+  90% jumps:                      1
+  99% jumps:                      1
+  max jumps:                      1
+  jump cache size:               12 bytes
+
+  $ "$PYTHON" ../truncate.py .hg/cache/evoext-stablesortcache-00 -4
+  $ f -H .hg/cache/evoext-stablesortcache-00
+  .hg/cache/evoext-stablesortcache-00:
+  0000: 00 00 00 03 2b 6d 66 99 47 cd 52 4d 74 f4 3c 1b |....+mf.G.RMt.<.|
+  0010: 11 c7 84 85 89 70 7e ef 00 00 00 20 00 00 00 00 |.....p~.... ....|
+  0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0030: 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 |................|
+  0040: 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 |................|
+
+  $ hg debugstablesortcache --debug
+  number of revisions:            4
+  stablesortcache file seems to be corrupted, it will be rebuilt from scratch
+  number of merge:                1
+  number of jumps:                1
+  average jumps:                  1.000
+  median jumps:                   1
+  90% jumps:                      1
+  99% jumps:                      1
+  max jumps:                      1
+  jump cache size:               12 bytes
+
+  $ cd ..
--- a/tests/test-check-sdist.t	Thu Mar 11 14:42:51 2021 +0800
+++ b/tests/test-check-sdist.t	Sun Apr 25 13:18:03 2021 +0800
@@ -35,7 +35,7 @@
 
   $ tar -tzf hg-evolve-*.tar.gz | sed 's|^hg-evolve-[^/]*/||' | sort > files
   $ wc -l files
-  346 files
+  348 files
   $ fgrep debian files
   tests/test-check-debian.t
   $ fgrep __init__.py files
--- a/tests/test-discovery-obshashrange-cache.t	Thu Mar 11 14:42:51 2021 +0800
+++ b/tests/test-discovery-obshashrange-cache.t	Sun Apr 25 13:18:03 2021 +0800
@@ -7,7 +7,7 @@
 
   $ cat << EOF >> $HGRCPATH
   > [extensions]
-  > hgext3rd.evolve =
+  > evolve =
   > blackbox =
   > [defaults]
   > blackbox = -l 100
@@ -17,8 +17,6 @@
   > [ui]
   > logtemplate = "{rev} {node|short} {desc} {tags}\n"
   > ssh = "$PYTHON" "$RUNTESTDIR/dummyssh"
-  > [alias]
-  > debugobsolete=debugobsolete -d '0 0'
   > EOF
 
   $ hg init main
@@ -26,9 +24,7 @@
   $ hg -R main debugbuilddag '.+7'
 
   $ for node in `hg -R main log -T '{node}\n'`; do
-  >     printf $node | grep -o . | sort |tr -d "\n" > ancfile
-  >     anc=`cat ancfile`
-  >     rm ancfile
+  >     anc=`echo $node | grep -o . | sort | tr -d "\n"`
   >     echo "marking $anc as predecessors of $node"
   >     hg -R main debugobsolete $anc $node
   > done
@@ -72,7 +68,7 @@
   remote: added 8 changesets with 0 changes to 0 files
   remote: 8 new obsolescence markers
 
-sever cash is warm
+server cache is warm
 
   $ f -s server/.hg/cache/evoext*
   server/.hg/cache/evoext-depthcache-00: size=88
@@ -82,7 +78,7 @@
   server/.hg/cache/evoext_obshashrange_v2.sqlite: size=?* (glob)
   server/.hg/cache/evoext_stablerange_v2.sqlite: size=?* (glob)
 
-client cash is warm
+client cache is warm
 
   $ f -s main/.hg/cache/evoext*
   main/.hg/cache/evoext-depthcache-00: size=88
@@ -128,7 +124,7 @@
   no changes found
   OBSEXC: looking for common markers in 8 nodes
 
-client cash is warm
+client cache is warm
 
   $ f -s main/.hg/cache/evoext*
   main/.hg/cache/evoext-depthcache-00: size=88
@@ -156,7 +152,7 @@
   no changes found
   [1]
 
-client cash is warm
+client cache is warm
 
   $ f -s main/.hg/cache/evoext*
   main/.hg/cache/evoext-depthcache-00: size=88
@@ -165,3 +161,39 @@
   main/.hg/cache/evoext-stablesortcache-00: size=92
   main/.hg/cache/evoext_obshashrange_v2.sqlite: size=?* (glob)
   main/.hg/cache/evoext_stablerange_v2.sqlite: size=?* (glob)
+
+let's look at the contents of the caches
+
+the reason we're doing this is to make sure our serialization works the same
+way on all platforms, see https://bz.mercurial-scm.org/show_bug.cgi?id=6354
+
+we don't need to check sqlite caches
+
+  $ f -H main/.hg/cache/evoext-*
+  main/.hg/cache/evoext-depthcache-00:
+  0000: 00 00 00 07 4d e3 2a 90 b6 6c d0 83 eb f3 c0 0b |....M.*..l......|
+  0010: 41 27 7a a7 ab ca 51 dd 01 00 00 00 00 00 00 00 |A'z...Q.........|
+  0020: 02 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 |................|
+  0030: 04 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 |................|
+  0040: 06 00 00 00 00 00 00 00 07 00 00 00 00 00 00 00 |................|
+  0050: 08 00 00 00 00 00 00 00                         |........|
+  main/.hg/cache/evoext-firstmerge-00:
+  0000: 00 00 00 07 4d e3 2a 90 b6 6c d0 83 eb f3 c0 0b |....M.*..l......|
+  0010: 41 27 7a a7 ab ca 51 dd ff ff ff ff ff ff ff ff |A'z...Q.........|
+  0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0050: ff ff ff ff ff ff ff ff                         |........|
+  main/.hg/cache/evoext-obscache-00:
+  0000: 00 00 00 00 00 00 00 07 4d e3 2a 90 b6 6c d0 83 |........M.*..l..|
+  0010: eb f3 c0 0b 41 27 7a a7 ab ca 51 dd 00 00 00 00 |....A'z...Q.....|
+  0020: 00 00 00 08 00 00 00 00 00 00 02 29 4c 16 b4 10 |...........)L...|
+  0030: 03 f6 c1 57 8d 58 25 ef c7 73 cd 79 03 4b fb 46 |...W.X%..s.y.K.F|
+  0040: 00 00 00 00 00 00 00 00                         |........|
+  main/.hg/cache/evoext-stablesortcache-00:
+  0000: 00 00 00 07 4d e3 2a 90 b6 6c d0 83 eb f3 c0 0b |....M.*..l......|
+  0010: 41 27 7a a7 ab ca 51 dd 00 00 00 40 00 00 00 00 |A'z...Q....@....|
+  0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0050: 00 00 00 00 00 00 00 00 00 00 00 00             |............|
--- a/tests/test-discovery-obshashrange.t	Thu Mar 11 14:42:51 2021 +0800
+++ b/tests/test-discovery-obshashrange.t	Sun Apr 25 13:18:03 2021 +0800
@@ -5,7 +5,7 @@
 
   $ cat << EOF >> $HGRCPATH
   > [extensions]
-  > hgext3rd.evolve =
+  > evolve =
   > blackbox =
   > [defaults]
   > blackbox = -l 100
@@ -18,8 +18,6 @@
   > [ui]
   > logtemplate = "{rev} {node|short} {desc} {tags}\n"
   > ssh = "$PYTHON" "$RUNTESTDIR/dummyssh"
-  > [alias]
-  > debugobsolete=debugobsolete -d '0 0'
   > EOF
 
   $ getid() {
@@ -79,27 +77,21 @@
   * @0000000000000000000000000000000000000000 (*)> writing .hg/cache/tags2-visible with 0 tags (glob)
   * @0000000000000000000000000000000000000000 (*)> log -G exited 0 after *.?? seconds (glob)
   * @0000000000000000000000000000000000000000 (*)> debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 66f7d451a68b85ed82ff5fcc254daf50c74144bd (glob)
-  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
   * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
   * @0000000000000000000000000000000000000000 (*)> debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 66f7d451a68b85ed82ff5fcc254daf50c74144bd exited 0 after *.?? seconds (glob)
   * @0000000000000000000000000000000000000000 (*)> debugobsolete bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 01241442b3c2bf3211e593b549c655ea65b295e3 --config *experimental.obshashrange.max-revs=1* (glob)
-  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
   * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
   * @0000000000000000000000000000000000000000 (*)> debugobsolete bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 01241442b3c2bf3211e593b549c655ea65b295e3 --config *experimental.obshashrange.max-revs=1* exited 0 after *.?? seconds (glob)
   * @0000000000000000000000000000000000000000 (*)> debugobsolete cccccccccccccccccccccccccccccccccccccccc bebd167eb94d257ace0e814aeb98e6972ed2970d (glob)
-  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
   * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
   * @0000000000000000000000000000000000000000 (*)> debugobsolete cccccccccccccccccccccccccccccccccccccccc bebd167eb94d257ace0e814aeb98e6972ed2970d exited 0 after *.?? seconds (glob)
   * @0000000000000000000000000000000000000000 (*)> debugobsolete dddddddddddddddddddddddddddddddddddddddd c8d03c1b5e94af74b772900c58259d2e08917735 --config *experimental.obshashrange.warm-cache=0* (glob)
-  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
   * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
   * @0000000000000000000000000000000000000000 (*)> debugobsolete dddddddddddddddddddddddddddddddddddddddd c8d03c1b5e94af74b772900c58259d2e08917735 --config *experimental.obshashrange.warm-cache=0* exited 0 after *.?? seconds (glob)
   * @0000000000000000000000000000000000000000 (*)> debugobsolete eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee 4de32a90b66cd083ebf3c00b41277aa7abca51dd (glob)
-  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (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
@@ -137,14 +129,10 @@
   (run 'hg update' to get a working copy)
   $ hg -R ../server blackbox
   * @0000000000000000000000000000000000000000 (*)> debugobshashrange --subranges --rev tip (glob)
-  * @0000000000000000000000000000000000000000 (*)> strip detected, evo-ext-stablerange-mergepoint cache reset (glob)
-  * @0000000000000000000000000000000000000000 (*)> strip detected, evo-ext-depthcache cache reset (glob)
   * @0000000000000000000000000000000000000000 (*)> updated evo-ext-depthcache in *.???? seconds (8r) (glob)
   * @0000000000000000000000000000000000000000 (*)> updated evo-ext-stablerange-mergepoint in *.???? seconds (8r) (glob)
   1970/01/01 00:00:00 * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obshashrange in *.???? seconds (8r, 5o) (glob)
-  1970/01/01 00:00:00 * @0000000000000000000000000000000000000000 (*)> strip detected, evo-ext-stablesort cache reset (glob)
   1970/01/01 00:00:00 * @0000000000000000000000000000000000000000 (*)> updated evo-ext-stablesort in *.???? seconds (8r) (glob)
-  1970/01/01 00:00:00 * @0000000000000000000000000000000000000000 (*)> strip detected, evo-ext-firstmerge cache reset (glob)
   1970/01/01 00:00:00 * @0000000000000000000000000000000000000000 (*)> updated evo-ext-firstmerge in *.???? seconds (8r) (glob)
   * @0000000000000000000000000000000000000000 (*)> debugobshashrange --subranges --rev tip exited 0 after *.?? seconds (glob)
   * @0000000000000000000000000000000000000000 (*)> -R server serve --stdio (glob)
@@ -292,7 +280,6 @@
   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)
@@ -303,7 +290,6 @@
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (1r, 0o) (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> commit -m foo exited 0 after *.?? seconds (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete ffffffffffffffffffffffffffffffffffffffff 45f8b879de922f6a6e620ba04205730335b6fc7e (glob)
-  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete ffffffffffffffffffffffffffffffffffffffff 45f8b879de922f6a6e620ba04205730335b6fc7e exited 0 after *.?? seconds (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> push -f --debug (glob)
@@ -327,17 +313,13 @@
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> preparing listkeys for "namespaces" (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> sending listkeys command (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> received listkey for "namespaces": 40 bytes (glob)
-  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> strip detected, evo-ext-stablerange-mergepoint cache reset (glob)
-  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> strip detected, evo-ext-depthcache cache reset (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-depthcache in *.???? seconds (6r) (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-stablerange-mergepoint in *.???? seconds (6r) (glob)
   1970/01/01 00:00:00 * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obshashrange in *.???? seconds (6r, 4o) (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> query 0; add more sample (target 100, current 1) (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> query 0; sample size is 9, largest range 5 (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> sending evoext_obshashrange_v1 command (glob)
-  1970/01/01 00:00:00 * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> strip detected, evo-ext-stablesort cache reset (glob)
   1970/01/01 00:00:00 * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-stablesort in *.???? seconds (6r) (glob)
-  1970/01/01 00:00:00 * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> strip detected, evo-ext-firstmerge cache reset (glob)
   1970/01/01 00:00:00 * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-firstmerge in *.???? seconds (6r) (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> obsdiscovery, 0/5 mismatch - 1 obshashrange queries in *.???? seconds (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> obsdiscovery, 0/5 mismatch - 1 obshashrange queries in *.???? seconds (glob)
@@ -365,11 +347,9 @@
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> writing .hg/cache/tags2-visible with 0 tags (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> log -G exited 0 after *.?? seconds (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete 111111111111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd (glob)
-  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete 111111111111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd exited 0 after *.?? seconds (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete 22222222222222222bbbbbbbbbbbbb2222222222 2dc09a01254db841290af0538aa52f6f52c776e3 (glob)
-  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete 22222222222222222bbbbbbbbbbbbb2222222222 2dc09a01254db841290af0538aa52f6f52c776e3 exited 0 after *.?? seconds (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> push (glob)
@@ -421,16 +401,13 @@
   $ hg -R ../server blackbox
   * @0000000000000000000000000000000000000000 (*)> -R ../server/ debugobsolete --rev '::tip' (glob) (no-windows !)
   * @0000000000000000000000000000000000000000 (*)> -R ../server/ debugobsolete --rev ::tip (glob) (windows !)
-  * @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) (no-windows !)
   * @0000000000000000000000000000000000000000 (*)> -R ../server/ debugobsolete --rev ::tip exited 0 after *.?? seconds (glob) (windows !)
   * @0000000000000000000000000000000000000000 (*)> -R ../server debugobsolete aaaaaaa11111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd (glob)
-  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
   * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
   * @0000000000000000000000000000000000000000 (*)> -R ../server debugobsolete aaaaaaa11111111aaaaaaaaa1111111111111111 66f7d451a68b85ed82ff5fcc254daf50c74144bd exited 0 after *.?? seconds (glob)
   * @0000000000000000000000000000000000000000 (*)> -R ../server debugobsolete bbbbbbb2222222222bbbbbbbbbbbbb2222222222 bebd167eb94d257ace0e814aeb98e6972ed2970d (glob)
-  * @0000000000000000000000000000000000000000 (*)> alias 'debugobsolete' expands to 'debugobsolete -d '0 0'' (glob)
   * @0000000000000000000000000000000000000000 (*)> updated evo-ext-obscache in *.???? seconds (0r, 1o) (glob)
   * @0000000000000000000000000000000000000000 (*)> -R ../server debugobsolete bbbbbbb2222222222bbbbbbbbbbbbb2222222222 bebd167eb94d257ace0e814aeb98e6972ed2970d exited 0 after *.?? seconds (glob)
   * @0000000000000000000000000000000000000000 (*)> -R server serve --stdio (glob)
@@ -450,7 +427,6 @@
   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)
@@ -524,7 +500,6 @@
   $ hg blackbox
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete --rev '::6' (glob) (no-windows !)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete --rev ::6 (glob) (windows !)
-  * @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) (no-windows !)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> debugobsolete --rev ::6 exited 0 after *.?? seconds (glob) (windows !)
@@ -1063,7 +1038,6 @@
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> writing .hg/cache/tags2-visible with 0 tags (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> log -G exited 0 after *.?? seconds (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> pull (glob)
-  * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-stablerange-mergepoint cache reset (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> strip detected, evo-ext-depthcache cache reset (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-depthcache in *.???? seconds (8r) (glob)
   * @bebd167eb94d257ace0e814aeb98e6972ed2970d (*)> updated evo-ext-stablerange-mergepoint in *.???? seconds (8r) (glob)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-topic-prev-next.t	Sun Apr 25 13:18:03 2021 +0800
@@ -0,0 +1,226 @@
+  $ . "$TESTDIR/testlib/topic_setup.sh"
+  $ . "$TESTDIR/testlib/common.sh"
+
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > evolve =
+  > [ui]
+  > logtemplate = '{rev} [{topic}] {desc}\n'
+  > EOF
+
+Checking target ambiguity in hg next
+
+  $ hg init ambiguous-next
+  $ cd ambiguous-next
+
+  $ mkcommit root
+  $ hg topic A
+  marked working directory as topic: A
+  $ mkcommit A1
+  active topic 'A' grew its first changeset
+  (see 'hg help topics' for more information)
+  $ mkcommit A2
+  $ mkcommit A3
+  $ mkcommit A4
+  $ hg up 'desc("A3")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit A5
+  $ hg up 'desc("A2")'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg topic B
+  $ mkcommit B1
+  active topic 'B' grew its first changeset
+  (see 'hg help topics' for more information)
+  $ mkcommit B2
+
+  $ hg log -G
+  @  7 [B] B2
+  |
+  o  6 [B] B1
+  |
+  | o  5 [A] A5
+  | |
+  | | o  4 [A] A4
+  | |/
+  | o  3 [A] A3
+  |/
+  o  2 [A] A2
+  |
+  o  1 [A] A1
+  |
+  o  0 [] root
+  
+
+Quick sanity check
+
+  $ hg up 'desc("A1")'
+  switching to topic A
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg next
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  [s2] A2
+  $ hg stack
+  ### topic: A (2 heads)
+  ### target: default (branch)
+  s5: A4
+  s3^ A3 (base)
+  s4: A5
+  s3: A3
+  s2@ A2 (current)
+  s1: A1
+  s0^ root (base)
+  $ hg next
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  [s3] A3
+  $ hg log -G
+  o  7 [B] B2
+  |
+  o  6 [B] B1
+  |
+  | o  5 [A] A5
+  | |
+  | | o  4 [A] A4
+  | |/
+  | @  3 [A] A3
+  |/
+  o  2 [A] A2
+  |
+  o  1 [A] A1
+  |
+  o  0 [] root
+  
+  $ hg next
+  ambiguous next changeset:
+  [s5] A4
+  [s4] A5
+  explicitly update to one of them
+  [1]
+
+Let's make some changesets unstable
+
+  $ hg up 'desc("A2")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo foo > A2
+  $ hg amend
+  5 new orphan changesets
+  $ hg stack
+  ### topic: A (2 heads)
+  ### target: default (branch)
+  s5$ A4 (orphan)
+  s3^ A3 (base orphan)
+  s4$ A5 (orphan)
+  s3$ A3 (orphan)
+  s2@ A2 (current)
+  s1: A1
+  s0^ root (base)
+  $ hg log -G
+  @  8 [A] A2
+  |
+  | *  7 [B] B2
+  | |
+  | *  6 [B] B1
+  | |
+  | | *  5 [A] A5
+  | | |
+  | | | *  4 [A] A4
+  | | |/
+  | | *  3 [A] A3
+  | |/
+  | x  2 [A] A2
+  |/
+  o  1 [A] A1
+  |
+  o  0 [] root
+  
+
+B1 shouldn't be considered a target, orphan or not
+
+  $ hg next
+  move:[s3] A3
+  atop:[s2] A2
+  working directory is now at 2b67b6a6cae1
+
+B1 is not considered a target when it's been stabilized
+
+  $ hg up 'desc("A2")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg evolve --rev 'desc("B1")'
+  move:[6] B1
+  atop:[8] A2
+  switching to topic A
+  $ hg next
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  [s3] A3
+
+A4 and A5 should be ambiguous for hg next even if A5 is an orphan and A4 is not
+
+  $ hg evolve --rev 'desc("A3") + desc("A4")'
+  move:[s5] A4
+  atop:[s3] A3
+  $ hg up 'desc("A3")'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg stack
+  ### topic: A (2 heads)
+  ### target: default (branch)
+  s5: A4
+  s3^ A3 (base current)
+  s4$ A5 (orphan)
+  s3@ A3 (current)
+  s2: A2
+  s1: A1
+  s0^ root (base)
+  $ hg next --no-evolve --dry-run
+  hg update 51d70e81d730;
+  [s5] A4
+  $ hg next
+  ambiguous next changeset:
+  [s5] A4
+  [s4] A5
+  explicitly update to one of them
+  [1]
+
+  $ cd ..
+
+Making sure plain hg next sticks to topic when target is unstable
+
+  $ hg init next-unstable-topic
+  $ cd next-unstable-topic
+
+  $ mkcommit ROOT
+  $ hg topics topic-a
+  marked working directory as topic: topic-a
+  $ mkcommit A
+  active topic 'topic-a' grew its first changeset
+  (see 'hg help topics' for more information)
+  $ hg topics topic-b
+  $ mkcommit B
+  active topic 'topic-b' grew its first changeset
+  (see 'hg help topics' for more information)
+  $ hg up 'topic("topic-a")'
+  switching to topic topic-a
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo foo > foo
+  $ hg ci -A --amend
+  adding foo
+  1 new orphan changesets
+  $ hg log -G
+  @  3 [topic-a] A
+  |
+  | *  2 [topic-b] B
+  | |
+  | x  1 [topic-a] A
+  |/
+  o  0 [] ROOT
+  
+
+  $ hg next
+  no children on topic "topic-a"
+  do you want --no-topic
+  [1]
+
+  $ hg next --no-topic
+  move:[2] B
+  atop:[3] A
+  working directory is now at 53f8332d648f
+
+  $ cd ..
--- a/tests/test-topic-server.t	Thu Mar 11 14:42:51 2021 +0800
+++ b/tests/test-topic-server.t	Sun Apr 25 13:18:03 2021 +0800
@@ -48,7 +48,7 @@
   Mercurial Distributed SCM (*) (glob)
   (see https://mercurial-scm.org for more information)
   
-  Copyright (C) 2005-* Matt Mackall and others (glob)
+  Copyright (C) 2005-* (glob)
   This is free software; see the source for copying conditions. There is NO
   warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   
@@ -75,7 +75,7 @@
   Mercurial Distributed SCM (*) (glob)
   (see https://mercurial-scm.org for more information)
   
-  Copyright (C) 2005-* Matt Mackall and others (glob)
+  Copyright (C) 2005-* (glob)
   This is free software; see the source for copying conditions. There is NO
   warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   
--- a/tests/test-version-install.t	Thu Mar 11 14:42:51 2021 +0800
+++ b/tests/test-version-install.t	Sun Apr 25 13:18:03 2021 +0800
@@ -9,7 +9,7 @@
   Mercurial Distributed SCM (version *) (glob)
   (see https://mercurial-scm.org for more information)
   
-  Copyright (C) 2005-* Matt Mackall and others (glob)
+  Copyright (C) 2005-* (glob)
   This is free software; see the source for copying conditions. There is NO
   warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.