Commit 453861da authored by Anton Shestakov's avatar Anton Shestakov
Browse files

topic: use fully qualified branch name during exchange

For the entire duration of exchange process, we now use branch//namespace/topic
format for branchmap, head checking, etc.

Sometimes we still need to disable this feature (e.g. when pushing to a repo
that doesn't have topic extension enabled), but every other case should be
handled using the new fqbn format. This applies both to internal API, such as
having fqbn as branchmap keys and even making ctx.branch() return fqbn, and to
UI, such as users seeing fqbn in hg branches output and messages from hg push.

Things to note: now we're wrapping revbranchcache.branchinfo() during the
extension setup, instead of doing it only in one place in _headssummary().
We're also using override_context_branch context manager that makes
ctx.branch() return fqbn also in cases when we call the original
_headssummary().

There are still places in the UI where branch names are not in fqbn format,
because they don't take part in the exchange process.
parent 394c795d7ba4
Pipeline #58976 passed with stage
in 17 minutes and 29 seconds
......@@ -260,6 +260,15 @@
configitem(b'_internal', b'keep-topic',
default=False,
)
# used for signaling that ctx.branch() shouldn't return fqbn even if topic is
# enabled for local repo
configitem(b'_internal', b'tns-disable-fqbn',
default=False,
)
# used for signaling that push will publish changesets
configitem(b'_internal', b'tns-publish',
default=False,
)
configitem(b'experimental', b'topic-mode.server',
default=configitems.dynamicdefault,
)
......
......@@ -6,6 +6,7 @@
from mercurial.i18n import _
from mercurial import (
branchmap,
bundle2,
discovery,
encoding,
......@@ -40,7 +41,7 @@
def branch():
branch = oldbranch()
if rev in publishedset:
return branch
return common.formatfqbn(branch=branch)
return ctx.fqbn()
def parents():
......@@ -53,7 +54,7 @@
def branch():
branch = pbranch()
if p.rev() in publishedset:
return branch
return common.formatfqbn(branch=branch)
return p.fqbn()
p.branch = branch
p._topic_ext_branch_hack = True
......@@ -63,22 +64,6 @@
ctx.parents = parents
return ctx
def revbranchcache(self):
rbc = super(repocls, self).revbranchcache()
localchangelog = self.changelog
def branchinfo(rev, changelog=None):
if changelog is None:
changelog = localchangelog
branch, close = changelog.branchinfo(rev)
if rev in publishedset:
return branch, close
branch = unfi[rev].fqbn()
return branch, close
rbc.branchinfo = branchinfo
return rbc
oldrepocls = unfi.__class__
try:
unfi.__class__ = repocls
......@@ -95,15 +80,6 @@
repo = pushop.repo.unfiltered()
remote = pushop.remote
publishing = (b'phases' not in remote.listkeys(b'namespaces')
or bool(remote.listkeys(b'phases').get(b'publishing', False)))
if not common.hastopicext(pushop.repo):
return orig(pushop, *args, **kwargs)
elif ((publishing or not remote.capable(b'topics'))
and not getattr(pushop, 'publish', False)):
return orig(pushop, *args, **kwargs)
publishedset = ()
remotebranchmap = None
if remote.capable(b'topics-namespaces'):
......@@ -115,6 +91,28 @@
publishednode,
pushop.remotephases.publicheads)
publishing = (b'phases' not in remote.listkeys(b'namespaces')
or bool(remote.listkeys(b'phases').get(b'publishing', False)))
# remote repo may be non-publishing, but if user does hg push --publish, we
# still need to consider push operation publishing
publishing = publishing or pushop.publish
ctxoverride = util.nullcontextmanager()
if common.hastopicext(pushop.repo) and remote.capable(b'topics'):
ctxoverride = override_context_branch(repo, publishedset=publishedset)
overrides = {(b'_internal', b'tns-publish'): publishing}
else:
overrides = {(b'_internal', b'tns-disable-fqbn'): True}
configoverride = repo.ui.configoverride(overrides, b'topic-namespaces')
if not common.hastopicext(pushop.repo):
with ctxoverride, configoverride:
return orig(pushop, *args, **kwargs)
elif ((publishing or not remote.capable(b'topics'))
and not getattr(pushop, 'publish', False)):
with ctxoverride, configoverride:
return orig(pushop, *args, **kwargs)
getrev = compat.getgetrev(repo.unfiltered().changelog)
def remotebranchmap():
......@@ -128,10 +126,10 @@
for h in heads:
r = getrev(h)
if r is not None and r in publishedset:
result[namedbranch].append(h)
result[common.formatfqbn(branch=namedbranch)].append(h)
else:
result[branch].append(h)
for heads in result.values():
heads.sort()
return result
......@@ -132,10 +130,10 @@
else:
result[branch].append(h)
for heads in result.values():
heads.sort()
return result
with override_context_branch(repo, publishedset=publishedset):
with ctxoverride, configoverride:
try:
if remotebranchmap is not None:
remote.branchmap = remotebranchmap
......@@ -344,6 +342,22 @@
return {}, decode
def wrapbranchinfo(orig, self, rev):
b, close = orig(self, rev)
if common.hastopicext(self._repo):
if self._repo.ui.configbool(b'_internal', b'tns-disable-fqbn'):
# the config option prevents this function from doing anything,
# this happens when e.g. the remote repo doesn't have topic
# extension enabled
pass
elif self._repo.ui.configbool(b'_internal', b'tns-publish'):
# when this rev gets published, only branch will stay
b = common.formatfqbn(branch=b)
else:
ctx = self._repo[rev]
b = ctx.fqbn()
return b, close
def modsetup(ui):
"""run at uisetup time to install all destinations wrapping"""
extensions.wrapfunction(discovery, '_headssummary', _headssummary)
......@@ -353,6 +367,7 @@
extensions.wrapfunction(wireprotov1server, '_capabilities', wireprotocaps)
wirepeer.branchmaptns = batchable(branchmaptns)
wireprotov1server.wireprotocommand(b'branchmaptns', permission=b'pull')(wireprotobranchmaptns)
extensions.wrapfunction(branchmap.revbranchcache, 'branchinfo', wrapbranchinfo)
# we need a proper wrap b2 part stuff
extensions.wrapfunction(bundle2, 'handlecheckheads', handlecheckheads)
bundle2.handlecheckheads.params = frozenset()
......
......@@ -178,6 +178,18 @@
new.phaseshash = self.phaseshash
return new
def load(self, repo, lineiter):
"""call branchmap.load(), and then transform branch names to be in the
new "//" format
"""
super(_topiccache, self).load(repo, lineiter)
entries = compat.bcentries(self)
for branch in tuple(entries):
formatted = common.formatfqbn(branch=branch)
if branch != formatted:
entries[formatted] = entries.pop(branch)
def validfor(self, repo):
"""Is the cache content valid regarding a repo
......@@ -198,6 +210,9 @@
return False
def write(self, repo):
"""write cache to disk if it's not topic-only, but first transform
cache keys from branches in "//" format into bare branch names
"""
# we expect mutable set to be small enough to be that computing it all
# the time will be fast enough
if not istopicfilter(repo.filtername):
......@@ -201,7 +216,15 @@
# we expect mutable set to be small enough to be that computing it all
# the time will be fast enough
if not istopicfilter(repo.filtername):
super(_topiccache, self).write(repo)
cache = self.copy()
entries = compat.bcentries(cache)
for formatted in tuple(entries):
branch, tns, topic = common.parsefqbn(formatted)
if branch != formatted:
entries[branch] = entries.pop(formatted)
super(_topiccache, cache).write(repo)
def update(self, repo, revgen):
"""Given a branchhead cache, self, that may have extra nodes or be
......@@ -210,6 +233,4 @@
"""
if not istopicfilter(repo.filtername):
return super(_topiccache, self).update(repo, revgen)
unfi = repo.unfiltered()
oldgetbranchinfo = unfi.revbranchcache().branchinfo
......@@ -215,14 +236,11 @@
def branchinfo(r, changelog=None):
info = oldgetbranchinfo(r)
ctx = unfi[r]
branch = info[0]
if ctx.mutable():
branch = ctx.fqbn()
return (branch, info[1])
try:
unfi.revbranchcache().branchinfo = branchinfo
super(_topiccache, self).update(repo, revgen)
self.phaseshash = _phaseshash(repo, self.tiprev)
finally:
unfi.revbranchcache().branchinfo = oldgetbranchinfo
# See topic.discovery._headssummary(), where repo.unfiltered gets
# overridden to return .filtered('unfiltered-topic'). revbranchcache
# only can be created for unfiltered repo (filtername is None), so we
# do that here, and this revbranchcache will be cached inside repo.
# When we get rid of *-topic filters, then this workaround can be
# removed too.
repo.unfiltered().revbranchcache()
super(_topiccache, self).update(repo, revgen)
self.phaseshash = _phaseshash(repo, self.tiprev)
......@@ -111,7 +111,7 @@
$ hg push
pushing to $TESTTMP/main
searching for changes
abort: push creates new remote branches: double//slash
abort: push creates new remote branches: double//slash//
(use 'hg push --new-branch' to create new remote branches)
[20]
$ hg push --new-branch
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment