changeset 6576:144a5814f1ce stable

branching: merge into stable in preparation for release
author Anton Shestakov <av6@dwimlabs.net>
date Wed, 18 Oct 2023 16:37:08 -0300
parents a567e5e90b4f (current diff) 1ecb25d771df (diff)
children 2d4a34d6606a 6ad9b4e01876
files
diffstat 49 files changed, 1376 insertions(+), 1035 deletions(-) [+]
line wrap: on
line diff
--- a/.gitlab/issue_templates/new-version.md	Wed Aug 16 15:11:43 2023 -0300
+++ b/.gitlab/issue_templates/new-version.md	Wed Oct 18 16:37:08 2023 -0300
@@ -1,6 +1,6 @@
 This is the actual check list for releasing evolve version X.Y.Z
 
-More details in the [README.rst file](README.rst#L210).
+More details in the [README.rst file](README.rst#L219).
 
 Preparation
 
@@ -23,9 +23,10 @@
 * [ ] build .deb on Heptapod CI for the tagged commit
 * [ ] add `.dev0` to the `__version__` field
 * [ ] merge stable into default
-* [ ] push the result to https://www.mercurial-scm.org/repo/evolve/
+* [ ] push the result to https://repo.mercurial-scm.org/evolve/
 * [ ] send the announcement by email to evolve-testers@mercurial-scm.org
 * [ ] send the announcement by email to mercurial@mercurial-scm.org
 * [ ] publish the announcement as a blog on https://octobus.net/blog/
 * [ ] update #hg-evolve topic
 * [ ] tweet about it
+* [ ] toot about it
--- a/CHANGELOG	Wed Aug 16 15:11:43 2023 -0300
+++ b/CHANGELOG	Wed Oct 18 16:37:08 2023 -0300
@@ -1,17 +1,40 @@
 Changelog
 =========
 
-11.0.3 - in progress
+11.1.0 - in progress
 --------------------
 
+  * remove deprecated evolve.serveronly extension, evolve extension is
+    recommended for all users, clients and servers
+
   * evolve: don't warn about topics while resolving public content-divergence
 
-topic (1.0.3)
+  * evolve, pullbundle: drop compatibility with Mercurial 4.8
+
+topic (1.1.0)
+
+  * remove deprecated serverminitopic extension, topic extension is recommended
+    for all users, clients and servers
+
+  * pick: update commit message hashes like other rewrite commands
 
-  * topic: only set topic on workingcommitctx if it doesn't already have one
+  * topic namespaces: add `experimental.tns-allow-rewrite` config option to
+    check topic namespace before rewriting changesets (known limitations: does
+    not prevent rebase and histedit from editing changesets outside of
+    configured topic namespaces on Mercurial 5.2 and older)
+  * topic namespaces: add `experimental.tns-default-pull-namespaces` config
+    option to pull only certain changesets by default
+  * topic namespaces: teach `hg import` and `hg export` to handle topic
+    namespaces
+  * topic namespaces: invalidate topic namespace cache in
+    repo.invalidatecaches()
+
+  * topic: only set topic on workingcommitctx if it doesn't already have one,
+    making `hg import` always prioritize patch metadata
   * topic: properly process revbranchcache before sending it to peers
     (issue6841)
-  * topic: invalidate topic namespace cache in repo.invalidatecaches()
+
+  * drop compatibility with Mercurial 4.8
 
 11.0.2 -- 2023-07-05
 --------------------
--- a/README.rst	Wed Aug 16 15:11:43 2023 -0300
+++ b/README.rst	Wed Oct 18 16:37:08 2023 -0300
@@ -87,19 +87,6 @@
 * make sure evolve is installed for the same version of Python that you use for
   running Mercurial (``hg debuginstall | grep Python``).
 
-Server-only Setup
-=================
-
-It is possible to enable a smaller subset of the features aimed at servers that
-simply serve repositories::
-
-    $ hg config --edit # add these two lines:
-    [extensions]
-    evolve.serveronly =
-
-It skips the additions of the new commands and local UI messages that might add
-performance overhead.
-
 Extension Purpose
 =================
 
--- a/debian/control	Wed Aug 16 15:11:43 2023 -0300
+++ b/debian/control	Wed Oct 18 16:37:08 2023 -0300
@@ -7,7 +7,7 @@
  Pierre-Yves David <pierre-yves.david@logilab.fr>,
 Standards-Version: 4.3.0
 Build-Depends:
- mercurial (>= 4.8),
+ mercurial (>= 4.9),
  python3,
  debhelper (>= 10),
  dh-python,
@@ -26,7 +26,7 @@
  ${python3:Depends},
  ${misc:Depends},
  ${sphinxdoc:Depends},
- mercurial (>= 4.8),
+ mercurial (>= 4.9),
 Built-Using: ${sphinxdoc:Built-Using}
 Description: evolve extension for Mercurial
  This package provides the experimental "evolve" extension for the Mercurial
--- a/hgext3rd/evolve/__init__.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/evolve/__init__.py	Wed Oct 18 16:37:08 2023 -0300
@@ -14,9 +14,6 @@
 - enable the changeset-evolution feature for Mercurial,
 - improves some aspect of the early implementation in Mercurial core,
 
-Note that a version dedicated to server usage only (no local working copy) is
-available as 'evolve.serveronly'.
-
 While many feature related to changeset evolution are directly handled by core
 this extensions contains significant additions recommended to any user of
 changeset evolution.
@@ -33,7 +30,7 @@
 backported to older version of Mercurial by this extension. Some older
 experimental protocols are also supported for a longer time in the extension to
 help people transitioning. (The extension is currently compatible down to
-Mercurial version 4.8).
+Mercurial version 4.9).
 
 New Config::
 
@@ -490,9 +487,14 @@
 
 @eh.uisetup
 def _installalias(ui):
+    odiffalias = b"diff --hidden --rev 'limit(predecessors(.),1)' --rev ."
     if ui.config(b'alias', b'odiff', None) is None:
         ui.setconfig(b'alias', b'odiff',
-                     b"diff --hidden --rev 'limit(predecessors(.),1)' --rev .",
+                     odiffalias,
+                     b'evolve')
+    if ui.config(b'alias', b'obsdiff', None) is None:
+        ui.setconfig(b'alias', b'obsdiff',
+                     odiffalias,
                      b'evolve')
 
 #####################################################################
--- a/hgext3rd/evolve/cmdrewrite.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/evolve/cmdrewrite.py	Wed Oct 18 16:37:08 2023 -0300
@@ -1417,9 +1417,10 @@
 def _dopick(ui, repo, pickstate, origctx):
     """shared logic for performing or continuing a pick"""
     overrides = {(b'phases', b'new-commit'): origctx.phase()}
+    new_desc = evolvecmd._rewrite_commit_message_hashes(repo,
+                                                        origctx.description())
     with repo.ui.configoverride(overrides, b'pick'):
-        newnode = repo.commit(text=origctx.description(),
-                              user=origctx.user(),
+        newnode = repo.commit(text=new_desc, user=origctx.user(),
                               date=origctx.date(), extra=origctx.extra())
     compat.setbranch(repo, origctx.branch())
 
--- a/hgext3rd/evolve/compat.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/evolve/compat.py	Wed Oct 18 16:37:08 2023 -0300
@@ -21,6 +21,7 @@
     node,
     obsolete,
     pycompat,
+    rewriteutil,
     scmutil,
     util,
 )
@@ -559,3 +560,26 @@
 else:
     # hg <= 5.6 (527ce85c2e60)
     StateError = error.Abort
+
+try:
+    retained_extras_on_rebase = rewriteutil.retained_extras_on_rebase
+    preserve_extras_on_rebase = rewriteutil.preserve_extras_on_rebase
+except AttributeError:
+    # hg <= 6.4 (cbcbf63b6dbf)
+    retained_extras_on_rebase = {
+        b'source',
+        b'intermediate-source',
+    }
+
+    def preserve_extras_on_rebase(old_ctx, new_extra):
+        """preserve the relevant `extra` entries from old_ctx on rebase-like operations
+        """
+        old_extra = old_ctx.extra()
+        for key in retained_extras_on_rebase:
+            value = old_extra.get(key)
+            if value is not None:
+                new_extra[key] = value
+
+    # give other extensions an opportunity to collaborate
+    rewriteutil.retained_extras_on_rebase = retained_extras_on_rebase
+    rewriteutil.preserve_extras_on_rebase = preserve_extras_on_rebase
--- a/hgext3rd/evolve/evolvecmd.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/evolve/evolvecmd.py	Wed Oct 18 16:37:08 2023 -0300
@@ -959,18 +959,9 @@
     _finalizerelocate(repo, orig, dest, nodenew, tr, category, evolvestate)
     return nodenew
 
-# This is copied from hgext.rebase._savegraft()
-def _savegraft(ctx, extra):
-    s = ctx.extra().get(b'source', None)
-    if s is not None:
-        extra[b'source'] = s
-    s = ctx.extra().get(b'intermediate-source', None)
-    if s is not None:
-        extra[b'intermediate-source'] = s
-
 def _relocatecommit(repo, orig, dest, pctx, keepbranch, commitmsg, update):
     extra = {}
-    _savegraft(orig, extra)
+    compat.preserve_extras_on_rebase(orig, extra)
     extra[b'rebase_source'] = orig.hex()
     targetphase = max(orig.phase(), phases.draft)
     configoverrides = {
@@ -2201,7 +2192,7 @@
     ctx = orig
 
     extra = {}
-    _savegraft(ctx, extra)
+    compat.preserve_extras_on_rebase(orig, extra)
     extra[b'rebase_source'] = orig.hex()
 
     user = ctx.user()
--- a/hgext3rd/evolve/metadata.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/evolve/metadata.py	Wed Oct 18 16:37:08 2023 -0300
@@ -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'11.0.3.dev0'
-testedwith = b'4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0 6.1 6.2 6.3 6.4 6.5'
-minimumhgversion = b'4.8'
+__version__ = b'11.1.0.dev0'
+testedwith = b'4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0 6.1 6.2 6.3 6.4 6.5'
+minimumhgversion = b'4.9'
 buglink = b'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/rewriteutil.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/evolve/rewriteutil.py	Wed Oct 18 16:37:08 2023 -0300
@@ -11,6 +11,8 @@
 #   happy one piece of it (and hopefully, able to reuse it in other core
 #   commands).
 
+import functools
+
 from mercurial import (
     cmdutil,
     commands,
@@ -60,8 +62,13 @@
 
     <action> can be used to control the commit message.
     """
+    if not pycompat.ispy3 and isinstance(corerewriteutil.precheck, functools.partial):
+        # inspect.getargspec() on py2 cannot inspect functools.partial objects
+        # directly, so we need to provide it with the underlying function
+        args = pycompat.getargspec(corerewriteutil.precheck.func).args
+    else:
+        args = pycompat.getargspec(corerewriteutil.precheck).args
     # hg <= 6.1 (d4752aeb20f1)
-    args = pycompat.getargspec(corerewriteutil.precheck).args
     if r'check_divergence' in args:
         return corerewriteutil.precheck(repo, revs, action,
                                         check_divergence=check_divergence)
--- a/hgext3rd/evolve/serveronly.py	Wed Aug 16 15:11:43 2023 -0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-'''enable experimental obsolescence feature of Mercurial (DEPRECATED)
-
-OBSOLESCENCE IS AN EXPERIMENTAL FEATURE MAKE SURE YOU UNDERSTOOD THE INVOLVED
-CONCEPT BEFORE USING IT.
-
-! THIS EXTENSION IS INTENDED FOR SERVER SIDE ONLY USAGE !
-
-For client side usages it is recommended to use the evolve extension for
-improved user interface.'''
-
-from __future__ import absolute_import
-
-import sys
-import os
-
-from mercurial import obsolete
-
-try:
-    from . import (
-        compat,
-        exthelper,
-        metadata,
-        obscache,
-        obsexchange,
-    )
-except (ValueError, ImportError) as exc:
-    if (isinstance(exc, ValueError)
-        and str(exc) != b'Attempted relative import in non-package'):
-        raise
-    # extension imported using direct path
-    sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
-    from evolve import (
-        compat,
-        exthelper,
-        metadata,
-        obscache,
-        obsexchange,
-    )
-
-__version__ = metadata.__version__
-testedwith = metadata.testedwith
-minimumhgversion = metadata.minimumhgversion
-buglink = metadata.buglink
-
-eh = exthelper.exthelper()
-eh.merge(compat.eh)
-eh.merge(obscache.eh)
-eh.merge(obsexchange.eh)
-uisetup = eh.finaluisetup
-extsetup = eh.finalextsetup
-reposetup = eh.finalreposetup
-cmdtable = eh.cmdtable
-configtable = eh.configtable
-
-@eh.reposetup
-def default2evolution(ui, repo):
-    evolveopts = repo.ui.configlist(b'experimental', b'evolution')
-    if not evolveopts:
-        evolveopts = b'all'
-        repo.ui.setconfig(b'experimental', b'evolution', evolveopts, b'evolve')
-    if obsolete.isenabled(repo, b'exchange'):
-        # if no config explicitly set, disable bundle1
-        if not isinstance(repo.ui.config(b'server', b'bundle1'), bytes):
-            repo.ui.setconfig(b'server', b'bundle1', False, b'evolve')
--- a/hgext3rd/evolve/stablerange.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/evolve/stablerange.py	Wed Oct 18 16:37:08 2023 -0300
@@ -417,7 +417,7 @@
 }
 
 @eh.command(
-    b'debugstablerange',
+    b'debug::evo-ext-stable-range',
     [
         (b'r', b'rev', [], b'operate on (rev, 0) ranges for rev in REVS'),
         (b'', b'subranges', False, b'recursively display data for subranges too'),
--- a/hgext3rd/evolve/stablerangecache.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/evolve/stablerangecache.py	Wed Oct 18 16:37:08 2023 -0300
@@ -441,7 +441,7 @@
             super(sqlstablerange, self).warmup(repo, upto)
 
 @eh.command(
-    b'debugstablerangecache',
+    b'debug::evo-ext-stable-range-cache',
     [] + commands.formatteropts,
     _(b''))
 def debugstablerangecache(ui, repo, **opts):
--- a/hgext3rd/evolve/stablesort.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/evolve/stablesort.py	Wed Oct 18 16:37:08 2023 -0300
@@ -293,7 +293,7 @@
     return key
 
 @eh.command(
-    b'debugstablesort',
+    b'debug::evo-ext-stable-sort',
     [
         (b'r', b'rev', [], b'heads to start from'),
         (b'', b'method', b'branchpoint', b"method used for sorting, one of: "
@@ -326,7 +326,7 @@
     displayer.close()
 
 @eh.command(
-    b'debugstablesortcache',
+    b'debug::evo-ext-stable-sort-cache',
     [] + commands.formatteropts,
     _(b''))
 def debugstablesortcache(ui, repo, **opts):
--- a/hgext3rd/pullbundle.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/pullbundle.py	Wed Oct 18 16:37:08 2023 -0300
@@ -93,8 +93,8 @@
 from mercurial.i18n import _
 
 __version__ = b'0.2.0.dev'
-testedwith = b'4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0 6.1'
-minimumhgversion = b'4.8'
+testedwith = b'4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0 6.1 6.2 6.3'
+minimumhgversion = b'4.9'
 buglink = b'https://bz.mercurial-scm.org/'
 
 cmdtable = {}
--- a/hgext3rd/serverminitopic.py	Wed Aug 16 15:11:43 2023 -0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,261 +0,0 @@
-"""enable a minimal verison of topic for server (DEPRECATED)
-
-! This extensions is not actively maintained
-! We recommand using the main topic extension instead
-
-Non publishing repository will see topic as "branch:topic" in the branch field.
-
-In addition to adding the extensions, the feature must be manually enabled in the config:
-
-    [experimental]
-    server-mini-topic = yes
-"""
-import hashlib
-import contextlib
-
-from mercurial import (
-    branchmap,
-    context,
-    encoding,
-    extensions,
-    node,
-    registrar,
-    util,
-)
-
-from mercurial import wireprotov1server
-
-if util.safehasattr(registrar, 'configitem'):
-
-    configtable = {}
-    configitem = registrar.configitem(configtable)
-    configitem(b'experimental', b'server-mini-topic',
-               default=False,
-    )
-
-# nodemap.get and index.[has_node|rev|get_rev]
-# hg <= 5.2 (02802fa87b74)
-def getgetrev(cl):
-    """Returns index.get_rev or nodemap.get (for pre-5.3 Mercurial)."""
-    if util.safehasattr(cl.index, 'get_rev'):
-        return cl.index.get_rev
-    return cl.nodemap.get
-
-# hg <= 5.4 (e2d17974a869)
-def nonpublicphaseroots(repo):
-    if util.safehasattr(repo._phasecache, 'nonpublicphaseroots'):
-        return repo._phasecache.nonpublicphaseroots(repo)
-    return set().union(
-        *[roots for roots in repo._phasecache.phaseroots[1:] if roots]
-    )
-
-def hasminitopic(repo):
-    """true if minitopic is enabled on the repository
-
-    (The value is cached on the repository)
-    """
-    enabled = getattr(repo, '_hasminitopic', None)
-    if enabled is None:
-        enabled = (repo.ui.configbool(b'experimental', b'server-mini-topic')
-                   and not repo.publishing())
-        repo._hasminitopic = enabled
-    return enabled
-
-### make topic visible though "ctx.branch()"
-
-def topicbranch(orig, self):
-    branch = orig(self)
-    if hasminitopic(self._repo) and self.phase():
-        topic = self._changeset.extra.get(b'topic')
-        if topic is not None:
-            topic = encoding.tolocal(topic)
-            branch = b'%s:%s' % (branch, topic)
-    return branch
-
-### avoid caching topic data in rev-branch-cache
-
-class revbranchcacheoverlay(object):
-    """revbranch mixin that don't use the cache for non public changeset"""
-
-    def _init__(self, *args, **kwargs):
-        super(revbranchcacheoverlay, self).__init__(*args, **kwargs)
-        if r'branchinfo' in vars(self):
-            del self.branchinfo
-
-    def branchinfo(self, rev, changelog=None):
-        """return branch name and close flag for rev, using and updating
-        persistent cache."""
-        phase = self._repo._phasecache.phase(self._repo, rev)
-        if phase:
-            ctx = self._repo[rev]
-            return ctx.branch(), ctx.closesbranch()
-        return super(revbranchcacheoverlay, self).branchinfo(rev)
-
-def reposetup(ui, repo):
-    """install a repo class with a special revbranchcache"""
-
-    if hasminitopic(repo):
-        repo = repo.unfiltered()
-
-        class minitopicrepo(repo.__class__):
-            """repository subclass that install the modified cache"""
-
-            def revbranchcache(self):
-                if self._revbranchcache is None:
-                    cache = super(minitopicrepo, self).revbranchcache()
-
-                    class topicawarerbc(revbranchcacheoverlay, cache.__class__):
-                        pass
-                    cache.__class__ = topicawarerbc
-                    if r'branchinfo' in vars(cache):
-                        del cache.branchinfo
-                    self._revbranchcache = cache
-                return self._revbranchcache
-
-        repo.__class__ = minitopicrepo
-
-### topic aware branch head cache
-
-def _phaseshash(repo, maxrev):
-    """uniq ID for a phase matching a set of rev"""
-    revs = set()
-    cl = repo.changelog
-    fr = cl.filteredrevs
-    getrev = getgetrev(cl)
-    for n in nonpublicphaseroots(repo):
-        r = getrev(n)
-        if r not in fr and r < maxrev:
-            revs.add(r)
-    key = node.nullid
-    revs = sorted(revs)
-    if revs:
-        s = hashlib.sha1()
-        for rev in revs:
-            s.update(b'%d;' % rev)
-        key = s.digest()
-    return key
-
-# needed to prevent reference used for 'super()' call using in branchmap.py to
-# no go into cycle. (yes, URG)
-_oldbranchmap = branchmap.branchcache
-
-@contextlib.contextmanager
-def oldbranchmap():
-    previous = branchmap.branchcache
-    try:
-        branchmap.branchcache = _oldbranchmap
-        yield
-    finally:
-        branchmap.branchcache = previous
-
-_publiconly = set([
-    b'base',
-    b'immutable',
-])
-
-def mighttopic(repo):
-    return hasminitopic(repo) and repo.filtername not in _publiconly
-
-class _topiccache(branchmap.branchcache): # combine me with branchmap.branchcache
-    @classmethod
-    def fromfile(cls, repo):
-        orig = super(_topiccache, cls).fromfile
-        return wrapread(orig, repo)
-
-    def __init__(self, *args, **kwargs):
-        # super() call may fail otherwise
-        with oldbranchmap():
-            super(_topiccache, self).__init__(*args, **kwargs)
-        self.phaseshash = None
-
-    def copy(self):
-        """return an deep copy of the branchcache object"""
-        if util.safehasattr(self, '_entries'):
-            _entries = self._entries
-        else:
-            # hg <= 4.9 (624d6683c705+b137a6793c51)
-            _entries = self
-        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
-
-    def validfor(self, repo):
-        """Is the cache content valid regarding a repo
-
-        - False when cached tipnode is unknown or if we detect a strip.
-        - True when cache is up to date or a subset of current repo."""
-        valid = super(_topiccache, self).validfor(repo)
-        if not valid:
-            return False
-        elif self.phaseshash is None:
-            # phasehash at None means this is a branchmap
-            # coming from a public only set
-            return True
-        else:
-            try:
-                valid = self.phaseshash == _phaseshash(repo, self.tiprev)
-                return valid
-            except IndexError:
-                return False
-
-    def write(self, repo):
-        # we expect (hope) mutable set to be small enough to be that computing
-        # it all the time will be fast enough
-        if not mighttopic(repo):
-            super(_topiccache, self).write(repo)
-
-    def update(self, repo, revgen):
-        """Given a branchhead cache, self, that may have extra nodes or be
-        missing heads, and a generator of nodes that are strictly a superset of
-        heads missing, this function updates self to be correct.
-        """
-        super(_topiccache, self).update(repo, revgen)
-        if mighttopic(repo):
-            self.phaseshash = _phaseshash(repo, self.tiprev)
-
-def wrapread(orig, repo):
-    # Avoiding to write cache for filter where topic applies is a good step,
-    # but we need to also avoid reading it. Existing branchmap cache might
-    # exists before the turned the feature on.
-    if mighttopic(repo):
-        return None
-    return orig(repo)
-
-# advertise topic capabilities
-
-def wireprotocaps(orig, repo, proto):
-    caps = orig(repo, proto)
-    if hasminitopic(repo):
-        caps.append(b'topics')
-    return caps
-
-# wrap the necessary bit
-
-def wrapclass(container, oldname, new):
-    old = getattr(container, oldname)
-    if not issubclass(old, new):
-        targetclass = new
-        # check if someone else already wrapped the class and handle that
-        if not issubclass(new, old):
-            class targetclass(new, old):
-                pass
-        setattr(container, oldname, targetclass)
-    current = getattr(container, oldname)
-    assert issubclass(current, new), (current, new, targetclass)
-
-def uisetup(ui):
-    wrapclass(branchmap, 'branchcache', _topiccache)
-    try:
-        # hg <= 4.9 (3461814417f3)
-        extensions.wrapfunction(branchmap, 'read', wrapread)
-    except AttributeError:
-        # Mercurial 5.0; branchcache.fromfile now takes care of this
-        # which is alredy defined on _topiccache
-        pass
-    extensions.wrapfunction(wireprotov1server, '_capabilities', wireprotocaps)
-    extensions.wrapfunction(context.changectx, 'branch', topicbranch)
--- a/hgext3rd/topic/__init__.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/topic/__init__.py	Wed Oct 18 16:37:08 2023 -0300
@@ -165,6 +165,7 @@
 from mercurial.i18n import _
 from mercurial import (
     bookmarks,
+    bundlerepo,
     changelog,
     cmdutil,
     commands,
@@ -186,7 +187,9 @@
     phases,
     pycompat,
     registrar,
+    rewriteutil,
     scmutil,
+    smartset,
     templatefilters,
     util,
 )
@@ -233,10 +236,10 @@
               b'log.topic': b'green_background',
               }
 
-__version__ = b'1.0.3.dev0'
+__version__ = b'1.1.0.dev0'
 
-testedwith = b'4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0 6.1 6.2 6.3 6.4 6.5'
-minimumhgversion = b'4.8'
+testedwith = b'4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0 6.1 6.2 6.3 6.4 6.5'
+minimumhgversion = b'4.9'
 buglink = b'https://bz.mercurial-scm.org/'
 
 configtable = {}
@@ -269,6 +272,22 @@
 configitem(b'_internal', b'tns-publish',
            default=False,
 )
+# used for signaling that the current command has explicit target arguments
+# (e.g. --rev or --branch) and we should ignore tns-default-* config
+configitem(b'_internal', b'tns-explicit-target',
+           default=False,
+)
+configitem(b'devel', b'tns-report-transactions',
+           default=lambda: [],
+)
+# used for allowing users to rewrite history only in their "own" topic
+# namespaces
+configitem(b'experimental', b'tns-allow-rewrite',
+           default=configitems.dynamicdefault,
+)
+configitem(b'experimental', b'tns-default-pull-namespaces',
+           default=configitems.dynamicdefault,
+)
 configitem(b'experimental', b'topic-mode.server',
            default=configitems.dynamicdefault,
 )
@@ -291,24 +310,27 @@
                   default=None,
         )
     if (b'devel' not in ui._knownconfig
-            or not ui._knownconfig[b'devel'].get(b'random')):
+            or not ui._knownconfig[b'devel'].get(b'randomseed')):
         extraitem(b'devel', b'randomseed',
                   default=None,
         )
 
 def _contexttns(self, force=False):
     if not force and not self.mutable():
