push: allow pushing new topic to non-publishing server by default
This improves and fix the behavior change introduced by the new "topicmap".
* Topics are properly ignored when pushing to a publishing server,
* pushing new topics is allowed without --force a non-publishing server,
* Pushing extra heads on a topic requires --force.
Create of new head on a branch by phase movement is not properly detected for
now. We'll improve that part in a later changesets.
There is more awful monkey patching going on. We'll have to refactor core to get
rid of them.
--- a/src/topic/__init__.py Sat Mar 12 15:36:17 2016 +0000
+++ b/src/topic/__init__.py Sat Mar 12 18:19:27 2016 +0000
@@ -13,11 +13,14 @@
import functools
from mercurial.i18n import _
+from mercurial import branchmap
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 extensions
+from mercurial import localrepo
from mercurial import lock
from mercurial import merge
from mercurial import namespaces
@@ -26,12 +29,13 @@
from mercurial import patch
from mercurial import phases
from mercurial import util
-from mercurial import branchmap
+from mercurial import wireproto
from . import constants
from . import revset as topicrevset
from . import destination
from . import topicmap
+from . import discovery
cmdtable = {}
command = cmdutil.command(cmdtable)
@@ -58,6 +62,8 @@
def reposetup(ui, repo):
orig = repo.__class__
+ if not isinstance(repo, localrepo.localrepository):
+ return # this can be a peer in the ssh case (puzzling)
class topicrepo(repo.__class__):
def commit(self, *args, **kwargs):
backup = self.ui.backupconfig('ui', 'allowemptycommit')
@@ -117,6 +123,17 @@
if '_topiccaches' in vars(self.unfiltered()):
self.unfiltered()._topiccaches.clear()
+ def peer(self):
+ peer = super(topicrepo, self).peer()
+ if getattr(peer, '_repo', None) is not None: # localpeer
+ class topicpeer(peer.__class__):
+ def branchmap(self):
+ usetopic = not self._repo.publishing()
+ return self._repo.branchmap(topic=usetopic)
+ peer.__class__ = topicpeer
+ return peer
+
+
repo.__class__ = topicrepo
if util.safehasattr(repo, 'names'):
repo.names.addnamespace(namespaces.namespace(
@@ -276,6 +293,8 @@
extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap)
extensions.wrapfunction(merge, 'update', mergeupdatewrap)
+extensions.wrapfunction(discoverymod, '_headssummary', discovery._headssummary)
+extensions.wrapfunction(wireproto, 'branchmap', discovery.wireprotobranchmap)
topicrevset.modsetup()
cmdutil.summaryhooks.add('topic', summaryhook)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/topic/discovery.py Sat Mar 12 18:19:27 2016 +0000
@@ -0,0 +1,51 @@
+from mercurial import branchmap
+from . import topicmap
+
+def _headssummary(orig, repo, remote, outgoing):
+ publishing = ('phases' not in remote.listkeys('namespaces')
+ or bool(remote.listkeys('phases').get('publishing', False)))
+ if publishing:
+ return orig(repo, remote, outgoing)
+ oldgetitem = repo.__getitem__
+ oldrepo = repo.__class__
+ oldbranchcache = branchmap.branchcache
+ oldfilename = branchmap._filename
+ try:
+ class repocls(repo.__class__):
+ def __getitem__(self, key):
+ ctx = super(repocls, self).__getitem__(key)
+ oldbranch = ctx.branch
+ def branch():
+ branch = oldbranch()
+ topic = ctx.topic()
+ if topic:
+ branch = "%s:%s" % (branch, topic)
+ return branch
+ ctx.branch = branch
+ return ctx
+ repo.__class__ = repocls
+ branchmap.branchcache = topicmap.topiccache
+ branchmap._filename = topicmap._filename
+ summary = orig(repo, remote, outgoing)
+ for key, value in summary.iteritems():
+ if ':' in key: # This is a topic
+ if value[0] is None and value[1]:
+ summary[key] = ([value[1].pop(0)], ) + value[1:]
+ return summary
+ finally:
+ repo.__class__ = oldrepo
+ branchmap.branchcache = oldbranchcache
+ branchmap._filename = oldfilename
+
+def wireprotobranchmap(orig, repo, proto):
+ oldrepo = repo.__class__
+ print repo
+ try:
+ class repocls(repo.__class__):
+ def branchmap(self):
+ usetopic = not self.publishing()
+ return super(repocls, self).branchmap(topic=usetopic)
+ repo.__class__ = repocls
+ return orig(repo, proto)
+ finally:
+ repo.__class__ = oldrepo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-topic-push.t Sat Mar 12 18:19:27 2016 +0000
@@ -0,0 +1,330 @@
+ $ . "$TESTDIR/testlib"
+
+ $ cat << EOF >> $HGRCPATH
+ > [ui]
+ > logtemplate = {rev} {branch} {get(namespaces, "topics")} {phase} {desc|firstline}\n
+ > [ui]
+ > ssh =python "$RUNTESTDIR/dummyssh"
+ > EOF
+
+ $ hg init main
+ $ hg init draft
+ $ cat << EOF >> draft/.hg/hgrc
+ > [phases]
+ > publish=False
+ > EOF
+ $ hg clone main client
+ updating to branch default
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cat << EOF >> client/.hg/hgrc
+ > [paths]
+ > draft=../draft
+ > EOF
+
+
+Testing core behavior to make sure we did not break anything
+============================================================
+
+Pushing a first changeset
+
+ $ cd client
+ $ echo aaa > aaa
+ $ hg add aaa
+ $ hg commit -m 'CA'
+ $ hg outgoing -G
+ comparing with $TESTTMP/main
+ searching for changes
+ @ 0 default draft CA
+
+ $ hg push
+ pushing to $TESTTMP/main
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+
+Pushing two heads
+
+ $ echo aaa > bbb
+ $ hg add bbb
+ $ hg commit -m 'CB'
+ $ echo aaa > ccc
+ $ hg up 'desc(CA)'
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg add ccc
+ $ hg commit -m 'CC'
+ created new head
+ $ hg outgoing -G
+ comparing with $TESTTMP/main
+ searching for changes
+ @ 2 default draft CC
+
+ o 1 default draft CB
+
+ $ hg push
+ pushing to $TESTTMP/main
+ searching for changes
+ abort: push creates new remote head 9fe81b7f425d!
+ (merge or see "hg help push" for details about pushing new heads)
+ [255]
+ $ hg outgoing -r 'desc(CB)' -G
+ comparing with $TESTTMP/main
+ searching for changes
+ o 1 default draft CB
+
+ $ hg push -r 'desc(CB)'
+ pushing to $TESTTMP/main
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+
+Pushing a new branch
+
+ $ hg branch mountain
+ marked working directory as branch mountain
+ (branches are permanent and global, did you want a bookmark?)
+ $ hg commit --amend
+ $ hg outgoing -G
+ comparing with $TESTTMP/main
+ searching for changes
+ @ 4 mountain draft CC
+
+ $ hg push
+ pushing to $TESTTMP/main
+ searching for changes
+ abort: push creates new remote branches: mountain!
+ (use 'hg push --new-branch' to create new remote branches)
+ [255]
+ $ hg push --new-branch
+ pushing to $TESTTMP/main
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files (+1 heads)
+ 2 new obsolescence markers
+
+Including on non-publishing
+
+ $ hg push --new-branch draft
+ pushing to $TESTTMP/draft
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 3 changes to 3 files (+1 heads)
+ 2 new obsolescence markers
+
+Testing topic behavior
+======================
+
+Local peer tests
+----------------
+
+ $ hg up -r 'desc(CA)'
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg topic babar
+ $ echo aaa > ddd
+ $ hg add ddd
+ $ hg commit -m 'CD'
+ created new head
+ $ hg log -G # keep track of phase because I saw some strange bug during developement
+ @ 5 default babar draft CD
+ |
+ | o 4 mountain public CC
+ |/
+ | o 1 default public CB
+ |/
+ o 0 default public CA
+
+
+Pushing a new topic to a non publishing server should not be seen as a new head
+
+ $ hg push draft
+ pushing to $TESTTMP/draft
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files (+1 heads)
+ $ hg log -G
+ @ 5 default babar draft CD
+ |
+ | o 4 mountain public CC
+ |/
+ | o 1 default public CB
+ |/
+ o 0 default public CA
+
+
+Pushing a new topic to a publishing server should be seen as a new head
+
+ $ hg push
+ pushing to $TESTTMP/main
+ searching for changes
+ abort: push creates new remote head 67f579af159d!
+ (merge or see "hg help push" for details about pushing new heads)
+ [255]
+ $ hg log -G
+ @ 5 default babar draft CD
+ |
+ | o 4 mountain public CC
+ |/
+ | o 1 default public CB
+ |/
+ o 0 default public CA
+
+
+wireprotocol tests
+------------------
+
+ $ hg up -r 'desc(CA)'
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg topic celeste
+ $ echo aaa > eee
+ $ hg add eee
+ $ hg commit -m 'CE'
+ created new head
+ $ hg log -G # keep track of phase because I saw some strange bug during developement
+ @ 6 default celeste draft CE
+ |
+ | o 5 default babar draft CD
+ |/
+ | o 4 mountain public CC
+ |/
+ | o 1 default public CB
+ |/
+ o 0 default public CA
+
+
+Pushing a new topic to a non publishing server should not be seen as a new head
+
+ $ hg push ssh://user@dummy/draft
+ pushing to ssh://user@dummy/draft
+ searching for changes
+ remote: adding changesets
+ remote: adding manifests
+ remote: adding file changes
+ remote: added 1 changesets with 1 changes to 1 files (+1 heads)
+ $ hg log -G
+ @ 6 default celeste draft CE
+ |
+ | o 5 default babar draft CD
+ |/
+ | o 4 mountain public CC
+ |/
+ | o 1 default public CB
+ |/
+ o 0 default public CA
+
+
+Pushing a new topic to a publishing server should be seen as a new head
+
+ $ hg push ssh://user@dummy/main
+ pushing to ssh://user@dummy/main
+ searching for changes
+ abort: push creates new remote head 67f579af159d!
+ (merge or see "hg help push" for details about pushing new heads)
+ [255]
+ $ hg log -G
+ @ 6 default celeste draft CE
+ |
+ | o 5 default babar draft CD
+ |/
+ | o 4 mountain public CC
+ |/
+ | o 1 default public CB
+ |/
+ o 0 default public CA
+
+
+Check that we reject multiple head on the same topic
+----------------------------------------------------
+
+ $ hg up 'desc(CB)'
+ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg topic babar
+ $ echo aaa > fff
+ $ hg add fff
+ $ hg commit -m 'CF'
+ $ hg log -G
+ @ 7 default babar draft CF
+ |
+ | o 6 default celeste draft CE
+ | |
+ | | o 5 default babar draft CD
+ | |/
+ | | o 4 mountain public CC
+ | |/
+ o | 1 default public CB
+ |/
+ o 0 default public CA
+
+
+ $ hg push draft
+ pushing to $TESTTMP/draft
+ searching for changes
+ abort: push creates new remote head f0bc62a661be on branch 'default:babar'!
+ (merge or see "hg help push" for details about pushing new heads)
+ [255]
+
+Multiple head on a branch merged in a topic changesets
+------------------------------------------------------------------------
+
+
+ $ hg up 'desc(CA)'
+ 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ $ echo aaa > ggg
+ $ hg add ggg
+ $ hg commit -m 'CG'
+ created new head
+ $ hg up 'desc(CF)'
+ switching to topic babar
+ 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg merge 'desc(CG)'
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ hg commit -m 'CM'
+ $ hg log -G
+ @ 9 default babar draft CM
+ |\
+ | o 8 default draft CG
+ | |
+ o | 7 default babar draft CF
+ | |
+ | | o 6 default celeste draft CE
+ | |/
+ | | o 5 default babar draft CD
+ | |/
+ | | o 4 mountain public CC
+ | |/
+ o | 1 default public CB
+ |/
+ o 0 default public CA
+
+
+Reject when pushing to draft
+
+ $ hg push draft -r .
+ pushing to $TESTTMP/draft
+ searching for changes
+ abort: push creates new remote head 4937c4cad39e!
+ (merge or see "hg help push" for details about pushing new heads)
+ [255]
+
+
+Reject when pushing to publishing
+
+ $ hg push -r .
+ pushing to $TESTTMP/main
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 3 changesets with 2 changes to 2 files
+
+ $ cd ..
+