changeset 6690:6c653a3160ff mercurial-5.3

test-compat: merge mercurial-5.4 into mercurial-5.3
author Anton Shestakov <av6@dwimlabs.net>
date Sun, 04 Feb 2024 20:40:31 -0300
parents 3194a80b96d3 (current diff) 4018000cdb3e (diff)
children ff935fca6cb1 3cd6d29c90c4
files tests/test-namespaces.t tests/test-topic.t
diffstat 15 files changed, 365 insertions(+), 122 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Sat Oct 14 14:55:43 2023 -0300
+++ b/.hgtags	Sun Feb 04 20:40:31 2024 -0300
@@ -106,3 +106,4 @@
 eb221b2c1f8196effb48a61d9d83565b878308fd 11.0.0
 33f24dd8cfa2c57a7da2fa6757ba41ffc69da794 11.0.1
 b9355f6f3093c0cf9698215f05059321880f28da 11.0.2
+a625eb5acea4d682becd21759170306ab769afb2 11.1.0
--- a/CHANGELOG	Sat Oct 14 14:55:43 2023 -0300
+++ b/CHANGELOG	Sun Feb 04 20:40:31 2024 -0300
@@ -1,13 +1,17 @@
 Changelog
 =========
 
-11.1.0 - in progress
+11.1.0 -- 2023-10-23
 --------------------
 
   * 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
+  * evolve: add obsdiff as alternative for odiff
+  * evolve: retain certain commit extras when relocating a commit
+
+  * pick: update commit message hashes like other rewrite commands
 
   * evolve, pullbundle: drop compatibility with Mercurial 4.8
 
@@ -16,8 +20,6 @@
   * remove deprecated serverminitopic extension, topic extension is recommended
     for all users, clients and servers
 
-  * pick: update commit message hashes like other rewrite commands
-
   * 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
--- a/debian/changelog	Sat Oct 14 14:55:43 2023 -0300
+++ b/debian/changelog	Sun Feb 04 20:40:31 2024 -0300
@@ -1,3 +1,9 @@
+mercurial-evolve (11.1.0-1) unstable; urgency=medium
+
+  * new upstream release
+
+ -- Anton Shestakov <av6@dwimlabs.net>  Mon, 23 Oct 2023 13:35:40 -0300
+
 mercurial-evolve (11.0.2-1) unstable; urgency=medium
 
   * new upstream release
--- a/hgext3rd/evolve/__init__.py	Sat Oct 14 14:55:43 2023 -0300
+++ b/hgext3rd/evolve/__init__.py	Sun Feb 04 20:40:31 2024 -0300
@@ -810,7 +810,7 @@
             if topic and _getcurrenttopic(repo) != _gettopic(target):
                 configoverride = repo.ui.configoverride({
                     (b'_internal', b'keep-topic'): b'yes'
-                }, source=b'topic-extension')
+                }, source=b'previous')
             with configoverride:
                 _prevupdate(repo, display, target, bookmark, dryrunopt,
                             mergeopt)
--- a/hgext3rd/evolve/cmdrewrite.py	Sat Oct 14 14:55:43 2023 -0300
+++ b/hgext3rd/evolve/cmdrewrite.py	Sun Feb 04 20:40:31 2024 -0300
@@ -540,10 +540,10 @@
     fp.seek(0)
     oldnode = node.short(old.node())
     message = b'temporary commit for uncommiting %s' % oldnode
-    tempnode = _patchtocommit(ui, repo, old, fp, message, oldnode)
+    tempnode = _patchtocommit(ui, repo, old, fp, message)
     return tempnode
 
-def _patchtocommit(ui, repo, old, fp, message=None, extras=None):
+def _patchtocommit(ui, repo, old, fp, message=None):
     """ A function which will apply the patch to the working directory and
     make a commit whose parents are same as that of old argument. The message
     argument tells us whether to use the message of the old commit or a
@@ -554,9 +554,9 @@
     date = old.date()
     branch = old.branch()
     user = old.user()
-    extra = old.extra()
-    if extras:
-        extra[b'uncommit_source'] = extras
+    extra = old.extra().copy()
+    extra[b'uncommit_source'] = node.short(old.node())
+
     if not message:
         message = old.description()
     store = patch.filestore()
@@ -1354,7 +1354,8 @@
 def cmdpick(ui, repo, *revs, **opts):
     """move a commit onto the working directory parent and update to it.
 
