--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/topic/server.py Wed Feb 19 01:35:23 2020 +0100
@@ -0,0 +1,98 @@
+# topic/server.py - server specific behavior with topic
+#
+# 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 import (
+ extensions,
+ repoview,
+ repoviewutil,
+ wireprototypes,
+ wireprotov1peer,
+ wireprotov1server,
+)
+
+from . import (
+ common,
+ constants,
+)
+
+### Visibility restriction
+#
+# Serving draft changesets with topics to clients without topic extension can
+# confuse them, because they won't see the topic label and will consider them
+# normal anonymous heads. Instead we have the option to not serve changesets
+# with topics to clients without topic support.
+#
+# To achieve this, we alter the behavior of the standard `heads` commands and
+# introduce a new `heads` command that only clients with topic will know about.
+
+# compat version of the wireprotocommand decorator, taken from evolve compat
+
+FILTERNAME = b'served-no-topic'
+
+def computeunservedtopic(repo, visibilityexceptions=None):
+ assert not repo.changelog.filteredrevs
+ filteredrevs = repoview.filtertable[b'served'](repo, visibilityexceptions).copy()
+ mutable = repoview.filtertable[b'immutable'](repo, visibilityexceptions)
+ consider = mutable - filteredrevs
+ cl = repo.changelog
+ extrafiltered = set()
+ for r in consider:
+ if cl.changelogrevision(r).extra.get(constants.extrakey, b''):
+ extrafiltered.add(r)
+ if extrafiltered:
+ filteredrevs = frozenset(filteredrevs | extrafiltered)
+ return filteredrevs
+
+def wireprotocommand(name, args=b'', permission=b'pull'):
+ try:
+ from mercurial.wireprotov1server import wireprotocommand
+ except (ImportError, AttributeError):
+ # hg <= 4.6 (b4d85bc122bd)
+ from mercurial.wireproto import wireprotocommand
+ return wireprotocommand(name, args, permission=permission)
+
+def wrapheads(orig, repo, proto):
+ """wrap head to hide topic^W draft changeset to old client"""
+ hidetopics = repo.ui.configbool(b'experimental', b'topic.server-gate-topic-changesets')
+ if common.hastopicext(repo) and hidetopics:
+ h = repo.filtered(FILTERNAME).heads()
+ return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + b'\n')
+ return orig(repo, proto)
+
+def topicheads(repo, proto):
+ """Same as the normal wireprotocol command, but accessing with a different end point."""
+ h = repo.heads()
+ 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)
+ if common.hastopicext(repo) and repo.peer().capable(b'topics'):
+ caps.append(b'_exttopics_heads')
+ return caps
+
+def setupserver(ui):
+ extensions.wrapfunction(wireprotov1server, 'heads', wrapheads)
+ wireprotov1server.commands.pop(b'heads')
+ wireprotocommand(b'heads', permission=b'pull')(wireprotov1server.heads)
+ wireprotocommand(b'_exttopics_heads', permission=b'pull')(topicheads)
+ extensions.wrapfunction(wireprotov1server, '_capabilities', wireprotocaps)
+
+ class topicpeerexecutor(wireprotov1peer.peerexecutor):
+
+ def callcommand(self, command, args):
+ if command == b'heads':
+ if 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
+ s = super(topicpeerexecutor, self)
+ return s.callcommand(command, args)
+
+ wireprotov1peer.peerexecutor = topicpeerexecutor
+
+ if FILTERNAME not in repoview.filtertable:
+ repoview.filtertable[FILTERNAME] = computeunservedtopic
+ repoviewutil.subsettable[FILTERNAME] = b'immutable'
+ repoviewutil.subsettable[b'served'] = FILTERNAME