hgext3rd/topic/server.py
author Anton Shestakov <av6@dwimlabs.net>
Fri, 28 Feb 2020 23:10:48 +0700
branchmercurial-5.1
changeset 5158 8d20c0af010a
parent 5150 e0c091b199bc
child 5180 515d425c0a05
permissions -rw-r--r--
test-compat: merge mercurial-5.2 into mercurial-5.1

# 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,
    wireprototypes,
    wireprotov1peer,
    wireprotov1server,
)


try:
    from mercurial.utils import (
        repoviewutil,
    )
    repoviewutil.subsettable
except (AttributeError, ImportError):
    # hg <= 4.8
    from mercurial import branchmap as repoviewutil

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:
        extrafiltered = set(repo.revs('%ld::%ld', extrafiltered, consider))
        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