-    If there is an active topic, it will be used for the resulting changeset.
+    The resulting changeset will have the current active topic. If there's no
+    active topic set, the resulting changeset will also not have any topic.
     """
 
     cont = opts.get('continue')
@@ -1416,7 +1417,10 @@
 
 def _dopick(ui, repo, pickstate, origctx):
     """shared logic for performing or continuing a pick"""
-    overrides = {(b'phases', b'new-commit'): origctx.phase()}
+    overrides = {
+        (b'phases', b'new-commit'): origctx.phase(),
+        (b'_internal', b'topic-source'): b'local',
+    }
     new_desc = evolvecmd._rewrite_commit_message_hashes(repo,
                                                         origctx.description())
     with repo.ui.configoverride(overrides, b'pick'):
--- a/hgext3rd/evolve/metadata.py	Sat Oct 14 14:55:43 2023 -0300
+++ b/hgext3rd/evolve/metadata.py	Sun Feb 04 20:40:31 2024 -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.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'
+__version__ = b'11.1.1.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 6.6'
 minimumhgversion = b'4.9'
 buglink = b'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/rewriteutil.py	Sat Oct 14 14:55:43 2023 -0300
+++ b/hgext3rd/evolve/rewriteutil.py	Sun Feb 04 20:40:31 2024 -0300
@@ -234,7 +234,7 @@
     return repomarks, revs
 
 try:
-    from mercural import mergestate
+    from mercurial import mergestate
     mergestate.memmergestate
     hasmemmergestate = True
 except (ImportError, AttributeError):
--- a/hgext3rd/topic/__init__.py	Sat Oct 14 14:55:43 2023 -0300
+++ b/hgext3rd/topic/__init__.py	Sun Feb 04 20:40:31 2024 -0300
@@ -157,6 +157,7 @@
 
 from __future__ import absolute_import
 
+import errno
 import functools
 import re
 import time
@@ -236,9 +237,9 @@
               b'log.topic': b'green_background',
               }
 
-__version__ = b'1.1.0.dev0'
+__version__ = b'1.1.1.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'
+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 6.6'
 minimumhgversion = b'4.9'
 buglink = b'https://bz.mercurial-scm.org/'
 
@@ -277,6 +278,12 @@
 configitem(b'_internal', b'tns-explicit-target',
            default=False,
 )
+# used for selecting what topic and topic namespace values take priority during
+# some history rewriting operations: 'local' prefers active topic and tns,
+# 'other' prefers values in commit extras, if there are any
+configitem(b'_internal', b'topic-source',
+           default=b'other',
+)
 configitem(b'devel', b'tns-report-transactions',
            default=lambda: [],
 )
@@ -516,6 +523,30 @@
     if tr.changes[b'tns']:
         repo.ui.status(b'topic namespaces affected: %s\n' % b' '.join(sorted(tr.changes[b'tns'])))
 
+def wrapmakebundlerepository(orig, ui, repopath, bundlepath):
+    repo = orig(ui, repopath, bundlepath)
+
+    # 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(repo.__class__):
+        @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()
+
+    repo.__class__ = topicbundlerepo
+    return repo
+
 def uisetup(ui):
     destination.modsetup(ui)
     discovery.modsetup(ui)
@@ -601,27 +632,9 @@
     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 {}
+    extensions.wrapfunction(bundlerepo, 'makebundlerepository', wrapmakebundlerepository)
 
-        @util.propertycache
-        def _topiccache(self):
-            return {}
-
-        def invalidatecaches(self):
-            self._tnscache.clear()
-            self._topiccache.clear()
-            super(topicbundlerepo, self).invalidatecaches()
-
-    bundlerepo.bundlerepository = topicbundlerepo
+    server.setupserver(ui)
 
 def reposetup(ui, repo):
     if not isinstance(repo, localrepo.localrepository):
@@ -665,15 +678,26 @@
                 # bypass the core "nothing changed" logic
                 configoverride = self.ui.configoverride({
                     (b'ui', b'allowemptycommit'): True
-                })
+                }, b'topic-extension')
             with configoverride:
                 return super(topicrepo, self).commit(*args, **kwargs)
 
         def commitctx(self, ctx, *args, **kwargs):
             if isinstance(ctx, context.workingcommitctx):
-                current = self.currenttopic
-                if current and constants.extrakey not in ctx.extra():
-                    ctx.extra()[constants.extrakey] = current
+                tns = self.currenttns
+                topic = self.currenttopic
+                # topic source:
+                # - 'local': we need to put currently active tns and topic into
+                #   commit extras in any case
+                # - 'other': we could use active tns and topic, but only if
+                #   commit extras don't already have them
+                ts = self.ui.config(b'_internal', b'topic-source')
+                if ts == b'local' or (tns != b'none' and b'topic-namespace' not in ctx.extra()):
+                    # default value will be dropped from extra later on
+                    ctx.extra()[b'topic-namespace'] = tns
+                if ts == b'local' or (topic and constants.extrakey not in ctx.extra()):
+                    # empty value will be dropped from extra later on
+                    ctx.extra()[constants.extrakey] = topic
             return super(topicrepo, self).commitctx(ctx, *args, **kwargs)
 
         @util.propertycache
@@ -693,7 +717,30 @@
 
         @property
         def currenttns(self):
-            return self.vfs.tryread(b'topic-namespace') or b'none'
+            tns = self.vfs.tryread(b'topic-namespace')
+            # we should definitely drop this at some point, but it depends on
+            # our own release schedule, not core's, so here's hg 1.0
+            # hg <= 1.0 (cfa08c88a5c4)
+            if tns == b'none':
+                try:
+                    with self.wlock(wait=False):
+                        try:
+                            # we make sure the file contains what we expect
+                            if self.vfs.read(b'topic-namespace') == b'none':
+                                repo.vfs.unlinkpath(b'topic-namespace')
+                        except IOError as err:
+                            if err.errno != errno.ENOENT:
+                                raise
+                except error.LockError:
+                    # if we cannot acquire wdir lock, then we shouldn't do
+                    # anything at all, since it'd be unsafe to modify wdir
+                    pass
+            elif tns == b'':
+                # technically, if user creates an empty file, it should be
+                # handled differently than non-existing file, but the
+                # distinction is probably not that important
+                tns = b'none'
+            return encoding.tolocal(tns)
 
         @util.propertycache
         def _topiccache(self):
@@ -712,7 +759,8 @@
 
         @property
         def currenttopic(self):
-            return self.vfs.tryread(b'topic')
+            topic = self.vfs.tryread(b'topic')
+            return encoding.tolocal(topic)
 
         # overwritten at the instance level by topicmap.py
         _autobranchmaptopic = True
@@ -1092,7 +1140,7 @@
         # Have some restrictions on the topic name just like bookmark name
         scmutil.checknewlabel(repo, topic, b'topic')
 
-        helptxt = _(b"topic names can only consist of alphanumeric, '-'"
+        helptxt = _(b"topic names can only consist of alphanumeric, '-',"
                     b" '_' and '.' characters")
         try:
             utopic = encoding.unifromlocal(topic)
@@ -1679,10 +1727,10 @@
             if pctx.phase() > phases.public:
                 tns = pctx.topic_namespace()
                 t = pctx.topic()
-            repo.vfs.write(b'topic-namespace', tns)
+            _changecurrenttns(repo, tns)
             if tns != b'none' and tns != otns:
                 repo.ui.status(_(b"switching to topic-namespace %s\n") % tns)
-            repo.vfs.write(b'topic', t)
+            _changecurrenttopic(repo, t)
             if t and t != ot:
                 repo.ui.status(_(b"switching to topic %s\n") % t)
             if ot and not t:
@@ -1839,6 +1887,18 @@
         if b'/' in tns:
             raise error.Abort(_(b"topic namespace cannot contain '/' character"))
         scmutil.checknewlabel(repo, tns, b'topic namespace')
+
+        helptxt = _(b"topic namespace names can only consist of alphanumeric, "
+                    b"'-', '_' and '.' characters")
+        try:
+            utns = encoding.unifromlocal(tns)
+        except error.Abort:
+            # Maybe we should allow these topic names as well, as long as they
+            # don't break any other rules
+            utns = ''
+        rmatch = re.match(r'[-_.\w]+', utns, re.UNICODE)
+        if not utns or not rmatch or rmatch.group(0) != utns:
+            raise compat.InputError(_(b"invalid topic namespace name: '%s'") % tns, hint=helptxt)
     ctns = repo.currenttns
     _changecurrenttns(repo, tns)
     if ctns == b'none' and tns != b'none':
--- a/hgext3rd/topic/server.py	Sat Oct 14 14:55:43 2023 -0300
+++ b/hgext3rd/topic/server.py	Sun Feb 04 20:40:31 2024 -0300
@@ -108,31 +108,27 @@
 
     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
+        @wireprotov1peer.batchable
+        def wp_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:
+        @wireprotov1peer.batchable
+        def wp_tns_heads(self, namespaces):
+            def decode(d):
                 try:
-                    yield wireprototypes.decodelist(d[:-1])
+                    return 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
+            return {b'namespaces': wireprototypes.encodelist(namespaces)}, decode
 
-    wireprotov1peer.wirepeer = tnspeer
+    wireprotov1peer.wirepeer.tns_heads = wp_tns_heads
 
     class topicpeerexecutor(wireprotov1peer.peerexecutor):
 
--- a/tests/test-namespaces-exchange.t	Sat Oct 14 14:55:43 2023 -0300
+++ b/tests/test-namespaces-exchange.t	Sun Feb 04 20:40:31 2024 -0300
@@ -70,6 +70,9 @@
   marked working directory as topic: apple
   $ hg ci -qAm apple
 
+  $ hg log -r . -T '{rev}: {join(extras, " ")}\n'
+  0: branch=default topic=apple
+
   $ hg incoming -R ../clone
   comparing with * (glob)
   0: apple default//apple (draft)
--- a/tests/test-namespaces.t	Sat Oct 14 14:55:43 2023 -0300
+++ b/tests/test-namespaces.t	Sun Feb 04 20:40:31 2024 -0300
@@ -11,6 +11,8 @@
   marked working directory as topic namespace: space-name
   $ hg debug-topic-namespaces
   space-name
+  $ cat .hg/topic-namespace
+  space-name (no-eol)
 
   $ hg log -r 'wdir()' -T '{topic_namespace}\n'
   none
@@ -31,6 +33,32 @@
   $ hg log -r 'wdir()' -T '{fqbn}\n'
   default//space-name/feature
 
+Non-ascii topic namespace name
+
+  $ hg debug-topic-namespace --clear
+  $ test -f .hg/topic-namespace
+  [1]
+  $ hg --encoding utf-8 debug-topic-namespace æ
+  marked working directory as topic namespace: \xc3\xa6 (esc)
+  $ hg --encoding utf-8 debug-topic-namespaces
+  \xc3\xa6 (esc)
+  $ hg --encoding ascii debug-topic-namespaces
+  ? (esc)
+  $ hg --encoding latin1 debug-topic-namespaces
+  \xe6 (esc)
+  $ cat .hg/topic-namespace
+  \xc3\xa6 (no-eol) (esc)
+
+  $ hg --encoding utf-8 debug-topic-namespace ©
+  abort: invalid topic namespace name: '\xc2\xa9' (esc)
+  (topic namespace names can only consist of alphanumeric, '-', '_' and '.' characters)
+  [255]
+
+  $ hg --encoding latin1 debug-topic-namespace æ
+  abort: invalid topic namespace name: '\xc3\xa6' (esc)
+  (topic namespace names can only consist of alphanumeric, '-', '_' and '.' characters)
+  [255]
+
   $ hg branches
 
   $ hg debug-topic-namespace --clear
@@ -63,25 +91,50 @@
   $ hg log -r . -T '{rev}: {fqbn}\n'
   0: stable//alice/feature
 
+  $ hg log -r . -T '{rev}: {join(extras, " ")}\n'
+  0: branch=stable topic=feature topic-namespace=alice
+
   $ hg branches
   stable//alice/feature          0:69c7dbf6acd1
 
+Removing topic namespace file if it contains the default value
+
+The default value changed from b'default' to b'none' in 11.1.0, this is a
+safeguard against accidentally putting the new default tns value into commit
+extras with an old version of topic extension
+
+  $ printf 'none' > .hg/topic-namespace
+  $ test -f .hg/topic-namespace
+  $ hg ci -m ''
+  nothing changed
+  [1]
+  $ test -f .hg/topic-namespace
+  [1]
+
 Updating to a revision with a namespace should activate it
 
   $ hg up null
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg debug-topic-namespace
   none
+  $ test -f .hg/topic-namespace
+  [1]
   $ hg topics
      feature (1 changesets)
+  $ test -f .hg/topic
+  [1]
   $ hg up 0
   switching to topic-namespace alice
   switching to topic feature
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg debug-topic-namespace
   alice
+  $ cat .hg/topic-namespace
+  alice (no-eol)
   $ hg topics
    * feature (1 changesets)
+  $ cat .hg/topic
+  feature (no-eol)
 
 Updating to a topic namespace is not supported
 
--- a/tests/test-pick.t	Sat Oct 14 14:55:43 2023 -0300
+++ b/tests/test-pick.t	Sun Feb 04 20:40:31 2024 -0300
@@ -32,7 +32,8 @@
   
   move a commit onto the working directory parent and update to it.
   
-      If there is an active topic, it will be used for the resulting changeset.
+      The resulting changeset will have the current active topic. If there's no
+      active topic set, the resulting changeset will also not have any topic.
   
   options:
   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-topic-issue6406.t	Sun Feb 04 20:40:31 2024 -0300
@@ -0,0 +1,104 @@
+hg pick with no active topic and with a different active topic (issue6406)
+https://bz.mercurial-scm.org/show_bug.cgi?id=6406
+For prior discussions on this behavior see also
+https://foss.heptapod.net/mercurial/evolve/-/merge_requests/313
+https://foss.heptapod.net/mercurial/evolve/-/merge_requests/390
+
+  $ . "$TESTDIR/testlib/common.sh"
+
+  $ cat << EOF >> "$HGRCPATH"
+  > [phases]
+  > publish = no
+  > [extensions]
+  > evolve =
+  > topic =
+  > EOF
+
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
+  $ hg init issue6406
+  $ cd issue6406
+
+  $ mkcommit ROOT
+
+  $ hg debug-topic-namespace aaa
+  marked working directory as topic namespace: aaa
+  $ hg topic a-things
+  marked working directory as topic: a-things
+  $ mkcommit apple
+  active topic 'a-things' grew its first changeset
+  (see 'hg help topics' for more information)
+
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg debug-topic-namespace bbb
+  marked working directory as topic namespace: bbb
+  $ hg topic b-things
+  marked working directory as topic: b-things
+  $ mkcommit banana
+  active topic 'b-things' grew its first changeset
+  (see 'hg help topics' for more information)
+  $ mkcommit blackberry
+
+  $ hg up 'desc("apple")'
+  switching to topic-namespace aaa
+  switching to topic a-things
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+
+This is what the help text says about this issue
+
+  $ hg help pick | grep 'active topic'
+      The resulting changeset will have the current active topic. If there's no
+      active topic set, the resulting changeset will also not have any topic.
+
+wdir has no active topic: pick should clear topic of the resulting cset
+
+  $ hg debug-topic-namespace --clear
+  $ hg topic --clear
+  $ hg pick 'desc("banana")'
+  picking 2:fcda3d8dafd2 "banana"
+  1 new orphan changesets
+  $ hg log -r . -T '{rev}: {desc} ({fqbn})\n'
+  4: banana (default)
+  $ hg debug-topic-namespace
+  none
+  $ hg topic --current
+  no active topic
+  [1]
+
+wdir has active topic: pick should use the active topic for the resulting cset
+
+  $ hg debug-topic-namespace everything
+  marked working directory as topic namespace: everything
+  $ hg topic all-things
+  marked working directory as topic: all-things
+  $ hg pick 'desc("blackberry")'
+  picking 3:48bbfbece8fa "blackberry"
+  active topic 'all-things' grew its first changeset
+  (see 'hg help topics' for more information)
+  $ hg log -r . -T '{rev}: {desc} ({fqbn})\n'
+  5: blackberry (default//everything/all-things)
+  $ hg debug-topic-namespace
+  everything
+  $ hg topic --current
+  all-things
+
+  $ hg log -GT '{rev}: {desc} ({fqbn})\n{join(extras, " ")}\n\n'
+  @  5: blackberry (default//everything/all-things)
+  |  branch=default topic=all-things topic-namespace=everything
+  |
+  o  4: banana (default)
+  |  branch=default
+  |
+  o  1: apple (default//aaa/a-things)
+  |  branch=default topic=a-things topic-namespace=aaa
+  |
+  o  0: ROOT (default)
+     branch=default
+  
--- a/tests/test-topic.t	Sat Oct 14 14:55:43 2023 -0300
+++ b/tests/test-topic.t	Sun Feb 04 20:40:31 2024 -0300
@@ -285,12 +285,12 @@
 
   $ hg topic 'a12#45'
   abort: invalid topic name: 'a12#45'
-  (topic names can only consist of alphanumeric, '-' '_' and '.' characters)
+  (topic names can only consist of alphanumeric, '-', '_' and '.' characters)
   [255]
 
   $ hg topic 'foo bar'
   abort: invalid topic name: 'foo bar'
-  (topic names can only consist of alphanumeric, '-' '_' and '.' characters)
+  (topic names can only consist of alphanumeric, '-', '_' and '.' characters)
   [255]
 
 this is trying to list topic names
@@ -298,7 +298,7 @@
 
   $ hg topic '*12 B23'
   abort: invalid topic name: '*12 B23'
-  (topic names can only consist of alphanumeric, '-' '_' and '.' characters)
+  (topic names can only consist of alphanumeric, '-', '_' and '.' characters)
   [255]
 
 Test commit flag and help text
@@ -322,20 +322,25 @@
 
 Non-ascii topic name
 
+  $ hg topic --clear
+  clearing empty topic "topicflag"
   $ hg --encoding utf-8 topic æ
-  $ hg topics
+  marked working directory as topic: \xc3\xa6 (esc)
+  $ hg --encoding utf-8 topics
    * \xc3\xa6 (0 changesets) (esc)
+  $ hg --encoding ascii topics
+   * ? (0 changesets) (esc)
   $ hg --encoding latin1 topics
-   * \xc3\xa6 (0 changesets) (esc)
+   * \xe6 (0 changesets) (esc)
 
   $ hg --encoding utf-8 topic ©
   abort: invalid topic name: '\xc2\xa9' (esc)
-  (topic names can only consist of alphanumeric, '-' '_' and '.' characters)
+  (topic names can only consist of alphanumeric, '-', '_' and '.' characters)
   [255]
 
   $ hg --encoding latin1 topic æ
   abort: invalid topic name: '\xc3\xa6' (esc)
-  (topic names can only consist of alphanumeric, '-' '_' and '.' characters)
+  (topic names can only consist of alphanumeric, '-', '_' and '.' characters)
   [255]
 
 Make a topic
--- a/tests/test-uncommit-interactive.t	Sat Oct 14 14:55:43 2023 -0300
+++ b/tests/test-uncommit-interactive.t	Sun Feb 04 20:40:31 2024 -0300
@@ -176,6 +176,9 @@
   $ hg debugobsolete
   e9635f4beaf11f64a07ccc74684092b144c53d89 0 {7733902a8d94c789ca81d866bea1893d79442db6} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'uncommit', 'user': 'test'}
   f70fb463d5bf9f0ffd38f105521d96e9f2591bc1 678a59e5ff90754d5e94719bd82ad169be773c21 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'note': 'note on amend --extract', 'operation': 'uncommit', 'user': 'test'}
+  $ hg log -l 2 -T '{rev}:{node|short} {join(extras, " ")}\n' --hidden
+  3:678a59e5ff90 branch=default uncommit_source=f70fb463d5bf
+  2:e9635f4beaf1 branch=default uncommit_source=f70fb463d5bf
   $ hg obslog
   @  678a59e5ff90 (3) another one
   |    amended(content) from f70fb463d5bf using uncommit by test (Thu Jan 01 00:00:00 1970 +0000)
@@ -272,7 +275,7 @@
   Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
 
   $ hg diff
-  diff -r 46e35360be47 a
+  diff -r ef651ea03f87 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,3 +1,6 @@
@@ -293,11 +296,11 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 46e35360be473bf761bedf3d05de4a68ffd9d9f8
+  # Node ID ef651ea03f873a6d70aeeb9ac351d4f65c84fb3b
   # Parent  7733902a8d94c789ca81d866bea1893d79442db6
   another one
   
-  diff -r 7733902a8d94 -r 46e35360be47 a
+  diff -r 7733902a8d94 -r ef651ea03f87 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,5 +1,7 @@
@@ -315,9 +318,14 @@
   e9635f4beaf11f64a07ccc74684092b144c53d89 0 {7733902a8d94c789ca81d866bea1893d79442db6} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'uncommit', 'user': 'test'}
   f70fb463d5bf9f0ffd38f105521d96e9f2591bc1 678a59e5ff90754d5e94719bd82ad169be773c21 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'note': 'note on amend --extract', 'operation': 'uncommit', 'user': 'test'}
   7ca9935a62f11b39b60c7fb8861377c7d45b3e99 0 {7733902a8d94c789ca81d866bea1893d79442db6} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'uncommit', 'user': 'test'}
-  678a59e5ff90754d5e94719bd82ad169be773c21 46e35360be473bf761bedf3d05de4a68ffd9d9f8 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'note': 'testing uncommit on dirty wdir', 'operation': 'uncommit', 'user': 'test'}
+  678a59e5ff90754d5e94719bd82ad169be773c21 ef651ea03f873a6d70aeeb9ac351d4f65c84fb3b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'note': 'testing uncommit on dirty wdir', 'operation': 'uncommit', 'user': 'test'}
+  $ hg log -l 4 -T '{rev}:{node|short} {join(extras, " ")}\n' --hidden
+  5:ef651ea03f87 branch=default uncommit_source=678a59e5ff90
+  4:7ca9935a62f1 branch=default uncommit_source=678a59e5ff90
+  3:678a59e5ff90 branch=default uncommit_source=f70fb463d5bf
+  2:e9635f4beaf1 branch=default uncommit_source=f70fb463d5bf
   $ hg obslog
-  @  46e35360be47 (5) another one
+  @  ef651ea03f87 (5) another one
   |    amended(content) from 678a59e5ff90 using uncommit by test (Thu Jan 01 00:00:00 1970 +0000)
   |      note: testing uncommit on dirty wdir
   |
@@ -332,7 +340,7 @@
 
   $ hg amend
   $ glog
-  @  6:905eb2a23ea2@default(draft) another one
+  @  6:f4c93db9c5cd@default(draft) another one
   |
   o  0:7733902a8d94@default(draft) The base commit
   
@@ -364,7 +372,7 @@
   $ hg status
   A foo
   $ hg diff
-  diff -r 857367499298 foo
+  diff -r 665843692be0 foo
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/foo	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
@@ -375,8 +383,8 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 857367499298e999b5841bb01df65f73088b5d3b
-  # Parent  905eb2a23ea2d92073419d0e19165b90d36ea223
+  # Node ID 665843692be04cb0619d8ad1f81ec31c7b33f366
+  # Parent  f4c93db9c5cde0d4ab20badcb9c514cfbf7b9e38
   Added foo
   
   $ hg amend
@@ -391,11 +399,11 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 219cfe20964e93f8bb9bd82ceaa54d3b776046db
-  # Parent  42cc15efbec26c14d96d805dee2766ba91d1fd31
+  # Node ID 24fcae345f93a1161b224f849c3a9ab362f76f44
+  # Parent  3f44e16f88daf37e5798606082ae9895eb90fc4d
   Removed a
   
-  diff -r 42cc15efbec2 -r 219cfe20964e a
+  diff -r 3f44e16f88da -r 24fcae345f93 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,11 +0,0 @@
@@ -444,7 +452,7 @@
   (use 'hg prune .' to remove it)
 
   $ hg diff
-  diff -r 737487f1e5f8 a
+  diff -r 3778ffc6315b a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,11 +0,0 @@
@@ -466,22 +474,22 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 737487f1e5f853e55decb73ea31522c63e7f5980
-  # Parent  42cc15efbec26c14d96d805dee2766ba91d1fd31
+  # Node ID 3778ffc6315b9cefdb01c218413677c23bf5bc9f
+  # Parent  3f44e16f88daf37e5798606082ae9895eb90fc4d
   Removed a
   
 
   $ hg prune .
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  working directory is now at 42cc15efbec2
+  working directory is now at 3f44e16f88da
   1 changesets pruned
   $ hg revert --all
   undeleting a
 
   $ glog
-  @  10:42cc15efbec2@default(draft) Added foo
+  @  10:3f44e16f88da@default(draft) Added foo
   |
-  o  6:905eb2a23ea2@default(draft) another one
+  o  6:f4c93db9c5cd@default(draft) another one
   |
   o  0:7733902a8d94@default(draft) The base commit
   
@@ -527,18 +535,18 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 25a080d13cb23dbd014839f54d99a96e57ba7e9b
-  # Parent  42cc15efbec26c14d96d805dee2766ba91d1fd31
+  # Node ID 5fcbf1c538b13186c920c63ca6a7dab443ad6663
+  # Parent  3f44e16f88daf37e5798606082ae9895eb90fc4d
   Added x
   
-  diff -r 42cc15efbec2 -r 25a080d13cb2 x
+  diff -r 3f44e16f88da -r 5fcbf1c538b1 x
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/x	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
   +abcd
 
   $ hg diff
-  diff -r 25a080d13cb2 foo
+  diff -r 5fcbf1c538b1 foo
   --- a/foo	Thu Jan 01 00:00:00 1970 +0000
   +++ b/foo	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,1 +1,2 @@
@@ -555,15 +563,15 @@
 =====================================================
 
   $ glog
-  @  16:25a080d13cb2@default(draft) Added x
+  @  16:5fcbf1c538b1@default(draft) Added x
   |
-  o  10:42cc15efbec2@default(draft) Added foo
+  o  10:3f44e16f88da@default(draft) Added foo
   |
-  o  6:905eb2a23ea2@default(draft) another one
+  o  6:f4c93db9c5cd@default(draft) another one
   |
   o  0:7733902a8d94@default(draft) The base commit
   
-  $ hg up 905eb2a23ea2
+  $ hg up f4c93db9c5cd
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
 
   $ touch bar
@@ -578,11 +586,11 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 905eb2a23ea2d92073419d0e19165b90d36ea223
+  # Node ID f4c93db9c5cde0d4ab20badcb9c514cfbf7b9e38
   # Parent  7733902a8d94c789ca81d866bea1893d79442db6
   another one
   
-  diff -r 7733902a8d94 -r 905eb2a23ea2 a
+  diff -r 7733902a8d94 -r f4c93db9c5cd a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,5 +1,11 @@
@@ -642,7 +650,7 @@
   2 new orphan changesets
 
   $ hg diff
-  diff -r 676366511f95 a
+  diff -r 98a3d38b1b81 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -8,3 +8,4 @@
@@ -650,7 +658,7 @@
    4
    5
   +babar
-  diff -r 676366511f95 bar
+  diff -r 98a3d38b1b81 bar
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/bar	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
@@ -661,11 +669,11 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 676366511f95ca4122413dcf79b45eaab61fb387
+  # Node ID 98a3d38b1b812aeca00a61a5554dfa228d632b9e
   # Parent  7733902a8d94c789ca81d866bea1893d79442db6
   another one
   
-  diff -r 7733902a8d94 -r 676366511f95 a
+  diff -r 7733902a8d94 -r 98a3d38b1b81 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,5 +1,10 @@
@@ -724,11 +732,11 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 62d907d0c4fa13b4b8bfeed05f13751035daf963
+  # Node ID 9c6818648d9e694d2decfde377c6821191c5bfd5
   # Parent  7733902a8d94c789ca81d866bea1893d79442db6
   another one
   
-  diff -r 7733902a8d94 -r 62d907d0c4fa a
+  diff -r 7733902a8d94 -r 9c6818648d9e a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,5 +1,7 @@
@@ -741,7 +749,7 @@
    5
 
   $ hg diff
-  diff -r 62d907d0c4fa a
+  diff -r 9c6818648d9e a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,3 +1,6 @@
@@ -756,7 +764,7 @@
    4
    5
   +babar
-  diff -r 62d907d0c4fa bar
+  diff -r 9c6818648d9e bar
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/bar	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
@@ -792,11 +800,11 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID be5c67225e80b050867862bbd9f4755c4e9207c5
-  # Parent  c280a907fddcef2ffe9fadcc2d87f29998e22b2f
+  # Node ID bbdfefb59fb08650a9a663367ab18a3c2d072691
+  # Parent  4f15d398b049b07eb4f4c98d3466a7f708e61735
   some more changes
   
-  diff -r c280a907fddc -r be5c67225e80 a
+  diff -r 4f15d398b049 -r bbdfefb59fb0 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -9,3 +9,4 @@
@@ -804,7 +812,7 @@
    5
    babar
   +celeste
-  diff -r c280a907fddc -r be5c67225e80 b
+  diff -r 4f15d398b049 -r bbdfefb59fb0 b
   --- a/b	Thu Jan 01 00:00:00 1970 +0000
   +++ b/b	Thu Jan 01 00:00:00 1970 +0000
   @@ -5,3 +5,4 @@
@@ -830,7 +838,7 @@
   M a
   ? foo.orig
   $ hg diff
-  diff -r c701d7c8d18b a
+  diff -r 0873ba67273f a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -9,3 +9,4 @@
@@ -843,11 +851,11 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID c701d7c8d18be55a92688f4458c26bd74fb1f525
-  # Parent  c280a907fddcef2ffe9fadcc2d87f29998e22b2f
+  # Node ID 0873ba67273ff5654e032c98df89be8cf431cb63
+  # Parent  4f15d398b049b07eb4f4c98d3466a7f708e61735
   some more changes
   
-  diff -r c280a907fddc -r c701d7c8d18b b
+  diff -r 4f15d398b049 -r 0873ba67273f b
   --- a/b	Thu Jan 01 00:00:00 1970 +0000
   +++ b/b	Thu Jan 01 00:00:00 1970 +0000
   @@ -5,3 +5,4 @@
@@ -904,7 +912,7 @@
   ? foo.orig
 
   $ hg diff
-  diff -r 28d5de12b225 a
+  diff -r 72c07d186be7 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,3 +1,4 @@
@@ -918,11 +926,11 @@
   # User test
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
-  # Node ID 28d5de12b225d1e0951110cced8d8994227be026
-  # Parent  c280a907fddcef2ffe9fadcc2d87f29998e22b2f
+  # Node ID 72c07d186be791e6fa80bfdaf85f493dca503df2
+  # Parent  4f15d398b049b07eb4f4c98d3466a7f708e61735
   some more changes
   
-  diff -r c280a907fddc -r 28d5de12b225 a
+  diff -r 4f15d398b049 -r 72c07d186be7 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -9,3 +9,4 @@
@@ -930,7 +938,7 @@
    5
    babar
   +celeste
-  diff -r c280a907fddc -r 28d5de12b225 b
+  diff -r 4f15d398b049 -r 72c07d186be7 b
   --- a/b	Thu Jan 01 00:00:00 1970 +0000
   +++ b/b	Thu Jan 01 00:00:00 1970 +0000
   @@ -5,3 +5,4 @@