# HG changeset patch # User Pierre-Yves David # Date 1457808136 0 # Node ID 68125d026b0754616f74a71c8779e90d6924686b # Parent 0504e76bfbd90ca254792b4353c3686043c800d3 push: hackish handeling of new branch head from phase move The current head checking mechanism is not expecting "head change" from phase movement. Topic allows that, changeset with a topic moving to public can create a new head. We introduce a hack to double check that no head were added at the transaction level to work around this. diff -r 0504e76bfbd9 -r 68125d026b07 src/topic/__init__.py --- a/src/topic/__init__.py Sat Mar 12 18:19:27 2016 +0000 +++ b/src/topic/__init__.py Sat Mar 12 18:42:16 2016 +0000 @@ -14,11 +14,13 @@ from mercurial.i18n import _ from mercurial import branchmap +from mercurial import bundle2 from mercurial import cmdutil from mercurial import commands from mercurial import context from mercurial import discovery as discoverymod from mercurial import error +from mercurial import exchange from mercurial import extensions from mercurial import localrepo from mercurial import lock @@ -295,6 +297,11 @@ extensions.wrapfunction(merge, 'update', mergeupdatewrap) extensions.wrapfunction(discoverymod, '_headssummary', discovery._headssummary) extensions.wrapfunction(wireproto, 'branchmap', discovery.wireprotobranchmap) +extensions.wrapfunction(bundle2, 'handlecheckheads', discovery.handlecheckheads) +bundle2.handlecheckheads.params = frozenset() # we need a proper wrape b2 part stuff +bundle2.parthandlermapping['check:heads'] = bundle2.handlecheckheads +extensions.wrapfunction(exchange, '_pushb2phases', discovery._pushb2phases) +exchange.b2partsgenmapping['phase'] = exchange._pushb2phases topicrevset.modsetup() cmdutil.summaryhooks.add('topic', summaryhook) diff -r 0504e76bfbd9 -r 68125d026b07 src/topic/discovery.py --- a/src/topic/discovery.py Sat Mar 12 18:19:27 2016 +0000 +++ b/src/topic/discovery.py Sat Mar 12 18:42:16 2016 +0000 @@ -1,4 +1,8 @@ +import weakref from mercurial import branchmap +from mercurial import error +from mercurial import exchange +from mercurial.i18n import _ from . import topicmap def _headssummary(orig, repo, remote, outgoing): @@ -49,3 +53,55 @@ return orig(repo, proto) finally: repo.__class__ = oldrepo + + +# Discovery have deficiency around phases, branch can get new heads with pure +# phases change. This happened with a changeset was allowed to be pushed +# because it had a topic, but it later become public and create a new branch +# head. +# +# Handle this by doing an extra check for new head creation server side +def _nbheads(repo): + data = {} + for b in repo.branchmap().iterbranches(): + if ':' in b[0]: + continue + data[b[0]] = len(b[1]) + return data + +def handlecheckheads(orig, op, inpart): + orig(op, inpart) + if op.repo.publishing(): + return + tr = op.gettransaction() + if tr.hookargs['source'] not in ('push', 'serve'): # not a push + return + tr._prepushheads = _nbheads(op.repo) + reporef = weakref.ref(op.repo) + oldvalidator = tr.validator + def validator(tr): + repo = reporef() + if repo is not None: + repo.invalidatecaches() + finalheads = _nbheads(repo) + for branch, oldnb in tr._prepushheads.iteritems(): + newnb = finalheads.pop(branch, 0) + if oldnb < newnb: + msg = _('push create a new head on branch "%s"' % branch) + raise error.Abort(msg) + for branch, newnb in finalheads.iteritems(): + if 1 < newnb: + msg = _('push create more than 1 head on new branch "%s"' % branch) + raise error.Abort(msg) + return oldvalidator(tr) + tr.validator = validator +handlecheckheads.params = frozenset() + +def _pushb2phases(orig, pushop, bundler): + hascheck = any(p.type == 'check:heads' for p in bundler._parts) + if pushop.outdatedphases and not hascheck: + exchange._pushb2ctxcheckheads(pushop, bundler) + return orig(pushop, bundler) + + + diff -r 0504e76bfbd9 -r 68125d026b07 tests/test-topic-push.t --- a/tests/test-topic-push.t Sat Mar 12 18:19:27 2016 +0000 +++ b/tests/test-topic-push.t Sat Mar 12 18:42:16 2016 +0000 @@ -328,3 +328,63 @@ $ cd .. +Test phase move +================================== + +setup, two repo knowns about two small topic branch + + $ hg init repoA + $ hg clone repoA repoB + updating to branch default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat << EOF >> repoA/.hg/hgrc + > [phases] + > publish=False + > EOF + $ cat << EOF >> repoB/.hg/hgrc + > [phases] + > publish=False + > EOF + $ cd repoA + $ echo aaa > base + $ hg add base + $ hg commit -m 'CBASE' + $ echo aaa > aaa + $ hg add aaa + $ hg topic topicA + $ hg commit -m 'CA' + $ hg up 'desc(CBASE)' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo aaa > bbb + $ hg add bbb + $ hg topic topicB + $ hg commit -m 'CB' + $ cd .. + $ hg push -R repoA repoB + pushing to repoB + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files (+1 heads) + $ hg log -G -R repoA + @ 2 default topicB draft CB + | + | o 1 default topicA draft CA + |/ + o 0 default draft CBASE + + +We turn different topic to public on each side, + + $ hg -R repoA phase --public topicA + $ hg -R repoB phase --public topicB + +Pushing should complain because it create to heads on default + + $ hg push -R repoA repoB + pushing to repoB + searching for changes + no changes found + abort: push create a new head on branch "default" + [255]