# HG changeset patch # User Anton Shestakov # Date 1658737812 -14400 # Node ID a2855aff12688c7f70300e1ab4c87fb622f3d6f5 # Parent e81b3516242f092208df55d9060cd8d1d8826f2a topic: call a different wire protocol command to get tns-aware branchmap To see if remote supports topic namespaces, we announce topics-namespaces capability and then check it. If it's supported, then we can use a different branchmap command that gives us branchmap keyed by fqbn. The support for old "branch:topic" format for the keys is continued through the already existing code. diff -r e81b3516242f -r a2855aff1268 hgext3rd/topic/__init__.py --- a/hgext3rd/topic/__init__.py Wed Jul 27 20:23:08 2022 +0400 +++ b/hgext3rd/topic/__init__.py Mon Jul 25 12:30:12 2022 +0400 @@ -503,6 +503,7 @@ def _restrictcapabilities(self, caps): caps = super(topicrepo, self)._restrictcapabilities(caps) caps.add(b'topics') + caps.add(b'topics-namespaces') if self.ui.configbool(b'phases', b'publish'): mode = b'all' elif self.ui.configbool(b'experimental', @@ -592,6 +593,15 @@ entries[key] = value return bm + def branchmaptns(self, topic=None): + """branchmap using fqbn as keys""" + if topic is None: + topic = getattr(self, '_autobranchmaptopic', False) + topicfilter = topicmap.topicfilter(self.filtername) + if not topic or topicfilter == self.filtername: + return super(topicrepo, self).branchmap() + return self.filtered(topicfilter).branchmap() + def branchheads(self, branch=None, start=None, closed=False): if branch is None: branch = self[None].branch() @@ -613,6 +623,10 @@ def branchmap(self): usetopic = not self._repo.publishing() return self._repo.branchmap(topic=usetopic, convertbm=usetopic) + + def branchmaptns(self): + usetopic = not self._repo.publishing() + return self._repo.branchmaptns(topic=usetopic) peer.__class__ = topicpeer return peer diff -r e81b3516242f -r a2855aff1268 hgext3rd/topic/common.py --- a/hgext3rd/topic/common.py Wed Jul 27 20:23:08 2022 +0400 +++ b/hgext3rd/topic/common.py Mon Jul 25 12:30:12 2022 +0400 @@ -80,3 +80,26 @@ result += namespace + b'/' result += topic return result + +def upgradeformat(branch): + """take branch and topic in ":" format and return fqbn in "//" format + + This function can be used for transforming branchmap contents of peers that + don't support topic namespaces yet to work with peers with topic namespaces + support. + + >>> upgradeformat(b'branch') + 'branch' + >>> upgradeformat(b'branch:topic') + 'branch//topic' + >>> upgradeformat(b'branch//') + 'branch////' + >>> upgradeformat(b'branch//:topic') + 'branch////topic' + """ + if b':' not in branch: + # formatting anyway, because named branch could contain "//" + return formatfqbn(branch=branch) + # topic namespace cannot be extracted from ":" format + branch, topic = branch.split(b':', 1) + return formatfqbn(branch=branch, topic=topic) diff -r e81b3516242f -r a2855aff1268 hgext3rd/topic/discovery.py --- a/hgext3rd/topic/discovery.py Wed Jul 27 20:23:08 2022 +0400 +++ b/hgext3rd/topic/discovery.py Mon Jul 25 12:30:12 2022 +0400 @@ -8,18 +8,22 @@ from mercurial import ( bundle2, discovery, + encoding, error, exchange, extensions, scmutil, util, + wireprototypes, + wireprotov1server, ) +from mercurial.wireprotov1peer import batchable, wirepeer from . import ( common, compat, ) -from mercurial import wireprotov1server +urlreq = util.urlreq @contextlib.contextmanager def override_context_branch(repo, publishedset=()): @@ -102,7 +106,10 @@ publishedset = () remotebranchmap = None - origremotebranchmap = remote.branchmap + if remote.capable(b'topics-namespaces'): + origremotebranchmap = remote.branchmaptns + else: + origremotebranchmap = remote.branchmap publishednode = [c.node() for c in pushop.outdatedphases] publishedset = repo.revs(b'ancestors(%ln + %ln)', publishednode, @@ -113,19 +120,17 @@ def remotebranchmap(): # drop topic information from changeset about to be published result = collections.defaultdict(list) - for branch, heads in compat.branchmapitems(origremotebranchmap()): - if b':' not in branch: - result[branch].extend(heads) - else: - namedbranch, topic = branch.split(b':', 1) - # we lost namespace when converting to ":" format - branch = common.formatfqbn(namedbranch, topic=topic) - for h in heads: - r = getrev(h) - if r is not None and r in publishedset: - result[namedbranch].append(h) - else: - result[branch].append(h) + items = list(compat.branchmapitems(origremotebranchmap())) + if not remote.capable(b'topics-namespaces'): + items = [(common.upgradeformat(branch), heads) for branch, heads in items] + for branch, heads in items: + namedbranch, tns, topic = common.parsefqbn(branch) + for h in heads: + r = getrev(h) + if r is not None and r in publishedset: + result[namedbranch].append(h) + else: + result[branch].append(h) for heads in result.values(): heads.sort() return result @@ -160,6 +165,12 @@ def branchmap(self): usetopic = not self.publishing() return super(repocls, self).branchmap(topic=usetopic, convertbm=usetopic) + + # Where is branchmaptns method, you might ask? The answer is that + # this repocls is only relevant when we're trying to use the old + # branchmap server command. If we use branchmaptns command that was + # introduced as a part of topic namespaces support, then this + # repocls shouldn't be used at all. unfi.__class__ = repocls if repo.filtername is not None: repo = unfi.filtered(repo.filtername) @@ -169,6 +180,19 @@ finally: unfi.__class__ = oldrepocls +def wireprotobranchmaptns(repo, proto): + """copied from wireprotov1server.branchmap()""" + if not common.hastopicext(repo): + return wireprotov1server.branchmap(repo, proto) + branchmaptns = repo.branchmaptns() + heads = [] + for branch, nodes in branchmaptns.items(): + branchname = urlreq.quote(encoding.fromlocal(branch)) + branchnodes = wireprototypes.encodelist(nodes) + heads.append(b'%s %s' % (branchname, branchnodes)) + + return wireprototypes.bytesresponse(b'\n'.join(heads)) + def _get_branch_name(ctx): # make it easy for extension with the branch logic there return ctx.branch() @@ -301,8 +325,29 @@ caps = orig(repo, proto) if common.hastopicext(repo) and repo.peer().capable(b'topics'): caps.append(b'topics') + caps.append(b'topics-namespaces') return caps +def branchmaptns(self): + """copied from wirepeer.branchmap() + + Client-side command for communicating with a peer repository. Calls wire + protocol command of the same name (thanks to the batchable decorator). + """ + def decode(d): + try: + branchmap = {} + for branchpart in d.splitlines(): + branchname, branchheads = branchpart.split(b' ', 1) + branchname = encoding.tolocal(urlreq.unquote(branchname)) + branchheads = wireprototypes.decodelist(branchheads) + branchmap[branchname] = branchheads + return branchmap + except TypeError: + self._abort(error.ResponseError(_(b"unexpected response:"), d)) + + return {}, decode + def modsetup(ui): """run at uisetup time to install all destinations wrapping""" extensions.wrapfunction(discovery, '_headssummary', _headssummary) @@ -310,6 +355,8 @@ wireprotov1server.commands.pop(b'branchmap') wireprotov1server.wireprotocommand(b'branchmap', permission=b'pull')(wireprotov1server.branchmap) extensions.wrapfunction(wireprotov1server, '_capabilities', wireprotocaps) + wirepeer.branchmaptns = batchable(branchmaptns) + wireprotov1server.wireprotocommand(b'branchmaptns', permission=b'pull')(wireprotobranchmaptns) # we need a proper wrap b2 part stuff extensions.wrapfunction(bundle2, 'handlecheckheads', handlecheckheads) bundle2.handlecheckheads.params = frozenset() diff -r e81b3516242f -r a2855aff1268 tests/test-extension-isolation.t --- a/tests/test-extension-isolation.t Wed Jul 27 20:23:08 2022 +0400 +++ b/tests/test-extension-isolation.t Mon Jul 25 12:30:12 2022 +0400 @@ -134,6 +134,7 @@ _exttopics_heads ext-topics-publish=all topics + topics-namespaces $ hg debugcapabilities http://$LOCALIP:$HGPORT/repo-no-ext | egrep 'topics|evoext' [1] @@ -148,6 +149,7 @@ _exttopics_heads ext-topics-publish=all topics + topics-namespaces $ hg debugcapabilities http://$LOCALIP:$HGPORT/repo-evo | egrep 'topics|evoext' _evoext_getbundle_obscommon _evoext_obshashrange_v1 @@ -155,6 +157,7 @@ _exttopics_heads ext-topics-publish=all topics + topics-namespaces $ hg debugcapabilities http://$LOCALIP:$HGPORT/repo-evo | egrep 'topics|evoext' _evoext_getbundle_obscommon _evoext_obshashrange_v1 diff -r e81b3516242f -r a2855aff1268 tests/test-topic-flow-publish-bare.t --- a/tests/test-topic-flow-publish-bare.t Wed Jul 27 20:23:08 2022 +0400 +++ b/tests/test-topic-flow-publish-bare.t Mon Jul 25 12:30:12 2022 +0400 @@ -350,16 +350,19 @@ $ hg debugcapabilities $TESTTMP/bare-branch-server | grep topics ext-topics-publish=auto topics + topics-namespaces $ hg debugcapabilities ssh://user@dummy/bare-branch-server | grep topics _exttopics_heads ext-topics-publish=auto topics + topics-namespaces $ hg serve -R ../bare-branch-server -p $HGPORT -d --pid-file hg.pid $ cat hg.pid >> $DAEMON_PIDS $ hg debugcapabilities http://localhost:$HGPORT | grep topics _exttopics_heads ext-topics-publish=auto topics + topics-namespaces $ killdaemons.py Trying to push changeset without topic (would publish them)