hgext3rd/topic/server.py
changeset 5139 19b8ffd23795
child 5140 c705c4069fb1
--- /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