-        return b'default'
+        return b'none'
     cache = getattr(self._repo, '_tnscache', None)
     # topic loaded, but not enabled (eg: multiple repo in the same process)
     if cache is None:
-        return b'default'
+        return b'none'
+    # topic namespace is meaningless when topic is not set
+    if not self.topic(force):
+        return b'none'
     if self.rev() is None:
         # don't cache volatile ctx instances that aren't stored on-disk yet
-        return self.extra().get(b'topic-namespace', b'default')
+        return self.extra().get(b'topic-namespace', b'none')
     tns = cache.get(self.rev())
     if tns is None:
-        tns = self.extra().get(b'topic-namespace', b'default')
+        tns = self.extra().get(b'topic-namespace', b'none')
         self._repo._tnscache[self.rev()] = tns
     return tns
 
@@ -430,6 +452,70 @@
         or (missing and self.deleted())
     )
 
+def find_affected_tns(repo, tr):
+    origrepolen = tr.changes[b'origrepolen']
+    unfi = repo.unfiltered()
+
+    affected = set()
+    # These are the new changesets that weren't in the repo before this
+    # transaction
+    for rev in smartset.spanset(repo, start=origrepolen):
+        ctx = unfi[rev]
+        tns = ctx.topic_namespace()
+        affected.add(tns)
+
+    # These are the changesets obsoleted by this transaction
+    for rev in obsutil.getobsoleted(repo, tr):
+        ctx = unfi[rev]
+        tns = ctx.topic_namespace()
+        affected.add(tns)
+
+    # Phase movements, we only care about:
+    # - publishing changesets (since they lose topic namespace)
+    # - forcefully making changesets draft again
+    # - turning secret changesets draft and making them visible to peers
+    tnsphases = (phases.secret, phases.draft)
+    phasechanges = tr.changes[b'phases']
+    if isinstance(phasechanges, dict):
+        # hg <= 5.3 (fdc802f29b2c)
+        phasechanges = [((k,), v) for k, v in phasechanges.items()]
+    for revs, (old, new) in phasechanges:
+        if old not in tnsphases and new not in tnsphases:
+            # Skip phase movement if there is no phase (old or new) that has
+            # visible topic namespace (i.e. draft and secret)
+            continue
+        revs = [rev for rev in revs if rev < origrepolen]
+        for rev in revs:
+            ctx = unfi[rev]
+            tns = ctx.topic_namespace(force=True)
+            affected.add(tns)
+
+    # We want to detect any bookmark movement, even within one topic namespace
+    for name, nodes in tr.changes[b'bookmarks'].items():
+        for n in nodes:
+            if n is not None and n in unfi:
+                ctx = unfi[n]
+                tns = ctx.topic_namespace()
+                affected.add(tns)
+
+    # We don't care about changesets without topic namespace
+    affected.discard(b'none')
+
+    tr.changes[b'tns'] = affected
+    report_affected_tns(repo, tr)
+
+def report_affected_tns(repo, tr):
+    report = set(repo.ui.configlist(b'devel', b'tns-report-transactions'))
+    # transaction names sometimes also have a URL after a newline byte
+    trnames = (trname.partition(b'\n')[0] for trname in tr._names)
+    if b'*' not in report:
+        # * matches any transaction
+        if not any(trname in report for trname in trnames):
+            return
+
+    if tr.changes[b'tns']:
+        repo.ui.status(b'topic namespaces affected: %s\n' % b' '.join(sorted(tr.changes[b'tns'])))
+
 def uisetup(ui):
     destination.modsetup(ui)
     discovery.modsetup(ui)
@@ -506,8 +592,37 @@
         extensions.wrapfunction(histedit.histeditaction, 'applychange',
                                 applychangewrap)
 
+    # Wrapping precheck() both in core and in evolve to make sure all rewrite
+    # operations that could use precheck() are covered
+    extensions.wrapfunction(rewriteutil, 'precheck', wrapprecheck)
+    try:
+        evolve = extensions.find(b'evolve')
+        extensions.wrapfunction(evolve.rewriteutil, 'precheck', wrapprecheck)
+    except (KeyError, AttributeError):
+        pass
+
     server.setupserver(ui)
 
+    # We want bundle repos to also have caches for topic extension, because we
+    # want to, for example, see topic and topic namespaces in `hg incoming`
+    # regardless if the bundle repo has topic extension, as long as local repo
+    # has topic enabled.
+    class topicbundlerepo(bundlerepo.bundlerepository):
+        @util.propertycache
+        def _tnscache(self):
+            return {}
+
+        @util.propertycache
+        def _topiccache(self):
+            return {}
+
+        def invalidatecaches(self):
+            self._tnscache.clear()
+            self._topiccache.clear()
+            super(topicbundlerepo, self).invalidatecaches()
+
+    bundlerepo.bundlerepository = topicbundlerepo
+
 def reposetup(ui, repo):
     if not isinstance(repo, localrepo.localrepository):
         return # this can be a peer in the ssh case (puzzling)
@@ -541,6 +656,7 @@
             else:
                 mode = b'none'
             caps.add(b'ext-topics-publish=%s' % mode)
+            caps.add(b'ext-topics-tns-heads')
             return caps
 
         def commit(self, *args, **kwargs):
@@ -568,15 +684,16 @@
         def topic_namespaces(self):
             if self._topic_namespaces is not None:
                 return self._topic_namespaces
-            namespaces = set([self.currenttns])
+            namespaces = set([b'none', self.currenttns])
             for c in self.set(b'not public()'):
                 namespaces.add(c.topic_namespace())
+            namespaces.remove(b'none')
             self._topic_namespaces = namespaces
             return namespaces
 
         @property
         def currenttns(self):
-            return self.vfs.tryread(b'topic-namespace') or b'default'
+            return self.vfs.tryread(b'topic-namespace') or b'none'
 
         @util.propertycache
         def _topiccache(self):
@@ -657,6 +774,20 @@
                     def branchmaptns(self):
                         usetopic = not self._repo.publishing()
                         return self._repo.branchmaptns(topic=usetopic)
+
+                    def tns_heads(self, namespaces):
+                        if b'*' in namespaces:
+                            # pulling all topic namespaces, all changesets are visible
+                            return self._repo.heads()
+                        else:
+                            # only changesets in the selected topic namespaces are visible
+                            h = []
+                            entries = compat.bcentries(self._repo.branchmaptns())
+                            for branch, nodes in compat.branchmapitems(entries):
+                                namedbranch, tns, topic = common.parsefqbn(branch)
+                                if tns == b'none' or tns in namespaces:
+                                    h.extend(nodes)
+                            return h
                 peer.__class__ = topicpeer
             return peer
 
@@ -670,21 +801,21 @@
             if self.ui.configbool(b'experimental', b'enforce-single-head'):
                 if util.safehasattr(tr, '_validator'):
                     # hg <= 5.3 (36f08ae87ef6)
-                    origvalidator = tr._validator
+                    origvalidator_single_head = tr._validator
 
-                def _validate(tr2):
+                def _validate_single_head(tr2):
                     repo = reporef()
                     flow.enforcesinglehead(repo, tr2)
 
                 def validator(tr2):
-                    _validate(tr2)
-                    return origvalidator(tr2)
+                    _validate_single_head(tr2)
+                    return origvalidator_single_head(tr2)
 
                 if util.safehasattr(tr, '_validator'):
                     # hg <= 5.3 (36f08ae87ef6)
                     tr._validator = validator
                 else:
-                    tr.addvalidator(b'000-enforce-single-head', _validate)
+                    tr.addvalidator(b'000-enforce-single-head', _validate_single_head)
 
             topicmodeserver = self.ui.config(b'experimental',
                                              b'topic-mode.server', b'ignore')
@@ -694,21 +825,21 @@
             if (topicmodeserver != b'ignore' and ispush):
                 if util.safehasattr(tr, '_validator'):
                     # hg <= 5.3 (36f08ae87ef6)
-                    origvalidator = tr._validator
+                    origvalidator_untopiced = tr._validator
 
-                def _validate(tr2):
+                def _validate_untopiced(tr2):
                     repo = reporef()
                     flow.rejectuntopicedchangeset(repo, tr2)
 
                 def validator(tr2):
-                    _validate(tr2)
-                    return origvalidator(tr2)
+                    _validate_untopiced(tr2)
+                    return origvalidator_untopiced(tr2)
 
                 if util.safehasattr(tr, '_validator'):
                     # hg <= 5.3 (36f08ae87ef6)
                     tr._validator = validator
                 else:
