[PATCH 2 of 2] push: introduce --multihead for pushing multiple heads without force

Mads Kiilerich mads at kiilerich.com
Thu Oct 15 04:25:17 CDT 2015


# HG changeset patch
# User Mads Kiilerich <madski at unity3d.com>
# Date 1444900849 -7200
#      Thu Oct 15 11:20:49 2015 +0200
# Node ID 2906d84ee5299a02864e253bf407196a3a35eb53
# Parent  5c78d0e30a35d0a2d50148681f3de10470ab088d
push: introduce --multihead for pushing multiple heads without force

Pushing of multiple heads can happen frequently. One example is when recreating
a named branch from another starting point after having closed the old branch
head. Another example is when history of a "pull request" is modified after
pushing to non-publishing repositories, for example on bitbucket, and you just
want to make the updated changes available to the world. That could only be
done by pushing with --force.

--force is scary. Smart developers know that "use a bigger hammer" rarely is
the _right_ answer. Telling them to use --force for relatively frequent
semi-power-user operations seems wrong.

Instead, introduce a new option for push that _just_ make it push multiple
heads without forcing other scary things that --force might do for this or
other commands.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -5303,6 +5303,7 @@ def pull(ui, repo, source="default", **o
 
 @command('^push',
     [('f', 'force', None, _('force push')),
+    ('', 'multihead', None, _('force push of multiple branch heads')),
     ('r', 'rev', [],
      _('a changeset intended to be included in the destination'),
      _('REV')),
@@ -5332,7 +5333,7 @@ def push(ui, repo, dest=None, **opts):
 
     .. note::
 
-      Extra care should be taken with the -f/--force option,
+      Extra care should be taken with the --multihead or -f/--force option,
       which will push all new heads on all branches, an action which will
       almost always cause confusion for collaborators.
 
@@ -5388,6 +5389,7 @@ def push(ui, repo, dest=None, **opts):
         del repo._subtoppath
     pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
                            newbranch=opts.get('new_branch'),
+                           multihead=opts.get('multihead'),
                            bookmarks=opts.get('bookmark', ()))
 
     result = not pushop.cgresult
diff --git a/mercurial/discovery.py b/mercurial/discovery.py
--- a/mercurial/discovery.py
+++ b/mercurial/discovery.py
@@ -238,8 +238,8 @@ def _oldheadssummary(repo, remoteheads, 
         unsynced = set()
     return {None: (oldheads, newheads, unsynced)}
 
-def checkheads(repo, remote, outgoing, remoteheads, newbranch=False, inc=False,
-               newbookmarks=[]):
+def checkheads(repo, remote, outgoing, remoteheads, newbranch, multihead,
+               inc=False, newbookmarks=[]):
     """Check that a push won't add any outgoing head
 
     raise Abort error and display ui message as needed.
@@ -303,7 +303,6 @@ def checkheads(repo, remote, outgoing, r
             oldhs = set(remoteheads)
         oldhs.update(unsyncedheads)
         candidate_newhs.update(unsyncedheads)
-        dhs = None # delta heads, the new heads on branch
         discardedheads = set()
         if not repo.obsstore:
             newhs = candidate_newhs
@@ -355,7 +354,10 @@ def checkheads(repo, remote, outgoing, r
             else:
                 repo.ui.status(_("remote has heads on branch '%s' that are "
                                  "not known locally: %s\n") % (branch, heads))
-        if remoteheads is None:
+        dhs = None # delta heads, the new heads on branch
+        if multihead:
+            pass
+        elif remoteheads is None:
             if len(newhs) > 1:
                 dhs = list(newhs)
                 if errormsg is None:
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -74,7 +74,7 @@ class pushoperation(object):
     """
 
     def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
-                 bookmarks=()):
+                 multihead=False, bookmarks=()):
         # repo we push from
         self.repo = repo
         self.ui = repo.ui
@@ -88,6 +88,8 @@ class pushoperation(object):
         self.bookmarks = bookmarks
         # allow push of new branch
         self.newbranch = newbranch
+        # allow push of multiple heads
+        self.multihead = multihead
         # did a local lock get acquired?
         self.locallocked = None
         # step already performed
@@ -177,7 +179,8 @@ bookmsgmap = {'update': (_("updating boo
               }
 
 
-def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=()):
+def push(repo, remote, force=False, revs=None, newbranch=False,
+         multihead=False, bookmarks=()):
     '''Push outgoing changesets (limited by revs) from a local
     repository to remote. Return an integer:
       - None means nothing to push
@@ -186,7 +189,8 @@ def push(repo, remote, force=False, revs
         we have outgoing changesets but refused to push
       - other values as described by addchangegroup()
     '''
-    pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks)
+    pushop = pushoperation(repo, remote, force, revs, newbranch, multihead,
+                           bookmarks)
     if pushop.remote.local():
         missing = (set(pushop.repo.requirements)
                    - pushop.remote.local().supported)
@@ -445,6 +449,7 @@ def _pushcheckoutgoing(pushop):
         discovery.checkheads(unfi, pushop.remote, outgoing,
                              pushop.remoteheads,
                              pushop.newbranch,
+                             pushop.multihead,
                              bool(pushop.incoming),
                              newbm)
     return True
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -213,7 +213,7 @@ Show all commands + options
   log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
   merge: force, rev, preview, tool
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
-  push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
+  push: force, multihead, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude
   serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
diff --git a/tests/test-push-warn.t b/tests/test-push-warn.t
--- a/tests/test-push-warn.t
+++ b/tests/test-push-warn.t
@@ -151,7 +151,7 @@ Specifying a revset that evaluates to nu
   (merge or see "hg help push" for details about pushing new heads)
   [255]
 
-  $ hg push -v -f -r 3 -r 4 ../c
+  $ hg push -v --multihead -r 3 -r 4 ../c
   pushing to ../c
   searching for changes
   2 changesets found
@@ -389,7 +389,7 @@ Pushing multi headed new branch:
   abort: push creates new branch 'f' with multiple heads
   (merge or see "hg help push" for details about pushing new heads)
   [255]
-  $ hg push --branch f --new-branch --force ../f
+  $ hg push --branch f --new-branch --multi ../f
   pushing to ../f
   searching for changes
   adding changesets


More information about the Mercurial-devel mailing list