changeset 6548:445240ccb701

topic: add experimental.tns-default-pull-namespaces config option This config option controls what topic namespaces get pulled by default. The current default option is '*', which means all namespaces get pulled.
author Anton Shestakov <av6@dwimlabs.net>
date Thu, 27 Jul 2023 16:39:43 -0300
parents d13cfd9eb6c0
children e45bfd1e0588
files hgext3rd/topic/__init__.py hgext3rd/topic/server.py tests/test-extension-isolation.t tests/test-namespaces-exchange.t tests/test-topic-flow-publish-bare.t
diffstat 5 files changed, 229 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/hgext3rd/topic/__init__.py	Wed Aug 30 15:08:35 2023 -0300
+++ b/hgext3rd/topic/__init__.py	Thu Jul 27 16:39:43 2023 -0300
@@ -279,6 +279,9 @@
 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,
 )
@@ -624,6 +627,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):
@@ -740,6 +744,19 @@
                     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 = []
+                            for branch, nodes in self._repo.branchmaptns().items():
+                                namedbranch, tns, topic = common.parsefqbn(branch)
+                                if tns == b'none' or tns in namespaces:
+                                    h.extend(nodes)
+                            return h
                 peer.__class__ = topicpeer
             return peer
 
--- a/hgext3rd/topic/server.py	Wed Aug 30 15:08:35 2023 -0300
+++ b/hgext3rd/topic/server.py	Thu Jul 27 16:39:43 2023 -0300
@@ -2,9 +2,13 @@
 #
 # 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,
     wireprototypes,
     wireprotov1peer,
@@ -58,6 +62,24 @@
     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 = []
+        for branch, nodes in repo.branchmaptns().items():
+            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 +92,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 +100,30 @@
     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)
 
+    class tnspeer(wireprotov1peer.wirepeer):
+        @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'
+                    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 +132,17 @@
 
     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'
+                    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/tests/test-extension-isolation.t	Wed Aug 30 15:08:35 2023 -0300
+++ b/tests/test-extension-isolation.t	Thu Jul 27 16:39:43 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'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-namespaces-exchange.t	Thu Jul 27 16:39:43 2023 -0300
@@ -0,0 +1,154 @@
+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
+  > 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 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 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 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 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 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)
+
+#if http
+  $ $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+  $ cat $TESTTMP/errors.log
+#endif
+
+  $ hg branches
+  default//dave/durian           3:d5d5dda52b2f
+  default//charlie/coconut       2:16d2440597e2 (inactive)
+  default//bob/banana            1:ed9751f04a18 (inactive)
+  default//apple                 0:bf4c1d971543 (inactive)
+
+  $ cd ..
--- a/tests/test-topic-flow-publish-bare.t	Wed Aug 30 15:08:35 2023 -0300
+++ b/tests/test-topic-flow-publish-bare.t	Thu Jul 27 16:39:43 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