-                    tr.addvalidator(b'000-reject-untopiced', _validate)
+                    tr.addvalidator(b'000-reject-untopiced', _validate_untopiced)
 
             elif publishbare and ispush:
                 origclose = tr.close
@@ -726,21 +857,40 @@
             if not allow_publish:
                 if util.safehasattr(tr, '_validator'):
                     # hg <= 5.3 (36f08ae87ef6)
-                    origvalidator = tr._validator
+                    origvalidator_publish = tr._validator
 
-                def _validate(tr2):
+                def _validate_publish(tr2):
                     repo = reporef()
                     flow.reject_publish(repo, tr2)
 
                 def validator(tr2):
-                    _validate(tr2)
-                    return origvalidator(tr2)
+                    _validate_publish(tr2)
+                    return origvalidator_publish(tr2)
 
                 if util.safehasattr(tr, '_validator'):
                     # hg <= 5.3 (36f08ae87ef6)
                     tr._validator = validator
                 else:
-                    tr.addvalidator(b'000-reject-publish', _validate)
+                    tr.addvalidator(b'000-reject-publish', _validate_publish)
+
+            if util.safehasattr(tr, '_validator'):
+                # hg <= 5.3 (36f08ae87ef6)
+                origvalidator_affected_tns = tr._validator
+
+            def _validate_affected_tns(tr2):
+                repo = reporef()
+                find_affected_tns(repo, tr2)
+
+            def validator(tr2):
+                result = origvalidator_affected_tns(tr2)
+                _validate_affected_tns(tr2)
+                return result
+
+            if util.safehasattr(tr, '_validator'):
+                # hg <= 5.3 (36f08ae87ef6)
+                tr._validator = validator
+            else:
+                tr.addvalidator(b'999-find-affected-tns', _validate_affected_tns)
 
             # real transaction start
             ct = self.currenttopic
@@ -818,7 +968,7 @@
             self._extra[b'topic-namespace'] = repo.currenttns
         else:
             # Default value will be dropped from extra by another hack at the changegroup level
-            self._extra[b'topic-namespace'] = b'default'
+            self._extra[b'topic-namespace'] = b'none'
     if constants.extrakey not in self._extra:
         if getattr(repo, 'currenttopic', b''):
             self._extra[constants.extrakey] = repo.currenttopic
@@ -829,12 +979,16 @@
 def wrapadd(orig, cl, manifest, files, desc, transaction, p1, p2, user,
             date=None, extra=None, p1copies=None, p2copies=None,
             filesadded=None, filesremoved=None):
-    if b'topic-namespace' in extra and extra[b'topic-namespace'] == b'default':
+    if b'topic-namespace' in extra and extra[b'topic-namespace'] == b'none':
         extra = extra.copy()
         del extra[b'topic-namespace']
     if constants.extrakey in extra and not extra[constants.extrakey]:
         extra = extra.copy()
         del extra[constants.extrakey]
+    if constants.extrakey not in extra and b'topic-namespace' in extra:
+        # if topic is not in extra, drop namespace as well
+        extra = extra.copy()
+        del extra[b'topic-namespace']
     # hg <= 4.9 (0e41f40b01cc)
     kwargs = {}
     if p1copies is not None:
@@ -1519,14 +1673,14 @@
         if repo.ui.configbool(b'_internal', b'keep-topic'):
             ist0 = True
         if ((not partial and not branchmerge) or isrebase) and not ist0:
-            tns = b'default'
+            tns = b'none'
             t = b''
             pctx = repo[node]
             if pctx.phase() > phases.public:
                 tns = pctx.topic_namespace()
                 t = pctx.topic()
             repo.vfs.write(b'topic-namespace', tns)
-            if tns != b'default' and tns != otns:
+            if tns != b'none' and tns != otns:
                 repo.ui.status(_(b"switching to topic-namespace %s\n") % tns)
             repo.vfs.write(b'topic', t)
             if t and t != ot:
@@ -1585,22 +1739,37 @@
 
 ## preserve topic during import/export
 
+def _exporttns(seq, ctx):
+    tns = ctx.topic_namespace()
+    if tns != b'none':
+        return b'EXP-Topic-Namespace %s' % tns
+    return None
+
 def _exporttopic(seq, ctx):
     topic = ctx.topic()
     if topic:
         return b'EXP-Topic %s' % topic
     return None
 
+def _importtns(repo, patchdata, extra, opts):
+    if b'topic-namespace' in patchdata:
+        extra[b'topic-namespace'] = patchdata[b'topic-namespace']
+
 def _importtopic(repo, patchdata, extra, opts):
     if b'topic' in patchdata:
         extra[b'topic'] = patchdata[b'topic']
 
 def setupimportexport(ui):
     """run at ui setup time to install import/export logic"""
+    cmdutil.extraexport.append(b'topic-namespace')
+    cmdutil.extraexportmap[b'topic-namespace'] = _exporttns
     cmdutil.extraexport.append(b'topic')
     cmdutil.extraexportmap[b'topic'] = _exporttopic
+    cmdutil.extrapreimport.append(b'topic-namespace')
+    cmdutil.extrapreimportmap[b'topic-namespace'] = _importtns
     cmdutil.extrapreimport.append(b'topic')
     cmdutil.extrapreimportmap[b'topic'] = _importtopic
+    patch.patchheadermap.append((b'EXP-Topic-Namespace', b'topic-namespace'))
     patch.patchheadermap.append((b'EXP-Topic', b'topic'))
 
 ## preserve topic during split
@@ -1618,13 +1787,33 @@
     original(repo, ui, prev, ctx)
 
     # Restore the topic if need
-    if tns:
+    if tns != b'none':
         _changecurrenttns(repo, tns)
     if topic:
         _changecurrenttopic(repo, topic)
 
+def wrapprecheck(orig, repo, revs, action=b'rewrite', check_divergence=True):
+    # hg <= 6.1 (d4752aeb20f1)
+    args = pycompat.getargspec(orig).args
+    if r'check_divergence' in args:
+        orig(repo, revs, action, check_divergence=check_divergence)
+    else:
+        orig(repo, revs, action)
+
+    # TODO: at some point in future the default will change from '*' to the
+    # default topic namespace for the current user
+    allow = set(repo.ui.configlist(b'experimental', b'tns-allow-rewrite', [b'*']))
+    if b'*' not in allow:
+        namespaces = set(repo[rev].topic_namespace() for rev in revs)
+        disallowed = namespaces - allow
+        if disallowed:
+            msg = _(b"refusing to %s changesets with these topic namespaces: %s")
+            msg %= (action, b' '.join(disallowed))
+            hint = _(b"modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces")
+            raise compat.InputError(msg, hint=hint)
+
 def _changecurrenttns(repo, tns):
-    if tns:
+    if tns != b'none':
         with repo.wlock():
             repo.vfs.write(b'topic-namespace', tns)
     else:
@@ -1639,7 +1828,7 @@
     if opts.get('clear'):
         if tns:
             raise error.Abort(_(b"cannot use --clear when setting a topic namespace"))
-        tns = None
+        tns = b'none'
     elif not tns:
         ui.write(b'%s\n' % repo.currenttns)
         return
@@ -1652,7 +1841,7 @@
         scmutil.checknewlabel(repo, tns, b'topic namespace')
     ctns = repo.currenttns
     _changecurrenttns(repo, tns)
-    if ctns == b'default' and tns:
+    if ctns == b'none' and tns != b'none':
         repo.ui.status(_(b'marked working directory as topic namespace: %s\n')
                        % tns)
 
--- a/hgext3rd/topic/common.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/topic/common.py	Wed Oct 18 16:37:08 2023 -0300
@@ -11,21 +11,21 @@
     """parse branch//namespace/topic string into branch, namespace and topic
 
     >>> parsefqbn(b'branch//topic')
-    ('branch', 'default', 'topic')
+    ('branch', 'none', 'topic')
     >>> parsefqbn(b'//namespace/topic')
     ('default', 'namespace', 'topic')
     >>> parsefqbn(b'branch//')
-    ('branch', 'default', '')
+    ('branch', 'none', '')
     >>> parsefqbn(b'//namespace/')
     ('default', 'namespace', '')
     >>> parsefqbn(b'/topic')
-    ('/topic', 'default', '')
+    ('/topic', 'none', '')
     >>> parsefqbn(b'//topic')
-    ('default', 'default', 'topic')
+    ('default', 'none', 'topic')
     >>> parsefqbn(b'branch//namespace/topic')
     ('branch', 'namespace', 'topic')
     >>> parsefqbn(b'file:///tmp/branch//')
-    ('file:///tmp/branch', 'default', '')
+    ('file:///tmp/branch', 'none', '')
     >>> parsefqbn(b'http://example.com/branch//namespace/topic')
     ('http://example.com/branch', 'namespace', 'topic')
     """
@@ -39,7 +39,7 @@
     tns, sep, topic = other.partition(b'/')
     if not sep:
         # when there's no / in the rest of the string, there can only be topic
-        tns, topic = b'default', tns
+        tns, topic = b'none', tns
     return branch, tns, topic
 
 def formatfqbn(branch=b'', namespace=b'', topic=b'', short=True):
@@ -70,7 +70,7 @@
     """
     result = b''
     showbranch = True # branch and not (short and branch == b'default')
-    shownamespace = namespace and not (short and namespace == b'default')
+    shownamespace = namespace and not (short and namespace == b'none')
     if short and not showbranch and not shownamespace and not topic:
         # if there's nothing to show, show at least branch
         showbranch = True
--- a/hgext3rd/topic/compat.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/topic/compat.py	Wed Oct 18 16:37:08 2023 -0300
@@ -49,14 +49,29 @@
     )
 
 def overridecommitstatus(overridefn):
-    if r'tip' in cmdutil.commitstatus.__code__.co_varnames:
+    code = cmdutil.commitstatus.__code__
+    if r'opts' in code.co_varnames[code.co_argcount:]:
+        # commitstatus(..., **opts)
         extensions.wrapfunction(cmdutil, 'commitstatus', overridefn)
+    elif r'tip' in code.co_varnames:
+        # hg <= 6.5 (489268c8ee7e)
+        def _override(orig, repo, node, branch, bheads=None, tip=None, opts=None):
+            def _orig(repo, node, branch, bheads=None, tip=None, **opts):
+                opts = pycompat.byteskwargs(opts)
+                return orig(repo, node, branch, bheads=bheads, tip=tip, opts=opts)
+            opts = pycompat.strkwargs(opts)
+            return overridefn(_orig, repo, node, branch, bheads=bheads, tip=tip, **opts)
+        extensions.wrapfunction(cmdutil, 'commitstatus', _override)
     else:
         # hg <= 5.6 (976b26bdd0d8)
         def _override(orig, repo, node, branch, bheads=None, opts=None):
-            def _orig(repo, node, branch, bheads=None, tip=None, opts=None):
+            def _orig(repo, node, branch, bheads=None, **opts):
+                opts = pycompat.byteskwargs(opts)
                 return orig(repo, node, branch, bheads=bheads, opts=opts)
-            return overridefn(_orig, repo, node, branch, bheads=bheads, tip=None, opts=opts)
+            if opts is None:
+                opts = {}
+            opts = pycompat.strkwargs(opts)
+            return overridefn(_orig, repo, node, branch, bheads=bheads, **opts)
         extensions.wrapfunction(cmdutil, 'commitstatus', _override)
 
 if util.safehasattr(error, 'InputError'):
--- a/hgext3rd/topic/discovery.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/topic/discovery.py	Wed Oct 18 16:37:08 2023 -0300
@@ -12,6 +12,7 @@
     encoding,
     exchange,
     extensions,
+    hg,
     scmutil,
     util,
     wireprototypes,
@@ -183,9 +184,9 @@
     """copied from wireprotov1server.branchmap()"""
     if not common.hastopicext(repo):
         return wireprotov1server.branchmap(repo, proto)
-    branchmaptns = repo.branchmaptns()
     heads = []
-    for branch, nodes in branchmaptns.items():
+    entries = compat.bcentries(repo.branchmaptns())
+    for branch, nodes in compat.branchmapitems(entries):
         branchname = urlreq.quote(encoding.fromlocal(branch))
         branchnodes = wireprototypes.encodelist(nodes)
         heads.append(b'%s %s' % (branchname, branchnodes))
@@ -248,7 +249,7 @@
     data = {}
     for b in repo.branchmap().iterbranches():
         namedbranch, tns, topic = common.parsefqbn(b[0])
-        if tns != b'default' or topic:
+        if tns != b'none' or topic:
             continue
         oldheads = [repo[n].rev() for n in b[1]]
         newheads = filterfn(repo, oldheads)
@@ -360,6 +361,24 @@
     with repo.ui.configoverride(overrides, b'topic-namespaces'):
         orig(repo, bundler, outgoing)
 
+def wraphgpeer(orig, uiorrepo, opts, *args, **kwargs):
+    """hg.peer() that checks if there are explicit arguments for e.g. pull"""
+    peer = orig(uiorrepo, opts, *args, **kwargs)
+    if any(opts.get(k) for k in (b'rev', b'bookmark', b'branch')):
+        peer.ui.setconfig(b'_internal', b'tns-explicit-target', True, b'topic-namespaces')
+    return peer
+
+def wraphgremoteui(orig, src, opts):
+    """hg.remoteui() that copies tns-related config options to peer ui"""
+    dst = orig(src, opts)
+    if util.safehasattr(src, 'baseui'):  # looks like a repository
+        src = src.ui
+    # we need to copy topic namespaces from local config to peer ui, since it
+    # needs to be accessible for peer command executors
+    namespaces = src.configlist(b'experimental', b'tns-default-pull-namespaces', [b'*'])
+    dst.setconfig(b'experimental', b'tns-default-pull-namespaces', namespaces, b'copied')
+    return dst
+
 def modsetup(ui):
     """run at uisetup time to install all destinations wrapping"""
     extensions.wrapfunction(discovery, '_headssummary', _headssummary)
@@ -385,3 +404,5 @@
     extensions.wrapfunction(bundle2, 'addpartrevbranchcache', wrapaddpartrevbranchcache)
     extensions.wrapfunction(exchange, '_pushb2phases', _pushb2phases)
     exchange.b2partsgenmapping[b'phase'] = exchange._pushb2phases
+    extensions.wrapfunction(hg, 'peer', wraphgpeer)
+    extensions.wrapfunction(hg, 'remoteui', wraphgremoteui)
--- a/hgext3rd/topic/revset.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/topic/revset.py	Wed Oct 18 16:37:08 2023 -0300
@@ -25,7 +25,7 @@
 def topicnamespaceset(repo, subset, x):
     """All changesets with the specified topic namespace or the topic
     namespaces of the given changesets. Without the argument, all changesets
-    with any non-default topic namespace.
+    with any non-empty topic namespace.
 
     Pattern matching is supported for `string`. See
     :hg:`help revisions.patterns`.
@@ -35,7 +35,7 @@
     mutable = revset._notpublic(repo, revset.fullreposet(repo), ())
 
     if not args:
-        return (subset & mutable).filter(lambda r: repo[r].topic_namespace() != b'default')
+        return (subset & mutable).filter(lambda r: repo[r].topic_namespace() != b'none')
 
     try:
         tns = getstringstrict(args[0], b'')
@@ -50,7 +50,7 @@
 
         def matches(r):
             tns = repo[r].topic_namespace()
-            if tns == b'default':
+            if tns == b'none':
                 return False
             return matcher(tns)
 
@@ -58,11 +58,11 @@
 
     s = revset.getset(repo, revset.fullreposet(repo), x)
     namespaces = {repo[r].topic_namespace() for r in s}
-    namespaces.discard(b'default')
+    namespaces.discard(b'none')
 
     def matches(r):
         tns = repo[r].topic_namespace()
-        if tns == b'default':
+        if tns == b'none':
             return False
         return tns in namespaces
 
--- a/hgext3rd/topic/server.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/topic/server.py	Wed Oct 18 16:37:08 2023 -0300
@@ -2,10 +2,15 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
+from mercurial.i18n import _
+
 from mercurial import (
     branchmap,
+    error,
     extensions,
+    localrepo,
     repoview,
+    util,
     wireprototypes,
     wireprotov1peer,
     wireprotov1server,
@@ -13,6 +18,7 @@
 
 from . import (
     common,
+    compat,
     constants,
 )
 
@@ -58,6 +64,25 @@
     h = repo.heads()
     return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + b'\n')
 
+def tns_heads(repo, proto, namespaces):
+    """wireprotocol command to filter heads based on topic namespaces"""
+    if not common.hastopicext(repo):
+        return topicheads(repo, proto)
+
+    namespaces = wireprototypes.decodelist(namespaces)
+    if b'*' in namespaces:
+        # pulling all topic namespaces, all changesets are visible
+        h = repo.heads()
+    else:
+        # only changesets in the selected topic namespaces are visible
+        h = []
+        entries = compat.bcentries(repo.branchmaptns())
+        for branch, nodes in compat.branchmapitems(entries):
+            namedbranch, tns, topic = common.parsefqbn(branch)
+            if tns == b'none' or tns in namespaces:
+                h.extend(nodes)
+    return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + b'\n')
+
 def wireprotocaps(orig, repo, proto):
     """advertise the new topic specific `head` command for client with topic"""
     caps = orig(repo, proto)
