push: allow pushing new topic to non-publishing server by default
authorPierre-Yves David <pierre-yves.david@fb.com>
Sat, 12 Mar 2016 18:19:27 +0000
changeset 1886 0504e76bfbd9
parent 1885 d49f75eab6a3
child 1887 68125d026b07
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.
src/topic/__init__.py
src/topic/discovery.py
tests/test-topic-push.t
--- 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 ..
+