[PATCH 2 of 2] push: introduce --readonly to make push behave more like pull

Mads Kiilerich mads at kiilerich.com
Sat Aug 20 19:33:14 EDT 2016


# HG changeset patch
# User Mads Kiilerich <madski at unity3d.com>
# Date 1471735620 -7200
#      Sun Aug 21 01:27:00 2016 +0200
# Branch stable
# Node ID 48f4ae36a49929c2389bfb75064f96d16159b2d0
# Parent  7880f56ca7495a2d0365a8280935eccd1e63f0c8
push: introduce --readonly to make push behave more like pull

Push and pull are almost symmetric ... except when they are not.

For example, push makes it possible to use a revset to specify the revisions to
push. Push can also be more suitable for distributing changes to multiple
mirrors than pull is. Finally, push will by default publish draft changesets.

Often, it is correct and desirable that push tries to update phases and thus
locks the repository while pushing ... put that also means that there can't be
multiple concurrent pushes and that pushing from non-publishing repositories
depends on correct configuration of the target repository.

We thus introduce --readonly to prevent push from locking the source repository
and thus prevent it from publishing. Push with --readonly is thus more
symmetric to pull.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -5878,6 +5878,7 @@ def pull(ui, repo, source="default", **o
     ('b', 'branch', [],
      _('a specific branch you would like to push'), _('BRANCH')),
     ('', 'new-branch', False, _('allow pushing a new branch')),
+    ('', 'readonly', False, _("no source repo locking or phase update")),
     ] + remoteopts,
     _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
 def push(ui, repo, dest=None, **opts):
@@ -5886,8 +5887,9 @@ def push(ui, repo, dest=None, **opts):
     Push changesets from the local repository to the specified
     destination.
 
-    This operation is symmetrical to pull: it is identical to a pull
-    in the destination repository from the current one.
+    This operation is almost symmetrical to pull: it is identical to a pull
+    in the destination repository from the current one, except that pushed
+    changesets in draft phase are made public unless --readonly is used.
 
     By default, push will not allow creation of new heads at the
     destination, since multiple heads would make it unclear which head
@@ -5969,7 +5971,8 @@ def push(ui, repo, dest=None, **opts):
     pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
                            newbranch=opts.get('new_branch'),
                            bookmarks=opts.get('bookmark', ()),
-                           opargs=opts.get('opargs'))
+                           opargs=opts.get('opargs'),
+                           readonly=opts.get('readonly'))
 
     result = not pushop.cgresult
 
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -275,7 +275,7 @@ class pushoperation(object):
     """
 
     def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
-                 bookmarks=()):
+                 bookmarks=(), readonly=False):
         # repo we push from
         self.repo = repo
         self.ui = repo.ui
@@ -289,6 +289,8 @@ class pushoperation(object):
         self.bookmarks = bookmarks
         # allow push of new branch
         self.newbranch = newbranch
+        # no locking of source repo and no publishing phase update
+        self.readonly = readonly
         # did a local lock get acquired?
         self.locallocked = None
         # step already performed
@@ -379,7 +381,7 @@ bookmsgmap = {'update': (_("updating boo
 
 
 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
-         opargs=None):
+         opargs=None, readonly=False):
     '''Push outgoing changesets (limited by revs) from a local
     repository to remote. Return an integer:
       - None means nothing to push
@@ -391,7 +393,7 @@ def push(repo, remote, force=False, revs
     if opargs is None:
         opargs = {}
     pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
-                           **opargs)
+                           readonly, **opargs)
     if pushop.remote.local():
         missing = (set(pushop.repo.requirements)
                    - pushop.remote.local().supported)
@@ -413,23 +415,26 @@ def push(repo, remote, force=False, revs
         raise error.Abort(_("destination does not support push"))
     # get local lock as we might write phase data
     localwlock = locallock = None
-    try:
-        # bundle2 push may receive a reply bundle touching bookmarks or other
-        # things requiring the wlock. Take it now to ensure proper ordering.
-        maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
-        if _canusebundle2(pushop) and maypushback:
-            localwlock = pushop.repo.wlock()
-        locallock = pushop.repo.lock()
-        pushop.locallocked = True
-    except IOError as err:
-        pushop.locallocked = False
-        if err.errno != errno.EACCES:
-            raise
-        # source repo cannot be locked.
-        # We do not abort the push, but just disable the local phase
-        # synchronisation.
-        msg = 'cannot lock source repository: %s\n' % err
-        pushop.ui.debug(msg)
+    if not pushop.readonly:
+        try:
+            # bundle2 push may receive a reply bundle touching bookmarks or
+            # other things requiring the wlock. Take it now to ensure proper
+            # ordering.
+            maypushback = pushop.ui.configbool('experimental',
+                                               'bundle2.pushback')
+            if _canusebundle2(pushop) and maypushback:
+                localwlock = pushop.repo.wlock()
+            locallock = pushop.repo.lock()
+            pushop.locallocked = True
+        except IOError as err:
+            pushop.locallocked = False
+            if err.errno != errno.EACCES:
+                raise
+            # source repo cannot be locked.
+            # We do not abort the push, but just disable the local phase
+            # synchronisation.
+            msg = 'cannot lock source repository: %s\n' % err
+            pushop.ui.debug(msg)
     try:
         if pushop.locallocked:
             pushop.trmanager = transactionmanager(pushop.repo,
@@ -988,7 +993,7 @@ def _localphasemove(pushop, nodes, phase
                                pushop.trmanager.transaction(),
                                phase,
                                nodes)
-    else:
+    elif not pushop.readonly:
         # repo is not locked, do not change any phases!
         # Informs the user that phases should have been moved when
         # applicable.
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -898,6 +898,7 @@ class hgsubrepo(abstractsubrepo):
         force = opts.get('force')
         newbranch = opts.get('new_branch')
         ssh = opts.get('ssh')
+        readonly = opts.get('readonly')
 
         # push subrepos depth-first for coherent ordering
         c = self._repo['']
@@ -916,7 +917,8 @@ class hgsubrepo(abstractsubrepo):
         self.ui.status(_('pushing subrepo %s to %s\n') %
             (subrelpath(self), dsturl))
         other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
-        res = exchange.push(self._repo, other, force, newbranch=newbranch)
+        res = exchange.push(self._repo, other, force, newbranch=newbranch,
+                            readonly=readonly)
 
         # the repo is now clean
         self._cachestorehash(dsturl)
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -217,7 +217,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, rev, bookmark, branch, new-branch, readonly, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude
   serve: accesslog, daemon, daemon-postexec, 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-phases-exchange.t b/tests/test-phases-exchange.t
--- a/tests/test-phases-exchange.t
+++ b/tests/test-phases-exchange.t
@@ -1194,7 +1194,7 @@ 2. Test that failed phases movement are 
 
 #endif
 
-Test that clone behaves like pull and doesn't
+Test that clone and push --readonly behave like pull and don't
 publish changesets as plain push does
 
   $ hg -R Upsilon phase -q --force --draft 2
@@ -1214,7 +1214,7 @@ publish changesets as plain push does
   |
   ~
 
-  $ hg -R Upsilon push Pi -r 8
+  $ hg -R Upsilon push Pi -r 8 --readonly
   pushing to Pi
   searching for changes
   adding changesets
@@ -1223,6 +1223,6 @@ publish changesets as plain push does
   added 1 changesets with 1 changes to 1 files
 
   $ hgph Upsilon -r 'min(draft())'
-  o  9 draft a-G - 3e27b6f1eee1
+  o  8 draft a-F - b740e3e5c05d
   |
   ~


More information about the Mercurial-devel mailing list