@@ -70,6 +95,7 @@
         else:
             mode = b'none'
         caps.append(b'ext-topics-publish=%s' % mode)
+        caps.append(b'ext-topics-tns-heads')
     return caps
 
 def setupserver(ui):
@@ -77,13 +103,48 @@
     wireprotov1server.commands.pop(b'heads')
     wireprotov1server.wireprotocommand(b'heads', permission=b'pull')(wireprotov1server.heads)
     wireprotov1server.wireprotocommand(b'_exttopics_heads', permission=b'pull')(topicheads)
+    wireprotov1server.wireprotocommand(b'tns_heads', b'namespaces', permission=b'pull')(tns_heads)
     extensions.wrapfunction(wireprotov1server, '_capabilities', wireprotocaps)
 
+    if util.safehasattr(wireprotov1peer, 'future'):
+        # hg <= 5.9 (c424ff4807e6)
+        class tnspeer(wireprotov1peer.wirepeer):
+            """ wirepeer that uses `future` class from before c424ff4807e6 """
+            @wireprotov1peer.batchable
+            def tns_heads(self, namespaces):
+                f = wireprotov1peer.future()
+                yield {b'namespaces': wireprototypes.encodelist(namespaces)}, f
+                d = f.value
+                try:
+                    yield wireprototypes.decodelist(d[:-1])
+                except ValueError:
+                    self._abort(error.ResponseError(_(b"unexpected response:"), d))
+    else:
+        class tnspeer(wireprotov1peer.wirepeer):
+            """ wirepeer that uses newer batchable scheme from c424ff4807e6 """
+            @wireprotov1peer.batchable
+            def tns_heads(self, namespaces):
+                def decode(d):
+                    try:
+                        return wireprototypes.decodelist(d[:-1])
+                    except ValueError:
+                        self._abort(error.ResponseError(_(b"unexpected response:"), d))
+
+                return {b'namespaces': wireprototypes.encodelist(namespaces)}, decode
+
+    wireprotov1peer.wirepeer = tnspeer
+
     class topicpeerexecutor(wireprotov1peer.peerexecutor):
 
         def callcommand(self, command, args):
             if command == b'heads':
-                if self._peer.capable(b'_exttopics_heads'):
+                if self._peer.capable(b'ext-topics-tns-heads'):
+                    command = b'tns_heads'
+                    if self._peer.ui.configbool(b'_internal', b'tns-explicit-target', False):
+                        args[b'namespaces'] = [b'*']
+                    else:
+                        args[b'namespaces'] = self._peer.ui.configlist(b'experimental', b'tns-default-pull-namespaces', [b'*'])
+                elif self._peer.capable(b'_exttopics_heads'):
                     command = b'_exttopics_heads'
                     if getattr(self._peer, '_exttopics_heads', None) is None:
                         self._peer._exttopics_heads = self._peer.heads
@@ -92,6 +153,20 @@
 
     wireprotov1peer.peerexecutor = topicpeerexecutor
 
+    class topiccommandexecutor(localrepo.localcommandexecutor):
+        def callcommand(self, command, args):
+            if command == b'heads':
+                if self._peer.capable(b'ext-topics-tns-heads'):
+                    command = b'tns_heads'
+                    if self._peer.ui.configbool(b'_internal', b'tns-explicit-target', False):
+                        args[b'namespaces'] = [b'*']
+                    else:
+                        args[b'namespaces'] = self._peer.ui.configlist(b'experimental', b'tns-default-pull-namespaces', [b'*'])
+            s = super(topiccommandexecutor, self)
+            return s.callcommand(command, args)
+
+    localrepo.localcommandexecutor = topiccommandexecutor
+
     if FILTERNAME not in repoview.filtertable:
         repoview.filtertable[FILTERNAME] = computeunservedtopic
         # hg <= 4.9 (caebe5e7f4bd)
--- a/hgext3rd/topic/topicmap.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/hgext3rd/topic/topicmap.py	Wed Oct 18 16:37:08 2023 -0300
@@ -97,7 +97,7 @@
     other = repo.filtered(topicfilter(repo.filtername))
     return orig(self, other, *args, **kwargs)
 
-def commitstatus(orig, repo, node, branch, bheads=None, tip=None, opts=None):
+def commitstatus(orig, repo, node, branch, bheads=None, tip=None, **opts):
     # wrap commit status use the topic branch heads
     ctx = repo[node]
     ctxbranch = common.formatfqbn(branch=ctx.branch())
@@ -105,16 +105,14 @@
         bheads = repo.branchheads(b"%s:%s" % (branch, ctx.topic()))
 
     with discovery.override_context_branch(repo) as repo:
-        ret = orig(repo, node, branch, bheads=bheads, tip=tip, opts=opts)
+        ret = orig(repo, node, branch, bheads=bheads, tip=tip, **opts)
 
     # logic copy-pasted from cmdutil.commitstatus()
-    if opts is None:
-        opts = {}
     if ctx.topic():
         return ret
     parents = ctx.parents()
 
-    if (not opts.get(b'amend') and bheads and node not in bheads and not any(
+    if (not opts.get('amend') and bheads and node not in bheads and not any(
         p.node() in bheads and common.formatfqbn(branch=p.branch()) == branch
         for p in parents
     )):
--- a/setup.py	Wed Aug 16 15:11:43 2023 -0300
+++ b/setup.py	Wed Oct 18 16:37:08 2023 -0300
@@ -18,9 +18,6 @@
     '''Read version info from a file without importing it'''
     return get_metadata()['minimumhgversion']
 
-py_modules = [
-    'hgext3rd.serverminitopic',
-]
 py_packages = [
     'hgext3rd',
     'hgext3rd.evolve',
@@ -45,7 +42,6 @@
     long_description=open(join(dirname(__file__), 'README.rst')).read(),
     keywords='hg mercurial',
     license='GPLv2+',
-    py_modules=py_modules,
     packages=py_packages,
     package_dir=py_packagedir,
     python_requires=py_versions
--- a/tests/test-cache-corruption.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-cache-corruption.t	Wed Oct 18 16:37:08 2023 -0300
@@ -152,7 +152,7 @@
   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
+  $ hg debug::evo-ext-stable-sort-cache --debug
   number of revisions:            3
   number of merge:                0
   number of jumps:                0
@@ -164,7 +164,7 @@
   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
+  $ hg debug::evo-ext-stable-sort-cache --debug
   number of revisions:            3
   stablesortcache file seems to be corrupted, it will be rebuilt from scratch
   number of merge:                0
@@ -177,7 +177,7 @@
   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
+  $ hg debug::evo-ext-stable-sort-cache --debug
   number of revisions:            3
   stablesortcache file seems to be corrupted, it will be rebuilt from scratch
   number of merge:                0
@@ -305,7 +305,7 @@
   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
+  $ hg debug::evo-ext-stable-sort-cache --debug
   number of revisions:            4
   number of merge:                1
   number of jumps:                1
@@ -324,7 +324,7 @@
   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
+  $ hg debug::evo-ext-stable-sort-cache --debug
   number of revisions:            4
   stablesortcache file seems to be corrupted, it will be rebuilt from scratch
   number of merge:                1
@@ -345,7 +345,7 @@
   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
+  $ hg debug::evo-ext-stable-sort-cache --debug
   number of revisions:            4
   stablesortcache file seems to be corrupted, it will be rebuilt from scratch
   number of merge:                1
--- a/tests/test-check-sdist.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-check-sdist.t	Wed Oct 18 16:37:08 2023 -0300
@@ -34,8 +34,12 @@
   hg-evolve-*.tar.gz (glob)
 
   $ tar -tzf hg-evolve-*.tar.gz | sed 's|^hg-evolve-[^/]*/||' | sort > files
-  $ wc -l files
-  362 files
+  $ egrep '^tests/test-.*\.(t|py)$' files > test-files
+  $ egrep -v '^tests/test-.*\.(t|py)$' files > other-files
+  $ wc -l other-files
+  148 other-files
+  $ wc -l test-files
+  ??? test-files (glob)
   $ fgrep debian files
   tests/test-check-debian.t
   $ fgrep __init__.py files
--- a/tests/test-check-tag.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-check-tag.t	Wed Oct 18 16:37:08 2023 -0300
@@ -23,7 +23,6 @@
   >   fi
   >   # Here we skip:
   >   # - pullbundle because it usually has no changes (so no version bump)
-  >   # - serverminitopic because it's not actively maintained
   >   if hg grep --rev $node '^__version__ = .*\.dev' hgext3rd/evolve/ hgext3rd/topic/; then
   >     echo "Versions should not end with .dev at tagged revision $node"
   >   fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-evolve-extras.t	Wed Oct 18 16:37:08 2023 -0300
@@ -0,0 +1,42 @@
+Testing retained_extras_on_rebase usage in evolve and modifying it in an extension
+
+  $ . $TESTDIR/testlib/common.sh
+
+  $ hg init repo
+  $ cd repo
+  $ cat > .hg/hgrc << EOF
+  > [extensions]
+  > evolve =
+  > EOF
+
+  $ echo apple > a
+  $ hg ci -qAm 'apple'
+  $ echo banana > b
+  $ hg ci -qAm 'banana' --config extensions.commitextras= \
+  > --extra useful=b-for-banana \
+  > --extra useless=banana-peel
+
+amending apple
+
+  $ hg prev
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [0] apple
+  $ echo apricot > a
+  $ hg amend -m 'apricot'
+  1 new orphan changesets
+
+the commit still has all extras that we added previously
+
+  $ hg log -r 'desc("banana")' -T '{join(extras, " ")}\n'
+  *useful=b-for-banana*useless=banana-peel* (glob)
+
+let's run evolve with our extension
+
+  $ hg --config extensions.retained_extras=${TESTDIR}/testlib/retain-extras-ext.py evolve
+  move:[1] banana
+  atop:[2] apricot
+
+evolving banana retained "useful" and discarded "useless"
+
+  $ hg log -r 'desc("banana")' -T '{join(extras, " ")}\n'
+  *useful=b-for-banana* (glob)
--- a/tests/test-evolve-issue6246.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-evolve-issue6246.t	Wed Oct 18 16:37:08 2023 -0300
@@ -17,7 +17,7 @@
   $ touch .hg/cache/evoext_stablerange_v2.sqlite
   $ chmod 0000 .hg/cache/evoext_stablerange_v2.sqlite
 
-  $ hg debugstablerange --method default --verify --subranges --rev 1 --debug
+  $ hg debug::evo-ext-stable-range --method default --verify --subranges --rev 1 --debug
   stable-range cache: unable to load, regenerating
   66f7d451a68b-0 (1, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), 66f7d451a68b-1 (1, 2, 1)
   1ea73414a91b-0 (0, 1, 1) [leaf] - 
--- a/tests/test-evolve-serveronly-bundle2.t	Wed Aug 16 15:11:43 2023 -0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-
-  $ . ${TESTDIR}/testlib/pythonpath.sh
-
-  $ cat >> $HGRCPATH <<EOF
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [ui]
-  > ssh = "$PYTHON" "$RUNTESTDIR/dummyssh"
-  > [phases]
-  > publish = False
-  > [experimental]
-  > bundle2-exp=True
-  > EOF
-
-  $ mkcommit() {
-  >    echo "$1" > "$1"
-  >    hg add "$1"
-  >    hg ci -m "add $1"
-  > }
-
-  $ hg init server
-
-Try the multiple ways to setup the extension
-
-  $ hg -R server log --config 'extensions.evolve.serveronly='
-  $ hg -R server log --config "extensions.evolve.serveronly=${SRCDIR}/hgext3rd/evolve/serveronly.py"
-  $ PYTHONPATH=$HGTEST_ORIG_PYTHONPATH hg -R server log --config "extensions.evolve.serveronly=${SRCDIR}/hgext3rd/evolve/serveronly.py"
-
-setup repo
-
-  $ echo "[extensions]" >> ./server/.hg/hgrc
-  $ echo "evolve.serveronly=" >> ./server/.hg/hgrc
-  $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
-  $ cat hg.pid >> $DAEMON_PIDS
-
-  $ hg clone http://localhost:$HGPORT/ client
-  no changes found
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cat ./errors.log
-  $ echo "[extensions]" >> ./client/.hg/hgrc
-  $ echo "evolve=" >> ./client/.hg/hgrc
-  $ echo "[paths]" >> ./client/.hg/hgrc
-  $ echo "ssh=ssh://user@dummy/server/" >> ./client/.hg/hgrc
-  $ cp -r client other
-
-Smoke testing
-===============
-
-  $ cd client
-  $ mkcommit 0
-  $ mkcommit a
-  $ hg push
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 2 changesets with 2 changes to 2 files
-  $ hg pull
-  pulling from http://localhost:$HGPORT/
-  searching for changes
-  no changes found
-  $ cat ../errors.log
-  $ hg pull -R ../other
-  pulling from http://localhost:$HGPORT/
-  requesting all changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 2 changes to 2 files
-  new changesets 8685c6d34325:4957bfdac07e (2 drafts)
-  (run 'hg update' to get a working copy)
-  $ cat ../errors.log
-  $ hg push -R ../other
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  no changes found
-  [1]
-  $ cat ../errors.log
-
-Capacity testing
-===================
-
-  $ hg debugdownload http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch * (glob)
-  $ hg debugdownload http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch * (glob)
-
-  $ hg debugpushkey http://localhost:$HGPORT namespaces
-  bookmarks	
-  namespaces	
-  obsolete	
-  phases	
-
-Push
-=============
-
-  $ echo 'A' > a
-  $ hg amend
-  $ hg push
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 1 changesets with 1 changes to 1 files (+1 heads)
-  remote: 1 new obsolescence markers
-  remote: obsoleted 1 changesets
-  $ cat ../errors.log
-  $ hg push
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  no changes found
-  [1]
-  $ cat ../errors.log
-
-Pull
-=============
-
-  $ hg -R ../other pull
-  pulling from http://localhost:$HGPORT/
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 1 changes to [12] files \(\+1 heads\) (re)
-  1 new obsolescence markers
-  obsoleted 1 changesets
-  new changesets 9d1c114e7797 (1 drafts)
-  (run 'hg heads' to see heads)
-  $ cat ../errors.log
-  $ hg -R ../other pull
-  pulling from http://localhost:$HGPORT/
-  searching for changes
-  no changes found
-  $ cat ../errors.log
-
-  $ cd ..
-
-Test disabling obsolete advertisement
-===========================================
-(used by bitbucket to select which repo use evolve)
-
-  $ hg debugpushkey http://localhost:$HGPORT namespaces
-  bookmarks	
-  namespaces	
-  obsolete	
-  phases	
-  $ hg debugdownload http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch * (glob)
-  $ hg debugdownload http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch * (glob)
-
-  $ echo '[experimental]' >> server/.hg/hgrc
-  $ echo 'evolution=!' >> server/.hg/hgrc
-  $ $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
-  $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
-  $ cat hg.pid >> $DAEMON_PIDS
-
-  $ hg debugpushkey http://localhost:$HGPORT namespaces
-  bookmarks	
-  namespaces	
-  phases	
-
-  $ echo 'evolution=all' >> server/.hg/hgrc
-  $ $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
-  $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
-  $ cat hg.pid >> $DAEMON_PIDS
-
-  $ hg debugpushkey http://localhost:$HGPORT namespaces
-  bookmarks	
-  namespaces	
-  obsolete	
-  phases	
-
-  $ hg debugdownload http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch * (glob)
-  $ hg debugdownload http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch * (glob)
-
-Test obshashrange discover
-===========================================
-
-  $ cat >> $HGRCPATH <<EOF
-  > [experimental]
-  > obshashrange = True
-  > EOF
-  $ cd client
-  $ hg pull ssh
-  pulling from ssh://user@dummy/server/
-  searching for changes
-  no changes found
--- a/tests/test-evolve-serveronly-legacy.t	Wed Aug 16 15:11:43 2023 -0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-
-  $ . ${TESTDIR}/testlib/pythonpath.sh
-
-  $ cat >> $HGRCPATH <<EOF
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
-  > [experimental]
-  > bundle2-exp=False # < Mercurial-4.0
-  > [devel]
-  > legacy.exchange=bundle1
-  > EOF
-
-  $ mkcommit() {
-  >    echo "$1" > "$1"
-  >    hg add "$1"
-  >    hg ci -m "add $1"
-  > }
-
-
-  $ hg init server
-
-Try the multiple ways to setup the extension
-
-  $ hg -R server log --config 'extensions.evolve.serveronly='
-  $ hg -R server log --config "extensions.evolve.serveronly=${SRCDIR}/hgext3rd/evolve/serveronly.py"
-  $ PYTHONPATH=$HGTEST_ORIG_PYTHONPATH hg -R server log --config "extensions.evolve.serveronly=${SRCDIR}/hgext3rd/evolve/serveronly.py"
-
-setup repo
-
-  $ echo "[extensions]" >> ./server/.hg/hgrc
-  $ echo "evolve.serveronly=" >> ./server/.hg/hgrc
-  $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log --traceback
-  $ cat hg.pid >> $DAEMON_PIDS
-
-  $ hg clone http://localhost:$HGPORT/ client
-  no changes found
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cat ./errors.log
-  $ echo "[extensions]" >> ./client/.hg/hgrc
-  $ echo "evolve=" >> ./client/.hg/hgrc
-  $ cp -r client other
-
-Smoke testing
-===============
-
-  $ cd client
-  $ mkcommit 0
-  $ mkcommit a
-  $ hg push
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  abort: remote error:
-  incompatible Mercurial client; bundle2 required
-  (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
-  [100]
-  $ cat ../errors.log
--- a/tests/test-extension-isolation.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-extension-isolation.t	Wed Oct 18 16:37:08 2023 -0300
@@ -133,6 +133,7 @@
   $ hg debugcapabilities http://$LOCALIP:$HGPORT/repo-topic | egrep 'topics|evoext'
     _exttopics_heads
     ext-topics-publish=all
+    ext-topics-tns-heads
     topics
     topics-namespaces
   $ hg debugcapabilities http://$LOCALIP:$HGPORT/repo-no-ext | egrep 'topics|evoext'
@@ -148,6 +149,7 @@
     _evoext_obshashrange_v1
     _exttopics_heads
     ext-topics-publish=all
+    ext-topics-tns-heads
     topics
     topics-namespaces
   $ hg debugcapabilities http://$LOCALIP:$HGPORT/repo-evo | egrep 'topics|evoext'
@@ -156,6 +158,7 @@
   $ hg debugcapabilities http://$LOCALIP:$HGPORT/repo-topic | egrep 'topics|evoext'
     _exttopics_heads
     ext-topics-publish=all
+    ext-topics-tns-heads
     topics
     topics-namespaces
   $ hg debugcapabilities http://$LOCALIP:$HGPORT/repo-evo | egrep 'topics|evoext'
--- a/tests/test-minitopic.t	Wed Aug 16 15:11:43 2023 -0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,240 +0,0 @@
-  $ . $TESTDIR/testlib/common.sh
-
-setup
-  $ cat >> $HGRCPATH << EOF
-  > [extensions]
-  > share=
-  > blackbox=
-  > [web]
-  > allow_push = *
-  > push_ssl = no
-  > [phases]
-  > publish = False
-  > [paths]
-  > enabled = http://localhost:$HGPORT/
-  > disabled = http://localhost:$HGPORT2/
-  > EOF
-
-  $ hg init ./server-enabled
-  $ cat >> server-enabled/.hg/hgrc << EOF
-  > [extensions]
-  > serverminitopic=
-  > [experimental]
-  > server-mini-topic = yes
-  > EOF
-
-  $ hg share ./server-enabled ./server-disabled
-  updating working directory
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cat >> server-disabled/.hg/hgrc << EOF
-  > [extensions]
-  > serverminitopic=
-  > [experimental]
-  > server-mini-topic = no
-  > EOF
-
-  $ hg init client-disabled
-  $ hg init client-enabled
-  $ cat >> client-enabled/.hg/hgrc << EOF
-  > [extensions]
-  > topic=
-  > EOF
-
-  $ hg serve -R server-enabled -p $HGPORT -d --pid-file hg1.pid --errorlog hg1.error
-  $ cat hg1.pid > $DAEMON_PIDS
-  $ hg serve -R server-disabled -p $HGPORT2 -d --pid-file hg2.pid --errorlog hg2.error
-  $ cat hg2.pid >> $DAEMON_PIDS
-
-  $ hg debugdownload http://localhost:$HGPORT/?cmd=capabilities | grep -o topics
-  topics
-  $ hg debugdownload http://localhost:$HGPORT2/?cmd=capabilities | grep -o topics
-  [1]
-
-Pushing first changesets to the servers
---------------------------------------
-
-  $ cd client-enabled
-  $ mkcommit c_A0
-  $ hg push enabled
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 1 changesets with 1 changes to 1 files
-  $ mkcommit c_B0
-  $ hg push disabled
-  pushing to http://localhost:$HGPORT2/
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 1 changesets with 1 changes to 1 files
-
-  $ cat $TESTTMP/hg1.error
-  $ cat $TESTTMP/hg2.error
-
-Pushing new head
-----------------
-
-  $ hg up 'desc("c_A0")'
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ mkcommit c_C0
-  created new head
-  (consider using topic for lightweight branches. See 'hg help topic')
-  $ hg push enabled
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  abort: push creates new remote head 22c9514ed811
-  (merge or see 'hg help push' for details about pushing new heads)
-  [20]
-  $ hg push disabled
-  pushing to http://localhost:$HGPORT2/
-  searching for changes
-  abort: push creates new remote head 22c9514ed811
-  (merge or see 'hg help push' for details about pushing new heads)
-  [20]
-
-  $ hg debugdownload http://localhost:$HGPORT/?cmd=branchmap | sort
-  default 0ab6d544d0efd629fda056601cfe95e73d1af210
-  $ hg debugdownload http://localhost:$HGPORT2/?cmd=branchmap | sort
-  default 0ab6d544d0efd629fda056601cfe95e73d1af210
-  $ cat $TESTTMP/hg1.error
-  $ cat $TESTTMP/hg2.error
-
-Pushing new topic
------------------
-
-  $ hg merge
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ mkcommit c_D0
-  $ hg log -G
-  @    changeset:   3:9c660cf97499
-  |\   tag:         tip
-  | |  parent:      2:22c9514ed811
-  | |  parent:      1:0ab6d544d0ef
-  | |  user:        test
-  | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  summary:     c_D0
-  | |
-  | o  changeset:   2:22c9514ed811
-  | |  parent:      0:14faebcf9752
-  | |  user:        test
-  | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  summary:     c_C0
-  | |
-  o |  changeset:   1:0ab6d544d0ef
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     c_B0
-  |
-  o  changeset:   0:14faebcf9752
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     c_A0
-  
-  $ hg push enabled
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 2 changesets with 2 changes to 2 files
-  $ hg up 'desc("c_C0")'
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg topic topic_A
-  marked working directory as topic: topic_A
-  $ mkcommit c_E0
-  active topic 'topic_A' grew its first changeset
-  (see 'hg help topics' for more information)
-  $ hg push disabled
-  pushing to http://localhost:$HGPORT2/
-  searching for changes
-  abort: push creates new remote head f31af349535e
-  (merge or see 'hg help push' for details about pushing new heads)
-  [20]
-  $ hg push enabled
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 1 changesets with 1 changes to 1 files (+1 heads)
-
-  $ hg debugdownload http://localhost:$HGPORT/?cmd=branchmap | sort
-  default 9c660cf97499ae01ccb6894880455c6ffa4b19cf
-  default%3Atopic_A f31af349535e413b6023f11b51a6afccf4139180
-  $ hg debugdownload http://localhost:$HGPORT2/?cmd=branchmap | sort
-  default 9c660cf97499ae01ccb6894880455c6ffa4b19cf f31af349535e413b6023f11b51a6afccf4139180
-  $ cat $TESTTMP/hg1.error
-  $ cat $TESTTMP/hg2.error
-
-Pushing new head to a topic
----------------------------
-
-  $ hg up 'desc("c_D0")'
-  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg topic topic_A
-  marked working directory as topic: topic_A
-  $ mkcommit c_F0
-  $ hg log -G
-  @  changeset:   5:82c5842e0472
-  |  tag:         tip
-  |  topic:       topic_A
-  |  parent:      3:9c660cf97499
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     c_F0
-  |
-  | o  changeset:   4:f31af349535e
-  | |  topic:       topic_A
-  | |  parent:      2:22c9514ed811
-  | |  user:        test
-  | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  summary:     c_E0
-  | |
-  o |  changeset:   3:9c660cf97499
-  |\|  parent:      2:22c9514ed811
-  | |  parent:      1:0ab6d544d0ef
-  | |  user:        test
-  | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  summary:     c_D0
-  | |
-  | o  changeset:   2:22c9514ed811
-  | |  parent:      0:14faebcf9752
-  | |  user:        test
-  | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  summary:     c_C0
-  | |
-  o |  changeset:   1:0ab6d544d0ef
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     c_B0
-  |
-  o  changeset:   0:14faebcf9752
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     c_A0
-  
-  $ hg push enabled
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  abort: push creates new remote head 82c5842e0472 on branch 'default//topic_A'
-  (merge or see 'hg help push' for details about pushing new heads)
-  [20]
-  $ hg push disabled
-  pushing to http://localhost:$HGPORT2/
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 1 changesets with 1 changes to 1 files
-
-  $ hg debugdownload http://localhost:$HGPORT/?cmd=branchmap | sort
-  default 9c660cf97499ae01ccb6894880455c6ffa4b19cf
-  default%3Atopic_A f31af349535e413b6023f11b51a6afccf4139180 82c5842e047215160763f81ae93ae42c65b20a63
-  $ hg debugdownload http://localhost:$HGPORT2/?cmd=branchmap | sort
-  default f31af349535e413b6023f11b51a6afccf4139180 82c5842e047215160763f81ae93ae42c65b20a63
-  $ cat $TESTTMP/hg1.error
-  $ cat $TESTTMP/hg2.error
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-namespaces-exchange.t	Wed Oct 18 16:37:08 2023 -0300
@@ -0,0 +1,248 @@
+Limiting topic namespaces during exchange based on a config option
+
+  $ . "$TESTDIR/testlib/common.sh"
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > topic =
+  > [phases]
+  > publish = no
+  > [ui]
+  > ssh = "$PYTHON" "$RUNTESTDIR/dummyssh"
+  > [devel]
+  > tns-report-transactions = pull
+  > [ui]
+  > logtemplate = "{rev}: {desc} {fqbn} ({phase})\n"
+  > EOF
+
+  $ hg init orig
+
+#testcases local ssh http
+
+#if http
+  $ hg serve -R orig -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
+  $ cat hg.pid >> $DAEMON_PIDS
+#endif
+
+we advertise the new capability, including during local exchange
+
+#if local
+  $ hg debugcapabilities orig | grep topics
+    ext-topics-publish=none
+    ext-topics-tns-heads
+    topics
+    topics-namespaces
+#endif
+#if ssh
+  $ hg debugcapabilities ssh://user@dummy/orig | grep topics
+    _exttopics_heads
+    ext-topics-publish=none
+    ext-topics-tns-heads
+    topics
+    topics-namespaces
+#endif
+#if http
+  $ hg debugcapabilities http://localhost:$HGPORT/ | grep topics
+    _exttopics_heads
+    ext-topics-publish=none
+    ext-topics-tns-heads
+    topics
+    topics-namespaces
+#endif
+
+#if local
+  $ hg clone orig clone -q
+#endif
+#if ssh
+  $ hg clone ssh://user@dummy/orig clone -q
+#endif
+#if http
+  $ hg clone http://localhost:$HGPORT/ clone -q
+#endif
+
+  $ cd orig
+
+changesets without topic namespace are freely exchanged
+
+  $ echo apple > a
+  $ hg debug-topic-namespace --clear
+  $ hg topic apple
+  marked working directory as topic: apple
+  $ hg ci -qAm apple
+
+  $ hg incoming -R ../clone
+  comparing with * (glob)
+  0: apple default//apple (draft)
+
+  $ hg pull -R ../clone
+  pulling from * (glob)
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  new changesets bf4c1d971543 (1 drafts)
+  (run 'hg update' to get a working copy)
+
+changesets with topic namespaces are only exchanged if configuration allows
+
+  $ echo banana > b
+  $ hg debug-topic-namespace bob
+  marked working directory as topic namespace: bob
+  $ hg topic banana
+  $ hg ci -qAm 'banana'
+
+  $ hg incoming -R ../clone --config experimental.tns-default-pull-namespaces=foo
+  comparing with * (glob)
+  searching for changes
+  no changes found
+  [1]
+
+  $ hg pull -R ../clone --config experimental.tns-default-pull-namespaces=foo
+  pulling from * (glob)
+  searching for changes
+  no changes found
+
+this config option takes a list of values
+
+  $ hg incoming -R ../clone --config experimental.tns-default-pull-namespaces=foo,bob
+  comparing with * (glob)
+  searching for changes
+  1: banana default//bob/banana (draft)
+
+  $ hg pull -R ../clone --config experimental.tns-default-pull-namespaces=foo,bob
+  pulling from * (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: bob
+  added 1 changesets with 1 changes to 1 files
+  new changesets ed9751f04a18 (1 drafts)
+  (run 'hg update' to get a working copy)
+
+we have a "permit all" config value
+
+  $ echo coconut > c
+  $ hg debug-topic-namespace charlie
+  $ hg topic coconut
+  $ hg ci -qAm 'coconut'
+
+  $ hg incoming -R ../clone --config experimental.tns-default-pull-namespaces=*
+  comparing with * (glob)
+  searching for changes
+  2: coconut default//charlie/coconut (draft)
+
+  $ hg pull -R ../clone --config experimental.tns-default-pull-namespaces=*
+  pulling from * (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: charlie
+  added 1 changesets with 1 changes to 1 files
+  new changesets 16d2440597e2 (1 drafts)
+  (run 'hg update' to get a working copy)
+
+testing the default value for this config option at the moment
+
+  $ echo durian > d
+  $ hg debug-topic-namespace dave
+  $ hg topic durian
+  $ hg ci -qAm 'durian'
+
+  $ hg incoming -R ../clone
+  comparing with * (glob)
+  searching for changes
+  3: durian default//dave/durian (draft)
+
+  $ hg pull -R ../clone
+  pulling from * (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: dave
+  added 1 changesets with 1 changes to 1 files
+  new changesets d5d5dda52b2f (1 drafts)
+  (run 'hg update' to get a working copy)
+
+testing related config options
+also specifying changesets and branches explicitly
+
+  $ echo elderberry > e
+  $ hg debug-topic-namespace eve
+  $ hg topic elderberry
+  $ hg ci -qAm 'elderberry'
+
+  $ echo feijoa > f
+  $ hg debug-topic-namespace frank
+  $ hg topic feijoa
+  $ hg ci -qAm 'feijoa'
+
+global hgrc
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > tns-default-pull-namespaces = alice, bob, eve
+  > EOF
+
+  $ hg incoming -R ../clone
+  comparing with * (glob)
+  searching for changes
+  4: elderberry default//eve/elderberry (draft)
+
+global hgrc with explicit target
+
+  $ hg incoming -R ../clone --rev tip
+  comparing with * (glob)
+  searching for changes
+  4: elderberry default//eve/elderberry (draft)
+  5: feijoa default//frank/feijoa (draft)
+
+source repo hgrc (should not matter)
+
+  $ cat >> ../orig/.hg/hgrc << EOF
+  > [experimental]
+  > tns-default-pull-namespaces = does, not, matter
+  > EOF
+
+  $ hg incoming -R ../clone
+  comparing with * (glob)
+  searching for changes
+  4: elderberry default//eve/elderberry (draft)
+
+local repo hgrc (overrides global hgrc)
+
+  $ cat >> ../clone/.hg/hgrc << EOF
+  > [experimental]
+  > tns-default-pull-namespaces = frank
+  > EOF
+
+  $ hg incoming -R ../clone
+  comparing with * (glob)
+  searching for changes
+  4: elderberry default//eve/elderberry (draft)
+  5: feijoa default//frank/feijoa (draft)
+
+local repo hgrc with explicit target
+
+  $ hg incoming -R ../clone --rev 4
+  comparing with * (glob)
+  searching for changes
+  4: elderberry default//eve/elderberry (draft)
+
+#if http
+  $ $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+  $ cat $TESTTMP/errors.log
+#endif
+
+  $ hg branches
+  default//frank/feijoa          5:c58726fdcfd8
+  default//eve/elderberry        4:59694f5082fe (inactive)
+  default//dave/durian           3:d5d5dda52b2f (inactive)
+  default//charlie/coconut       2:16d2440597e2 (inactive)
+  default//bob/banana            1:ed9751f04a18 (inactive)
+  default//apple                 0:bf4c1d971543 (inactive)
+
+  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-namespaces-precheck.t	Wed Oct 18 16:37:08 2023 -0300
@@ -0,0 +1,96 @@
+Checking affected topic namespaces before history rewrites
+==========================================================
+
+  $ . "$TESTDIR/testlib/common.sh"
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > evolve =
+  > topic =
+  > rebase =
+  > histedit =
+  > [phases]
+  > publish = no
+  > [experimental]
+  > tns-allow-rewrite =
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+
+Make sure general checks in precheck() happen before topic namespaces checks
+
+  $ hg prune null
+  abort: cannot prune the null revision
+  (no changeset checked out)
+  [10]
+
+  $ echo apple > a
+  $ hg ci -qAm apple
+
+  $ hg debug-topic-namespace foo
+  marked working directory as topic namespace: foo
+  $ hg topic bar
+  marked working directory as topic: bar
+  $ echo banana > b
+  $ hg ci -qAm 'banana'
+
+Allowing topic namespaces with --config works correctly
+
+  $ echo broccoli > b
+  $ hg amend -m 'broccoli'
+  abort: refusing to amend changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+  $ hg amend -m 'broccoli' --config experimental.tns-allow-rewrite=foo,something-unrelated
+
+  $ echo coconut > b
+  $ hg ci -qAm 'coconut'
+
+Testing history-rewriting commands from evolve extension
+
+  $ hg amend -m 'coconut'
+  abort: refusing to amend changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+  $ hg amend --patch -m 'coconut'
+  abort: refusing to amend changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+  $ hg uncommit
+  abort: refusing to uncommit changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+  $ hg prune -r .
+  abort: refusing to prune changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+  $ hg split -r .
+  abort: refusing to split changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+  $ hg touch -r .
+  abort: refusing to touch changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+
+Testing core history-rewriting commands
+
+  $ hg ci --amend
+  abort: refusing to amend changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+  $ hg branch different-branch --rev .
+  abort: refusing to change branch of changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+  $ hg rebase -r . -d null
+  abort: refusing to rebase changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+  $ hg histedit
+  abort: refusing to edit changesets with these topic namespaces: foo
+  (modify experimental.tns-allow-rewrite to allow rewriting changesets from these topic namespaces)
+  [10]
+
+  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-namespaces-report.t	Wed Oct 18 16:37:08 2023 -0300
@@ -0,0 +1,284 @@
+============================================================
+Test detection of topic name space affected by a transaction
+============================================================
+
+Reporting affected topic namespaces in transactions
+
+  $ . "$TESTDIR/testlib/common.sh"
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > evolve =
+  > topic =
+  > [phases]
+  > publish = no
+  > [devel]
+  > tns-report-transactions = push
+  > EOF
+
+  $ hg init orig
+
+case 1: new changeset (draft with topic namespace)
+==================================================
+
+topic namespace of that changeset is reported
+
+  $ hg clone orig case-1 -q
+  $ cd orig
+
+  $ echo apple > a
+  $ hg ci -qAm apple
+
+  $ hg push ../case-1
+  pushing to ../case-1
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+  $ echo banana > b
+  $ hg debug-topic-namespace bob
+  marked working directory as topic namespace: bob
+  $ hg topic b
+  marked working directory as topic: b
+  $ hg ci -qAm 'banana'
+
+  $ hg push ../case-1
+  pushing to ../case-1
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: bob
+  added 1 changesets with 1 changes to 1 files
+
+  $ cd ..
+
+case 2: obsmarker affecting known changeset
+===========================================
+
+topic namespaces of both the precursor and the successor are affected
+
+  $ hg clone orig case-2 -q
+  $ cd orig
+
+  $ echo broccoli > b
+  $ hg debug-topic-namespace bruce
+  $ hg ci --amend -m 'broccoli'
+
+  $ hg push ../case-2
+  pushing to ../case-2
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: bob bruce
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  obsoleted 1 changesets
+
+  $ cd ..
+
+case 3: phase divergence
+========================
+
+3 phase divergence resolution can point to a thing but not affect it (probably not affected)
+
+In this case, the pushed changeset comes with an obsmarker whose predecessors
+has the `charlie` topic-namespace and the successors has the `carol`
+topic-namespace. However, that obsolescence is part of a phase-divergence
+fixup, so we should now mark `coconut` as affected since it is already public.
+
+  $ hg clone orig case-3 -q
+  $ cd orig
+
+  $ hg debug-topic-namespace charlie
+  $ hg topic c
+  $ echo coconut > c
+  $ hg ci -qAm 'coconut'
+
+  $ hg debug-topic-namespace carol
+  $ echo cloudberry > c
+  $ hg ci --amend -m 'cloudberry'
+
+  $ hg phase --hidden -r 'desc("coconut")' --public
+  1 new phase-divergent changesets
+
+  $ hg evolve --phase-divergent
+  recreate:[s1] cloudberry
+  atop:[3] coconut
+  committed as 9f1abc6f4a6f
+  working directory is now at 9f1abc6f4a6f
+
+  $ hg push ../case-3
+  pushing to ../case-3
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: bruce carol
+  added 2 changesets with 2 changes to 1 files
+  2 new obsolescence markers
+
+  $ cd ..
+
+case 4: phase movement: publishing drafts
+=========================================
+
+topic namespaces of published changesets are affected
+
+  $ hg clone orig case-4 -q
+  $ cd orig
+
+  $ hg push ../case-4 --publish
+  pushing to ../case-4
+  searching for changes
+  no changes found
+  topic namespaces affected: carol
+  active topic 'c' is now empty
+  (use 'hg topic --clear' to clear it if needed)
+  [1]
+
+  $ cd ..
+
+case 5: bookmark movement
+=========================
+
+Bookmark movement that affect tns (like putting a bookmark on obsolete
+changesets) their topic namespaces reappear and are therefore reported
+
+  $ hg clone orig case-5 -q
+  $ cd orig
+
+  $ hg debug-topic-namespace dana
+  $ hg topic d
+  $ echo durian > d
+  $ hg ci -qAm 'durian'
+
+  $ hg push ../case-5
+  pushing to ../case-5
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: dana
+  added 1 changesets with 1 changes to 1 files
+
+  $ hg debug-topic-namespace dave
+  $ echo dragonfruit > d
+  $ hg ci --amend -m 'dragonfruit'
+
+  $ hg push ../case-5
+  pushing to ../case-5
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: dana dave
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  obsoleted 1 changesets
+
+  $ hg bookmark --hidden -r 'desc("durian")' @
+  bookmarking hidden changeset c56d89b2348b
+  (hidden revision 'c56d89b2348b' was rewritten as: 7fc662c4767d)
+
+  $ hg push ../case-5 -B @
+  pushing to ../case-5
+  searching for changes
+  no changes found
+  topic namespaces affected: dana
+  exporting bookmark @
+  [1]
+
+  $ cd ..
+
+case 6: phase movement: publishing secret changesets
+====================================================
+
+(that are known on the server)
+
+topic namespaces of published changesets are affected
+
+  $ hg clone orig case-6 -q
+  $ cd orig
+
+XXX: we see "active topic is now empty" twice because stack doesn't handle topic namespaces yet
+
+  $ hg push ../case-6 -r . --publish
+  pushing to ../case-6
+  searching for changes
+  no changes found
+  topic namespaces affected: dave
+  active topic 'd' is now empty
+  active topic 'd' is now empty
+  (use 'hg topic --clear' to clear it if needed)
+  [1]
+
+previous topic namespace is resurrected...
+
+  $ hg phase --secret --force -r . --config 'devel.tns-report-transactions=phase'
+  topic namespaces affected: dave
+  active topic 'd' grew its first changeset
+  (see 'hg help topics' for more information)
+
+...just to disappear again
+
+  $ hg push ../case-6 -r . --config 'devel.tns-report-transactions=*'
+  pushing to ../case-6
+  searching for changes
+  no changes found
+  topic namespaces affected: dave
+  active topic 'd' is now empty
+  (use 'hg topic --clear' to clear it if needed)
+  [1]
+
+  $ cd ..
+
+case 7: phase movement: secret->draft on the server
+===================================================
+
+changeset becomes visible to peers, so its topic namespace is affected
+
+  $ hg clone orig case-7 -q
+  $ cd orig
+
+  $ hg phase --draft --force -r tip
+  active topic 'd' grew its first changeset
+  (see 'hg help topics' for more information)
+  $ hg phase --secret --force -r tip -R ../case-7
+  active topic 'd' grew its first changeset
+  (see 'hg help topics' for more information)
+
+  $ hg push ../case-7 -r . --config 'devel.tns-report-transactions=*'
+  pushing to ../case-7
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: dave
+  added 0 changesets with 0 changes to 1 files
+  active topic 'd' grew its first changeset
+  (see 'hg help topics' for more information)
+
+  $ cd ..
+
+case: 99 pushing obsmarker for an unknown changeset
+===================================================
+doesn't affect any topic namespace, we report nothing
+
+  $ hg clone orig case-99 -q
+  $ cd orig
+
+  $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa `getid "desc('dragonfruit')"`
+  1 new obsolescence markers
+
+  $ hg push ../case-99
+  pushing to ../case-99
+  searching for changes
+  no changes found
+  1 new obsolescence markers
+  [1]
+
+  $ cd ..
--- a/tests/test-namespaces.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-namespaces.t	Wed Oct 18 16:37:08 2023 -0300
@@ -5,22 +5,40 @@
   $ hg init repo
   $ cd repo
 
+Setting a topic namespace alone doesn't affect wdir()
+
   $ hg debug-topic-namespace space-name
   marked working directory as topic namespace: space-name
   $ hg debug-topic-namespaces
   space-name
 
   $ hg log -r 'wdir()' -T '{topic_namespace}\n'
+  none
+
+  $ hg log -r 'wdir()' -T '{fqbn}\n'
+  default
+
+But after setting a topic the already-set namespace is visible on wdir()
+
+  $ hg topic feature
+  marked working directory as topic: feature
+  $ hg topics
+   * feature (0 changesets)
+
+  $ hg log -r 'wdir()' -T '{topic_namespace}\n'
   space-name
 
   $ hg log -r 'wdir()' -T '{fqbn}\n'
-  default//space-name/
+  default//space-name/feature
 
   $ hg branches
 
   $ hg debug-topic-namespace --clear
   $ hg debug-topic-namespaces
-  default
+
+  $ hg topic --clear
+  clearing empty topic "feature"
+  $ hg topics
 
   $ hg debugtopicnamespace --clear nonsense
   abort: cannot use --clear when setting a topic namespace
@@ -53,7 +71,7 @@
   $ hg up null
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg debug-topic-namespace
-  default
+  none
   $ hg topics
      feature (1 changesets)
   $ hg up 0
@@ -81,6 +99,7 @@
   # Branch stable
   # Node ID 69c7dbf6acd180eeec055dd67933badd3601d45f
   # Parent  0000000000000000000000000000000000000000
+  # EXP-Topic-Namespace alice
   # EXP-Topic feature
   a
   
@@ -96,8 +115,9 @@
   > # Date 0 0
   > #      Thu Jan 01 00:00:00 1970 +0000
   > # Branch another-branch
-  > # Node ID 94981e5d988ea23cf2b17f6c07c39edc0f174b01
-  > # Parent  d6d47657e765570283ec03fc68836d9eb297c4b3
+  > # Node ID 1111111111111111111111111111111111111111
+  > # Parent  2222222222222222222222222222222222222222
+  > # EXP-Topic-Namespace mynamespace
   > # EXP-Topic mytopic
   > added z
   > 
@@ -111,10 +131,77 @@
   applying patch from stdin
 
   $ hg log -r tip -T '{rev}: {branch} {topic_namespace} {topic}\n'
-  1: stable alice mytopic
+  1: stable mynamespace mytopic
+
+  $ hg log -r tip -T '{rev}: {fqbn}\n'
+  1: stable//mynamespace/mytopic
+
+  $ hg log -r tip -T '{rev}: {join(extras, " ")}\n'
+  1: branch=stable topic=mytopic topic-namespace=mynamespace
+
+Importing a patch with default namespace and topic values
+
+  $ hg import - << EOF
+  > # HG changeset patch
+  > # User test
+  > # Date 0 0
+  > #      Thu Jan 01 00:00:00 1970 +0000
+  > # Branch stable
+  > # Node ID 1111111111111111111111111111111111111111
+  > # Parent  2222222222222222222222222222222222222222
+  > # EXP-Topic-Namespace none
+  > # EXP-Topic 
+  > more z
+  > 
+  > diff --git a/z b/z
+  > --- a/z
+  > +++ b/z
+  > @@ -1,1 +1,1 @@
+  > -z
+  > +zebra
+  > EOF
+  applying patch from stdin
+
+  $ hg log -r tip -T '{rev}: {branch} {topic_namespace} {topic}\n'
+  2: stable none 
 
   $ hg log -r tip -T '{rev}: {fqbn}\n'
-  1: stable//alice/mytopic
+  2: stable
+
+  $ hg log -r tip -T '{rev}: {join(extras, " ")}\n'
+  2: branch=stable
+
+Importing a patch with topic namespace set and topic unset
+
+  $ hg import - << EOF
+  > # HG changeset patch
+  > # User test
+  > # Date 0 0
+  > #      Thu Jan 01 00:00:00 1970 +0000
+  > # Branch stable
+  > # Node ID 1111111111111111111111111111111111111111
+  > # Parent  2222222222222222222222222222222222222222
+  > # EXP-Topic-Namespace mynamespace
+  > # EXP-Topic 
+  > more z
+  > 
+  > diff --git a/z b/z
+  > --- a/z
+  > +++ b/z
+  > @@ -1,1 +1,1 @@
+  > -zebra
+  > +z
+  > EOF
+  applying patch from stdin
+
+  $ hg log -r tip -T '{rev}: {branch} {topic_namespace} {topic}\n'
+  3: stable none 
+
+  $ hg log -r tip -T '{rev}: {fqbn}\n'
+  3: stable
+
+  $ hg log -r tip -T '{rev}: {join(extras, " ")}\n'
+  3: branch=stable
 
 Revsets
 
@@ -122,21 +209,21 @@
   >   hg log -T '{rev}: {topic_namespace}\n' -r "$1"
   > }
 
+  $ nslog 'topicnamespace()'
+  0: alice
+  1: mynamespace
   $ nslog 'topicnamespace(:)'
   0: alice
-  1: alice
+  1: mynamespace
   $ nslog 'topicnamespace(all())'
   0: alice
-  1: alice
+  1: mynamespace
   $ nslog 'topicnamespace(topicnamespace("alice"))'
   0: alice
-  1: alice
   $ nslog 'topicnamespace(wdir())'
   0: alice
-  1: alice
   $ nslog 'topicnamespace("re:ice$")'
   0: alice
-  1: alice
   $ nslog 'topicnamespace(nonsense)'
   abort: unknown revision 'nonsense'
   [10]
@@ -154,7 +241,7 @@
 no double slashes means it's a named branch
   $ hg debug-parse-fqbn foo/bar
   branch:    foo/bar
-  namespace: default
+  namespace: none
   topic:     
 
 Formatting
@@ -170,9 +257,9 @@
 
 default values
 
-  $ hg debug-format-fqbn -b default -n default -t '' --no-short
-  default//default/
-  $ hg debug-format-fqbn -b default -n default -t '' --short
+  $ hg debug-format-fqbn -b default -n none -t '' --no-short
+  default//none/
+  $ hg debug-format-fqbn -b default -n none -t '' --short
   default
 
   $ hg debug-format-fqbn -b default -n namespace -t '' --no-short
@@ -180,9 +267,9 @@
   $ hg debug-format-fqbn -b default -n namespace -t '' --short
   default//namespace/
 
-  $ hg debug-format-fqbn -b default -n default -t topic --no-short
-  default//default/topic
-  $ hg debug-format-fqbn -b default -n default -t topic --short
+  $ hg debug-format-fqbn -b default -n none -t topic --no-short
+  default//none/topic
+  $ hg debug-format-fqbn -b default -n none -t topic --short
   default//topic
 
   $ cd ..
--- a/tests/test-pick.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-pick.t	Wed Oct 18 16:37:08 2023 -0300
@@ -380,6 +380,41 @@
 
   $ hg phase -r .
   13: secret
+
+Pick rewrites node hashes in commit description
+
+  $ echo 'hg pick rewrites hashes 1' > l
+  $ hg ci -m '1705aa17bfed is my parent'
+  $ echo 'hg pick rewrites hashes 2' > l
+  $ hg ci -m 'f9ed1703aa00 is my parent'
+
+  $ hg glog -r .^^::
+  @  15:e3cb66090723 f9ed1703aa00 is my parent
+  |
+  o  14:f9ed1703aa00 1705aa17bfed is my parent
+  |
+  o  13:1705aa17bfed added l
+  |
+  ~
+
+  $ hg up -q c437988de89f
+  $ hg pick 13
+  picking 13:1705aa17bfed "added l"
+  2 new orphan changesets
+  $ hg pick 14
+  picking 14:f9ed1703aa00 "1705aa17bfed is my parent"
+  $ hg pick 15
+  picking 15:e3cb66090723 "f9ed1703aa00 is my parent"
+
+  $ hg glog -r .^^::
+  @  18:5825531d1322 7a6997f82fbe is my parent
+  |
+  o  17:7a6997f82fbe fb6281ea03e8 is my parent
+  |
+  o  16:fb6281ea03e8 added l
+  |
+  ~
+
   $ cd ..
 
 Check pick behavior regarding working copy branch (issue6089)
--- a/tests/test-split.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-split.t	Wed Oct 18 16:37:08 2023 -0300
@@ -543,6 +543,7 @@
   # Branch another-branch
   # Node ID 94981e5d988ea23cf2b17f6c07c39edc0f174b01
   # Parent  d6d47657e765570283ec03fc68836d9eb297c4b3
+  # EXP-Topic-Namespace mynamespace
   # EXP-Topic mytopic
   To be split
   
@@ -739,6 +740,7 @@
   # Branch another-branch
   # Node ID 61661257a93759374255a4c05fcd9b8a78bbe399
   # Parent  a05395d0b42120af8bfb222d19f01008b1342c15
+  # EXP-Topic-Namespace mynamespace
   # EXP-Topic mytopic
   split10
   
@@ -755,6 +757,7 @@
   # Branch another-branch
   # Node ID e6ca7ba1372dc452769c51a56d853c8ede26d9fa
   # Parent  a05395d0b42120af8bfb222d19f01008b1342c15
+  # EXP-Topic-Namespace mynamespace
   # EXP-Topic mytopic
   split10
   
@@ -791,6 +794,7 @@
   # Branch another-branch
   # Node ID bb8f1c282ddf89515bd07bd63a84962ab51ac277
   # Parent  a05395d0b42120af8bfb222d19f01008b1342c15
+  # EXP-Topic-Namespace mynamespace
   # EXP-Topic mytopic
   split12
   
--- a/tests/test-stablerange-branchpoint.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-stablerange-branchpoint.t	Wed Oct 18 16:37:08 2023 -0300
@@ -15,14 +15,14 @@
 #if basic-branchpoint
   $ cat << EOF >> $HGRCPATH
   > [defaults]
-  > debugstablerange = --method basic-branchpoint
+  > debug::evo-ext-stable-range = --method basic-branchpoint
   > EOF
 #endif
 
 #if branchpoint
   $ cat << EOF >> $HGRCPATH
   > [defaults]
-  > debugstablerange = --method branchpoint
+  > debug::evo-ext-stable-range = --method branchpoint
   > EOF
 #endif
 
@@ -40,14 +40,14 @@
   bebd167eb94d 5
   c8d03c1b5e94 6
   f69452c5b1af 7
-  $ hg debugstablerange --verify --verbose --subranges --rev 1 | tee 1.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 1 | tee 1.range
   66f7d451a68b-0 (1, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), 66f7d451a68b-1 (1, 2, 1)
   1ea73414a91b-0 (0, 1, 1) [leaf] - 
   66f7d451a68b-1 (1, 2, 1) [leaf] - 
 
 bigger subset reuse most of the previous one
 
-  $ hg debugstablerange --verify --verbose --subranges --rev 4 | tee 4.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 4 | tee 4.range
   bebd167eb94d-0 (4, 5, 5) [complete] - 2dc09a01254d-0 (3, 4, 4), bebd167eb94d-4 (4, 5, 1)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   2dc09a01254d-2 (3, 4, 2) [complete] - 01241442b3c2-2 (2, 3, 1), 2dc09a01254d-3 (3, 4, 1)
@@ -75,7 +75,7 @@
 Using a range not ending on 2**N boundary
 we fall back on 2**N as much as possible
 
-  $ hg debugstablerange --verify --verbose --subranges --rev 5 | tee 5.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 5 | tee 5.range
   c8d03c1b5e94-0 (5, 6, 6) [complete] - 2dc09a01254d-0 (3, 4, 4), c8d03c1b5e94-4 (5, 6, 2)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   2dc09a01254d-2 (3, 4, 2) [complete] - 01241442b3c2-2 (2, 3, 1), 2dc09a01254d-3 (3, 4, 1)
@@ -107,7 +107,7 @@
 
 Even two unperfect range overlap a lot
 
-  $ hg debugstablerange --verify --verbose --subranges --rev tip | tee tip.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev tip | tee tip.range
   f69452c5b1af-0 (6, 7, 7) [complete] - 2dc09a01254d-0 (3, 4, 4), f69452c5b1af-4 (6, 7, 3)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   f69452c5b1af-4 (6, 7, 3) [complete] - c8d03c1b5e94-4 (5, 6, 2), f69452c5b1af-6 (6, 7, 1)
@@ -192,11 +192,11 @@
 
 (left branch)
 
-  $ hg debugstablerange --verify --verbose --subranges --rev 'left~2' | tee left-2.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 'left~2' | tee left-2.range
   66f7d451a68b-0 (1, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), 66f7d451a68b-1 (1, 2, 1)
   1ea73414a91b-0 (0, 1, 1) [leaf] - 
   66f7d451a68b-1 (1, 2, 1) [leaf] - 
-  $ hg debugstablerange --verify --verbose --subranges --rev left | tee left.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev left | tee left.range
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   2dc09a01254d-2 (3, 4, 2) [complete] - 01241442b3c2-2 (2, 3, 1), 2dc09a01254d-3 (3, 4, 1)
   66f7d451a68b-0 (1, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), 66f7d451a68b-1 (1, 2, 1)
@@ -219,11 +219,11 @@
 
 (right branch)
 
-  $ hg debugstablerange --verify --verbose --subranges --rev right~2 | tee right-2.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev right~2 | tee right-2.range
   e7bd5218ca15-0 (4, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), e7bd5218ca15-1 (4, 2, 1)
   1ea73414a91b-0 (0, 1, 1) [leaf] - 
   e7bd5218ca15-1 (4, 2, 1) [leaf] - 
-  $ hg debugstablerange --verify --verbose --subranges --rev right | tee right.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev right | tee right.range
   a2f58e9c1e56-0 (6, 4, 4) [complete] - e7bd5218ca15-0 (4, 2, 2), a2f58e9c1e56-2 (6, 4, 2)
   a2f58e9c1e56-2 (6, 4, 2) [complete] - 3a367db1fabc-2 (5, 3, 1), a2f58e9c1e56-3 (6, 4, 1)
   e7bd5218ca15-0 (4, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), e7bd5218ca15-1 (4, 2, 1)
@@ -246,7 +246,7 @@
 
 The merge reuse as much of the slicing created for one of the branch
 
-  $ hg debugstablerange --verify --verbose --subranges --rev merge | tee merge.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev merge | tee merge.range
   5f18015f9110-0 (7, 8, 8) [complete] - 2dc09a01254d-0 (3, 4, 4), 5f18015f9110-4 (7, 8, 4)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   5f18015f9110-4 (7, 8, 4) [complete] - 3a367db1fabc-1 (5, 3, 2), 5f18015f9110-6 (7, 8, 2)
@@ -372,13 +372,13 @@
 
 (left branch)
 
-  $ hg debugstablerange --verify --verbose --subranges --rev 'left~2' | tee left-2.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 'left~2' | tee left-2.range
   01241442b3c2-0 (2, 3, 3) [complete] - 66f7d451a68b-0 (1, 2, 2), 01241442b3c2-2 (2, 3, 1)
   66f7d451a68b-0 (1, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), 66f7d451a68b-1 (1, 2, 1)
   01241442b3c2-2 (2, 3, 1) [leaf] - 
   1ea73414a91b-0 (0, 1, 1) [leaf] - 
   66f7d451a68b-1 (1, 2, 1) [leaf] - 
-  $ hg debugstablerange --verify --verbose --subranges --rev left | tee left.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev left | tee left.range
   bebd167eb94d-0 (4, 5, 5) [complete] - 2dc09a01254d-0 (3, 4, 4), bebd167eb94d-4 (4, 5, 1)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   2dc09a01254d-2 (3, 4, 2) [complete] - 01241442b3c2-2 (2, 3, 1), 2dc09a01254d-3 (3, 4, 1)
@@ -406,7 +406,7 @@
 
 (right branch)
 
-  $ hg debugstablerange --verify --verbose --subranges --rev right~2 | tee right-2.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev right~2 | tee right-2.range
   42b07e8da27d-0 (7, 4, 4) [complete] - de561312eff4-0 (5, 2, 2), 42b07e8da27d-2 (7, 4, 2)
   42b07e8da27d-2 (7, 4, 2) [complete] - b9bc20507e0b-2 (6, 3, 1), 42b07e8da27d-3 (7, 4, 1)
   de561312eff4-0 (5, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), de561312eff4-1 (5, 2, 1)
@@ -414,7 +414,7 @@
   42b07e8da27d-3 (7, 4, 1) [leaf] - 
   b9bc20507e0b-2 (6, 3, 1) [leaf] - 
   de561312eff4-1 (5, 2, 1) [leaf] - 
-  $ hg debugstablerange --verify --verbose --subranges --rev right | tee right.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev right | tee right.range
   f4b7da68b467-0 (9, 6, 6) [complete] - 42b07e8da27d-0 (7, 4, 4), f4b7da68b467-4 (9, 6, 2)
   42b07e8da27d-0 (7, 4, 4) [complete] - de561312eff4-0 (5, 2, 2), 42b07e8da27d-2 (7, 4, 2)
   42b07e8da27d-2 (7, 4, 2) [complete] - b9bc20507e0b-2 (6, 3, 1), 42b07e8da27d-3 (7, 4, 1)
@@ -449,7 +449,7 @@
 
 We are still able to reuse one of the branch however
 
-  $ hg debugstablerange --verify --verbose --subranges --rev merge | tee merge.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev merge | tee merge.range
   8aca7f8c9bd2-0 (10, 11, 11) [complete] - bebd167eb94d-0 (4, 5, 5), 42b07e8da27d-1 (7, 4, 3), 8aca7f8c9bd2-8 (10, 11, 3)
   bebd167eb94d-0 (4, 5, 5) [complete] - 2dc09a01254d-0 (3, 4, 4), bebd167eb94d-4 (4, 5, 1)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
@@ -526,7 +526,7 @@
 
 Range above the merge, reuse subrange from the merge
 
-  $ hg debugstablerange --verify --verbose --subranges --rev tip | tee tip.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev tip | tee tip.range
   e6b8d5b46647-0 (12, 13, 13) [complete] - bebd167eb94d-0 (4, 5, 5), 42b07e8da27d-1 (7, 4, 3), e6b8d5b46647-8 (12, 13, 5)
   bebd167eb94d-0 (4, 5, 5) [complete] - 2dc09a01254d-0 (3, 4, 4), bebd167eb94d-4 (4, 5, 1)
   e6b8d5b46647-8 (12, 13, 5) [complete] - 485383494a89-8 (11, 12, 4), e6b8d5b46647-12 (12, 13, 1)
@@ -653,7 +653,7 @@
   b4594d867745 6
   43227190fef8 5
   1d8d22637c2d 8
-  $ hg debugstablerange --verify --verbose --subranges --rev 'head()'
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 'head()'
   1d8d22637c2d-0 (15, 8, 8) [complete] - 2b6d669947cd-0 (3, 4, 4), 1d8d22637c2d-4 (15, 8, 4)
   dcbb326fdec2-0 (9, 7, 7) [complete] - 2b6d669947cd-0 (3, 4, 4), dcbb326fdec2-4 (9, 7, 3)
   ff43616e5d0f-0 (10, 7, 7) [complete] - 2b6d669947cd-0 (3, 4, 4), ff43616e5d0f-4 (10, 7, 3)
@@ -718,7 +718,7 @@
   fa942426a6fd 2
   36315563e2fa 3
   f37e476fba9a 5
-  $ hg debugstablerange --verify --verbose --subranges --rev 'head()'
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 'head()'
   f37e476fba9a-0 (4, 5, 5) [complete] - 66f7d451a68b-0 (1, 2, 2), 36315563e2fa-1 (3, 3, 2), f37e476fba9a-4 (4, 5, 1)
   36315563e2fa-1 (3, 3, 2) [complete] - fa942426a6fd-1 (2, 2, 1), 36315563e2fa-2 (3, 3, 1)
   66f7d451a68b-0 (1, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), 66f7d451a68b-1 (1, 2, 1)
--- a/tests/test-stablerange.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-stablerange.t	Wed Oct 18 16:37:08 2023 -0300
@@ -15,21 +15,21 @@
 #if default
   $ cat << EOF >> $HGRCPATH
   > [defaults]
-  > debugstablerange = --method default
+  > debug::evo-ext-stable-range = --method default
   > EOF
 #endif
 
 #if basic-mergepoint
   $ cat << EOF >> $HGRCPATH
   > [defaults]
-  > debugstablerange = --method basic-mergepoint
+  > debug::evo-ext-stable-range = --method basic-mergepoint
   > EOF
 #endif
 
 #if mergepoint
   $ cat << EOF >> $HGRCPATH
   > [defaults]
-  > debugstablerange = --method mergepoint
+  > debug::evo-ext-stable-range = --method mergepoint
   > EOF
 #endif
 
@@ -47,14 +47,14 @@
   bebd167eb94d 5
   c8d03c1b5e94 6
   f69452c5b1af 7
-  $ hg debugstablerange --verify --verbose --subranges --rev 1 | tee 1.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 1 | tee 1.range
   66f7d451a68b-0 (1, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), 66f7d451a68b-1 (1, 2, 1)
   1ea73414a91b-0 (0, 1, 1) [leaf] - 
   66f7d451a68b-1 (1, 2, 1) [leaf] - 
 
 bigger subset reuse most of the previous one
 
-  $ hg debugstablerange --verify --verbose --subranges --rev 4 | tee 4.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 4 | tee 4.range
   bebd167eb94d-0 (4, 5, 5) [complete] - 2dc09a01254d-0 (3, 4, 4), bebd167eb94d-4 (4, 5, 1)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   2dc09a01254d-2 (3, 4, 2) [complete] - 01241442b3c2-2 (2, 3, 1), 2dc09a01254d-3 (3, 4, 1)
@@ -82,7 +82,7 @@
 Using a range not ending on 2**N boundary
 we fall back on 2**N as much as possible
 
-  $ hg debugstablerange --verify --verbose --subranges --rev 5 | tee 5.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 5 | tee 5.range
   c8d03c1b5e94-0 (5, 6, 6) [complete] - 2dc09a01254d-0 (3, 4, 4), c8d03c1b5e94-4 (5, 6, 2)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   2dc09a01254d-2 (3, 4, 2) [complete] - 01241442b3c2-2 (2, 3, 1), 2dc09a01254d-3 (3, 4, 1)
@@ -114,7 +114,7 @@
 
 Even two unperfect range overlap a lot
 
-  $ hg debugstablerange --verify --verbose --subranges --rev tip | tee tip.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev tip | tee tip.range
   f69452c5b1af-0 (6, 7, 7) [complete] - 2dc09a01254d-0 (3, 4, 4), f69452c5b1af-4 (6, 7, 3)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   f69452c5b1af-4 (6, 7, 3) [complete] - c8d03c1b5e94-4 (5, 6, 2), f69452c5b1af-6 (6, 7, 1)
@@ -199,11 +199,11 @@
 
 (left branch)
 
-  $ hg debugstablerange --verify --verbose --subranges --rev 'left~2' | tee left-2.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 'left~2' | tee left-2.range
   66f7d451a68b-0 (1, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), 66f7d451a68b-1 (1, 2, 1)
   1ea73414a91b-0 (0, 1, 1) [leaf] - 
   66f7d451a68b-1 (1, 2, 1) [leaf] - 
-  $ hg debugstablerange --verify --verbose --subranges --rev left | tee left.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev left | tee left.range
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   2dc09a01254d-2 (3, 4, 2) [complete] - 01241442b3c2-2 (2, 3, 1), 2dc09a01254d-3 (3, 4, 1)
   66f7d451a68b-0 (1, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), 66f7d451a68b-1 (1, 2, 1)
@@ -226,11 +226,11 @@
 
 (right branch)
 
-  $ hg debugstablerange --verify --verbose --subranges --rev right~2 | tee right-2.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev right~2 | tee right-2.range
   e7bd5218ca15-0 (4, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), e7bd5218ca15-1 (4, 2, 1)
   1ea73414a91b-0 (0, 1, 1) [leaf] - 
   e7bd5218ca15-1 (4, 2, 1) [leaf] - 
-  $ hg debugstablerange --verify --verbose --subranges --rev right | tee right.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev right | tee right.range
   a2f58e9c1e56-0 (6, 4, 4) [complete] - e7bd5218ca15-0 (4, 2, 2), a2f58e9c1e56-2 (6, 4, 2)
   a2f58e9c1e56-2 (6, 4, 2) [complete] - 3a367db1fabc-2 (5, 3, 1), a2f58e9c1e56-3 (6, 4, 1)
   e7bd5218ca15-0 (4, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), e7bd5218ca15-1 (4, 2, 1)
@@ -253,7 +253,7 @@
 
 The merge reuse as much of the slicing created for one of the branch
 
-  $ hg debugstablerange --verify --verbose --subranges --rev merge | tee merge.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev merge | tee merge.range
   5f18015f9110-0 (7, 8, 8) [complete] - 2dc09a01254d-0 (3, 4, 4), 5f18015f9110-4 (7, 8, 4)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   5f18015f9110-4 (7, 8, 4) [complete] - 3a367db1fabc-1 (5, 3, 2), 5f18015f9110-6 (7, 8, 2)
@@ -379,13 +379,13 @@
 
 (left branch)
 
-  $ hg debugstablerange --verify --verbose --subranges --rev 'left~2' | tee left-2.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 'left~2' | tee left-2.range
   01241442b3c2-0 (2, 3, 3) [complete] - 66f7d451a68b-0 (1, 2, 2), 01241442b3c2-2 (2, 3, 1)
   66f7d451a68b-0 (1, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), 66f7d451a68b-1 (1, 2, 1)
   01241442b3c2-2 (2, 3, 1) [leaf] - 
   1ea73414a91b-0 (0, 1, 1) [leaf] - 
   66f7d451a68b-1 (1, 2, 1) [leaf] - 
-  $ hg debugstablerange --verify --verbose --subranges --rev left | tee left.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev left | tee left.range
   bebd167eb94d-0 (4, 5, 5) [complete] - 2dc09a01254d-0 (3, 4, 4), bebd167eb94d-4 (4, 5, 1)
   2dc09a01254d-0 (3, 4, 4) [complete] - 66f7d451a68b-0 (1, 2, 2), 2dc09a01254d-2 (3, 4, 2)
   2dc09a01254d-2 (3, 4, 2) [complete] - 01241442b3c2-2 (2, 3, 1), 2dc09a01254d-3 (3, 4, 1)
@@ -413,7 +413,7 @@
 
 (right branch)
 
-  $ hg debugstablerange --verify --verbose --subranges --rev right~2 | tee right-2.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev right~2 | tee right-2.range
   42b07e8da27d-0 (7, 4, 4) [complete] - de561312eff4-0 (5, 2, 2), 42b07e8da27d-2 (7, 4, 2)
   42b07e8da27d-2 (7, 4, 2) [complete] - b9bc20507e0b-2 (6, 3, 1), 42b07e8da27d-3 (7, 4, 1)
   de561312eff4-0 (5, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), de561312eff4-1 (5, 2, 1)
@@ -421,7 +421,7 @@
   42b07e8da27d-3 (7, 4, 1) [leaf] - 
   b9bc20507e0b-2 (6, 3, 1) [leaf] - 
   de561312eff4-1 (5, 2, 1) [leaf] - 
-  $ hg debugstablerange --verify --verbose --subranges --rev right | tee right.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev right | tee right.range
   f4b7da68b467-0 (9, 6, 6) [complete] - 42b07e8da27d-0 (7, 4, 4), f4b7da68b467-4 (9, 6, 2)
   42b07e8da27d-0 (7, 4, 4) [complete] - de561312eff4-0 (5, 2, 2), 42b07e8da27d-2 (7, 4, 2)
   42b07e8da27d-2 (7, 4, 2) [complete] - b9bc20507e0b-2 (6, 3, 1), 42b07e8da27d-3 (7, 4, 1)
@@ -456,7 +456,7 @@
 
 We are still able to reuse one of the branch however
 
-  $ hg debugstablerange --verify --verbose --subranges --rev merge | tee merge.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev merge | tee merge.range
   8aca7f8c9bd2-0 (10, 11, 11) [complete] - f4b7da68b467-0 (9, 6, 6), 01241442b3c2-1 (2, 3, 2), 8aca7f8c9bd2-8 (10, 11, 3)
   f4b7da68b467-0 (9, 6, 6) [complete] - 42b07e8da27d-0 (7, 4, 4), f4b7da68b467-4 (9, 6, 2)
   42b07e8da27d-0 (7, 4, 4) [complete] - de561312eff4-0 (5, 2, 2), 42b07e8da27d-2 (7, 4, 2)
@@ -534,7 +534,7 @@
 
 Range above the merge, reuse subrange from the merge
 
-  $ hg debugstablerange --verify --verbose --subranges --rev tip | tee tip.range
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev tip | tee tip.range
   e6b8d5b46647-0 (12, 13, 13) [complete] - f4b7da68b467-0 (9, 6, 6), 01241442b3c2-1 (2, 3, 2), e6b8d5b46647-8 (12, 13, 5)
   f4b7da68b467-0 (9, 6, 6) [complete] - 42b07e8da27d-0 (7, 4, 4), f4b7da68b467-4 (9, 6, 2)
   e6b8d5b46647-8 (12, 13, 5) [complete] - 485383494a89-8 (11, 12, 4), e6b8d5b46647-12 (12, 13, 1)
@@ -661,7 +661,7 @@
   b4594d867745 6
   43227190fef8 5
   1d8d22637c2d 8
-  $ hg debugstablerange --verify --verbose --subranges --rev 'head()'
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 'head()'
   1d8d22637c2d-0 (15, 8, 8) [complete] - 2b6d669947cd-0 (3, 4, 4), 1d8d22637c2d-4 (15, 8, 4)
   dcbb326fdec2-0 (9, 7, 7) [complete] - 2b6d669947cd-0 (3, 4, 4), dcbb326fdec2-4 (9, 7, 3)
   ff43616e5d0f-0 (10, 7, 7) [complete] - 2b6d669947cd-0 (3, 4, 4), ff43616e5d0f-4 (10, 7, 3)
@@ -726,7 +726,7 @@
   fa942426a6fd 2
   36315563e2fa 3
   f37e476fba9a 5
-  $ hg debugstablerange --verify --verbose --subranges --rev 'head()'
+  $ hg debug::evo-ext-stable-range --verify --verbose --subranges --rev 'head()'
   f37e476fba9a-0 (4, 5, 5) [complete] - 36315563e2fa-0 (3, 3, 3), 66f7d451a68b-1 (1, 2, 1), f37e476fba9a-4 (4, 5, 1)
   36315563e2fa-0 (3, 3, 3) [complete] - fa942426a6fd-0 (2, 2, 2), 36315563e2fa-2 (3, 3, 1)
   fa942426a6fd-0 (2, 2, 2) [complete] - 1ea73414a91b-0 (0, 1, 1), fa942426a6fd-1 (2, 2, 1)
--- a/tests/test-stablesort-branchpoint-criss-cross.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-stablesort-branchpoint-criss-cross.t	Wed Oct 18 16:37:08 2023 -0300
@@ -9,7 +9,7 @@
   > [ui]
   > logtemplate = "{rev} {node|short} {desc} {tags}\n"
   > [alias]
-  > showsort = debugstablesort --template="{node|short}\n" --method branchpoint
+  > showsort = debug::evo-ext-stable-sort --template="{node|short}\n" --method branchpoint
   > EOF
 
   $ checktopo () {
--- a/tests/test-stablesort-branchpoint.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-stablesort-branchpoint.t	Wed Oct 18 16:37:08 2023 -0300
@@ -9,7 +9,7 @@
   > [ui]
   > logtemplate = "{rev} {node|short} {desc} {tags}\n"
   > [alias]
-  > showsort = debugstablesort --template="{node|short}\n" --method branchpoint
+  > showsort = debug::evo-ext-stable-sort --template="{node|short}\n" --method branchpoint
   > EOF
 
   $ checktopo () {
--- a/tests/test-stablesort-criss-cross.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-stablesort-criss-cross.t	Wed Oct 18 16:37:08 2023 -0300
@@ -9,7 +9,7 @@
   > [ui]
   > logtemplate = "{rev} {node|short} {desc} {tags}\n"
   > [alias]
-  > showsort = debugstablesort --template="{node|short}\n" --method headondisk
+  > showsort = debug::evo-ext-stable-sort --template="{node|short}\n" --method headondisk
   > EOF
 
   $ checktopo () {
--- a/tests/test-stablesort.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-stablesort.t	Wed Oct 18 16:37:08 2023 -0300
@@ -9,8 +9,8 @@
   > [ui]
   > logtemplate = "{rev} {node|short} {desc} {tags}\n"
   > [alias]
-  > showsort = debugstablesort --template="{node|short}\n" --method basic-mergepoint
-  > showsorthead = debugstablesort --template="{node|short}\n" --method headondisk
+  > showsort = debug::evo-ext-stable-sort --template="{node|short}\n" --method basic-mergepoint
+  > showsorthead = debug::evo-ext-stable-sort --template="{node|short}\n" --method headondisk
   > EOF
 
   $ checktopo () {
--- a/tests/test-topic-flow-publish-bare.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-topic-flow-publish-bare.t	Wed Oct 18 16:37:08 2023 -0300
@@ -349,11 +349,13 @@
 
   $ hg debugcapabilities $TESTTMP/bare-branch-server | grep topics
     ext-topics-publish=auto
+    ext-topics-tns-heads
     topics
     topics-namespaces
   $ hg debugcapabilities ssh://user@dummy/bare-branch-server | grep topics
     _exttopics_heads
     ext-topics-publish=auto
+    ext-topics-tns-heads
     topics
     topics-namespaces
   $ hg serve -R ../bare-branch-server -p $HGPORT -d --pid-file hg.pid
@@ -361,6 +363,7 @@
   $ hg debugcapabilities http://localhost:$HGPORT | grep topics
     _exttopics_heads
     ext-topics-publish=auto
+    ext-topics-tns-heads
     topics
     topics-namespaces
   $ killdaemons.py
--- a/tests/test-topic-stack.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-topic-stack.t	Wed Oct 18 16:37:08 2023 -0300
@@ -676,6 +676,8 @@
   $ echo aaa > aaa
   $ hg commit -Am 'c_A'
   adding aaa
+  $ hg debug-topic-namespace my-tns
+  marked working directory as topic namespace: my-tns
   $ hg topic red
   marked working directory as topic: red
   $ echo bbb > bbb
@@ -905,12 +907,13 @@
 get things linear again
 
   $ hg rebase -r s1 -d default
-  rebasing 16:1d84ec948370 tip blue "c_D"
+  rebasing 16:c9b07601c2f4 tip blue "c_D"
+  switching to topic-namespace my-tns
   switching to topic blue
   $ hg rebase -r s2 -d s1
-  rebasing 13:3ab2eedae500 blue "c_G"
+  rebasing 13:90c34d9f99aa blue "c_G"
   $ hg rebase -r s3 -d s2
-  rebasing 8:3bfe800e0486 blue "c_I"
+  rebasing 8:77174443ad61 blue "c_I"
   $ hg stack
   ### topic: blue
   ### target: default (branch)
@@ -967,48 +970,49 @@
   continue splitting? [Ycdq?] c
 
   $ hg debugobsolete
-  34679cfcccdd07565970b959c79428af9a5744e4 6a11ae6b0cde4d4952ed68e8077b9e3596d99548 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
-  20f7945d89a5e372b7548f766ebc800677856443 3c7bec987cd37ba12b83c01683e8609dd549c07b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
-  952e24687ebc5f11743268f6f1c3f24fa83c7ddd 74979543bf1d6c0f75229991400f352a6fb3fddb 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
-  c9961f97c7b40b54b3c1922986675d6f38793014 d7bfa3d6ce36dfb917547246752f0c2a564fe33b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
-  c7d60a180d05255e8c6ea50bce09d014015b7cdc 3ab2eedae500f52b6aa220bb8ce6e20732a8a6d1 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
-  6a11ae6b0cde4d4952ed68e8077b9e3596d99548 61700bf67137c724a72aa5f034e9187d2c5e7e47 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
-  3c7bec987cd37ba12b83c01683e8609dd549c07b 4bcfa5dd0945476ba938e8115e81ba367af3b573 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
-  74979543bf1d6c0f75229991400f352a6fb3fddb 1d84ec948370a2ac1a51f3ab27835e31d50c3407 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
-  1d84ec948370a2ac1a51f3ab27835e31d50c3407 f3328cd199dc389b850ca952f65a15a8e6dbc79b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
-  3ab2eedae500f52b6aa220bb8ce6e20732a8a6d1 907f7d3c2333082d62942ac3a47e466ce03d82b9 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
-  3bfe800e04863d23f909f8d98848656b7b5a971a 662ff4ad29901b325a64c39f7850e3efaaeeccc5 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
-  907f7d3c2333082d62942ac3a47e466ce03d82b9 b24bab30ac12f6124a52e74aaf46b7468e42526c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
-  b24bab30ac12f6124a52e74aaf46b7468e42526c dde94df880e97f4a1ee8c5408254b429b3d90204 e7ea874afbd5c17aeee366d39a828dbcb01682ce 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'split', 'user': 'test'}
+  8da160be12a0d8d40b5aad42aea02123e255f802 6646bdc8cb87942886d4b8548bc243aaacf94b61 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
+  2dc043b3e268bc3d7f30fc2319a72f4430af1d31 6044ea1ba1f7e573bc33312518cd005c45e2ad7d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
+  d2c0265139080fe437d3550f5a6c975bc5c3f545 03ec02fb9548883827e62560d0b1e786851f4536 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
+  97cd99629af4d20fc3ccb0989d36294a85dd026d 17318610d72be0981692fb3f02ef1bf30191367e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
+  78da2467366f01b547bd1fc39bcfcc5917be7caa 90c34d9f99aafcc26de07beb0b3c85856de0fa46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
+  6646bdc8cb87942886d4b8548bc243aaacf94b61 3603891aa2fe38371f226d956f39252576155fba 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
+  6044ea1ba1f7e573bc33312518cd005c45e2ad7d 0a3ac1989f8bde7725203cba7a812e547af916fc 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
+  03ec02fb9548883827e62560d0b1e786851f4536 c9b07601c2f4a8c6c0bd8fe292d6d2e85b872d56 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
+  c9b07601c2f4a8c6c0bd8fe292d6d2e85b872d56 eed7b08171dd3b5a9359789531abc0bed2161580 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  90c34d9f99aafcc26de07beb0b3c85856de0fa46 7fae9524de068a420f7fa9f262669c461ada141f 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  77174443ad6187ba2a216529d7c9785b5f309b70 cc55835562515ff390751032f9e735606c549e96 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  7fae9524de068a420f7fa9f262669c461ada141f e2ca321d00b4adb62525539578290e49e662da79 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
+  e2ca321d00b4adb62525539578290e49e662da79 744c2a22b7da8397bcbd2d472911d51404e54a38 ff95a51a90b9c6710e71bc8ea62c382a5d45e500 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'split', 'user': 'test'}
   $ hg --config extensions.evolve= obslog --all
-  o  dde94df880e9 (21) c_G
-  |    split(parent, content) from b24bab30ac12 using split by test (Thu Jan 01 00:00:00 1970 +0000)
+  o  744c2a22b7da (21) c_G
+  |    split(parent, content) from e2ca321d00b4 using split by test (Thu Jan 01 00:00:00 1970 +0000)
   |
-  | @  e7ea874afbd5 (22) c_G
-  |/     split(parent, content) from b24bab30ac12 using split by test (Thu Jan 01 00:00:00 1970 +0000)
+  | @  ff95a51a90b9 (22) c_G
+  |/     split(parent, content) from e2ca321d00b4 using split by test (Thu Jan 01 00:00:00 1970 +0000)
   |
-  x  b24bab30ac12 (20) c_G
-  |    amended(content) from 907f7d3c2333 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
+  x  e2ca321d00b4 (20) c_G
+  |    amended(content) from 7fae9524de06 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
   |
-  x  907f7d3c2333 (18) c_G
-  |    rebased(parent) from 3ab2eedae500 using rebase by test (Thu Jan 01 00:00:00 1970 +0000)
+  x  7fae9524de06 (18) c_G
+  |    rebased(parent) from 90c34d9f99aa using rebase by test (Thu Jan 01 00:00:00 1970 +0000)
   |
-  x  3ab2eedae500 (13) c_G
-  |    reauthored(user) from c7d60a180d05 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
+  x  90c34d9f99aa (13) c_G
+  |    reauthored(user) from 78da2467366f using amend by test (Thu Jan 01 00:00:00 1970 +0000)
   |
-  x  c7d60a180d05 (6) c_G
+  x  78da2467366f (6) c_G
   
   $ hg export .
   # HG changeset patch
   # User test3
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID e7ea874afbd5c17aeee366d39a828dbcb01682ce
-  # Parent  dde94df880e97f4a1ee8c5408254b429b3d90204
+  # Node ID ff95a51a90b9c6710e71bc8ea62c382a5d45e500
+  # Parent  744c2a22b7da8397bcbd2d472911d51404e54a38
+  # EXP-Topic-Namespace my-tns
   # EXP-Topic blue
   c_G
   
-  diff -r dde94df880e9 -r e7ea874afbd5 ggg
+  diff -r 744c2a22b7da -r ff95a51a90b9 ggg
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/ggg	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
@@ -1018,12 +1022,13 @@
   # User test3
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID dde94df880e97f4a1ee8c5408254b429b3d90204
-  # Parent  f3328cd199dc389b850ca952f65a15a8e6dbc79b
+  # Node ID 744c2a22b7da8397bcbd2d472911d51404e54a38
+  # Parent  eed7b08171dd3b5a9359789531abc0bed2161580
+  # EXP-Topic-Namespace my-tns
   # EXP-Topic blue
   c_G
   
-  diff -r f3328cd199dc -r dde94df880e9 Z
+  diff -r eed7b08171dd -r 744c2a22b7da Z
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/Z	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
--- a/tests/test-topic.t	Wed Aug 16 15:11:43 2023 -0300
+++ b/tests/test-topic.t	Wed Oct 18 16:37:08 2023 -0300
@@ -848,6 +848,10 @@
   abort: topic 'nonsense' does not exist
   [10]
 
+Sanity checks for topicnamespace() revset
+
+  $ tlog 'topicnamespace()'
+
 Deactivate the topic.
   $ hg topics
    * fran (1 changesets)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testlib/retain-extras-ext.py	Wed Oct 18 16:37:08 2023 -0300
@@ -0,0 +1,16 @@
+"""
+Wrap 'retained_extras_on_rebase' (from either mercurial or evolve) to retain
+the "useful" extra.
+"""
+
+from mercurial import rewriteutil
+
+try:
+    rewriteutil.retained_extras_on_rebase
+except AttributeError:
+    # install the compatibility layer on older version
+    from hgext3rd.evolve import compat
+    compat.retained_extras_on_rebase # silence linter
+
+def extsetup(ui):
+    rewriteutil.retained_extras_on_rebase.add(b'useful')