D6776: bookmarks: validate changes on push (issue6193) (BC)

idlsoft (Sandu Turcan) phabricator at mercurial-scm.org
Sat Sep 14 00:53:04 EDT 2019


idlsoft updated this revision to Diff 16534.

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D6776?vs=16533&id=16534

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D6776/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D6776

AFFECTED FILES
  mercurial/bundle2.py
  mercurial/exchange.py
  mercurial/localrepo.py
  mercurial/repository.py
  mercurial/wireprotov1peer.py
  mercurial/wireprotov1server.py
  tests/test-bookmarks-conflict.t
  tests/test-bookmarks-pushpull.t
  tests/test-hook.t

CHANGE DETAILS

diff --git a/tests/test-hook.t b/tests/test-hook.t
--- a/tests/test-hook.t
+++ b/tests/test-hook.t
@@ -545,6 +545,7 @@
   HG_URL=file:$TESTTMP/a
   
   pushkey hook: HG_BUNDLE2=1
+  HG_FORCE=0
   HG_HOOKNAME=pushkey
   HG_HOOKTYPE=pushkey
   HG_KEY=foo
@@ -632,6 +633,7 @@
   HG_TXNNAME=push
   
   prepushkey.forbid hook: HG_BUNDLE2=1
+  HG_FORCE=0
   HG_HOOKNAME=prepushkey
   HG_HOOKTYPE=prepushkey
   HG_KEY=baz
diff --git a/tests/test-bookmarks-pushpull.t b/tests/test-bookmarks-pushpull.t
--- a/tests/test-bookmarks-pushpull.t
+++ b/tests/test-bookmarks-pushpull.t
@@ -813,7 +813,7 @@
      Z                         0d2164f0ce0d
      foo                                   
      foobar                                
-  $ hg push -B Z http://localhost:$HGPORT/
+  $ hg push -B Z http://localhost:$HGPORT/ --force
   pushing to http://localhost:$HGPORT/
   searching for changes
   no changes found
diff --git a/tests/test-bookmarks-conflict.t b/tests/test-bookmarks-conflict.t
new file mode 100644
--- /dev/null
+++ b/tests/test-bookmarks-conflict.t
@@ -0,0 +1,142 @@
+initialize
+  $ make_changes() {
+  >     d=`pwd`
+  >     [ ! -z $1 ] && cd $1
+  >     echo "test `basename \`pwd\``" >> test
+  >     hg commit -Am"${2:-test}"
+  >     r=$?
+  >     cd $d
+  >     return $r
+  > }
+  $ ls -1a
+  .
+  ..
+  $ hg init a
+  $ cd a
+  $ echo 'test' > test; hg commit -Am'test'
+  adding test
+  $ hg book @
+
+clone to b
+
+  $ mkdir ../b
+  $ cd ../b
+  $ hg clone ../a .
+  updating to bookmark @
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ make_changes
+  $ hg book bk_b
+
+clone to c
+  $ mkdir ../c
+  $ cd ../c
+  $ hg clone ../a .
+  updating to bookmark @
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ make_changes
+  $ hg book bk_c
+
+push from b
+  $ cd ../b
+  $ hg push -B .
+  pushing to $TESTTMP/a
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  updating bookmark @
+  exporting bookmark bk_b
+  $ hg -R ../a id -r @
+  e11a942451be tip @/bk_b
+
+push from c
+  $ cd ../c
+  $ hg push -B .
+  pushing to $TESTTMP/a
+  searching for changes
+  remote has heads on branch 'default' that are not known locally: e11a942451be
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  exporting bookmark bk_c
+  $ hg push -B @
+  pushing to $TESTTMP/a
+  searching for changes
+  no changes found
+  abort: push rejected: bookmark "@" has changed
+  (run 'hg pull', resolve conflicts, and push again)
+  [255]
+  $ hg -R ../a log -G -T '{rev} {bookmarks}'
+  o  2 bk_c
+  |
+  | o  1 @ bk_b
+  |/
+  @  0
+  
+
+  $ hg push -B @ --force
+  pushing to $TESTTMP/a
+  searching for changes
+  no changes found
+  updating bookmark @
+  [1]
+  $ hg -R ../a log -G -T '{rev} {bookmarks}'
+  o  2 @ bk_c
+  |
+  | o  1 bk_b
+  |/
+  @  0
+  
+
+## push using ssh  
+  $ hg -R ../b push -B @ --force
+  pushing to $TESTTMP/a
+  searching for changes
+  no changes found
+  updating bookmark @
+  [1]
+  $ hg push -B @ ssh://user@dummy/a -e"$PYTHON $TESTDIR/dummyssh"
+  pushing to ssh://user@dummy/a
+  searching for changes
+  no changes found
+  remote: push rejected: bookmark "@" has changed
+  remote: (run 'hg pull', resolve conflicts, and push again)
+  abort: push failed on remote
+  [255]
+  $ hg push --force -B @ ssh://user@dummy/a -e"$PYTHON $TESTDIR/dummyssh"
+  pushing to ssh://user@dummy/a
+  searching for changes
+  no changes found
+  updating bookmark @
+  [1]
+
+# push using http
+  $ cat <<EOF > ../a/.hg/hgrc
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > EOF
+  $ hg serve -R ../a -p $HGPORT -d --pid-file=../hg.pid
+  $ cat ../hg.pid >> $DAEMON_PIDS
+  $ hg -R ../b push -B @ --force
+  pushing to $TESTTMP/a
+  searching for changes
+  no changes found
+  updating bookmark @
+  [1]
+  $ hg push -B @ http://localhost:$HGPORT
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  remote: push rejected: bookmark "@" has changed
+  remote: (run 'hg pull', resolve conflicts, and push again)
+  abort: push failed on remote
+  [255]
+  $ hg push --force -B @ http://localhost:$HGPORT
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  updating bookmark @
+  [1]
diff --git a/mercurial/wireprotov1server.py b/mercurial/wireprotov1server.py
--- a/mercurial/wireprotov1server.py
+++ b/mercurial/wireprotov1server.py
@@ -548,8 +548,8 @@
     return wireprototypes.streamreslegacy(
         streamclone.generatev1wireproto(repo))
 
- at wireprotocommand('unbundle', 'heads', permission='push')
-def unbundle(repo, proto, heads):
+ at wireprotocommand('unbundle', 'heads *', permission='push')
+def unbundle(repo, proto, heads, others):
     their_heads = wireprototypes.decodelist(heads)
 
     with proto.mayberedirectstdio() as output:
@@ -594,7 +594,7 @@
                                       hint=bundle2requiredhint)
 
                 r = exchange.unbundle(repo, gen, their_heads, 'serve',
-                                      proto.client())
+                                      proto.client(), others.get('force') == '1')
                 if util.safehasattr(r, 'addpart'):
                     # The return looks streamable, we are in the bundle2 case
                     # and should return a stream.
diff --git a/mercurial/wireprotov1peer.py b/mercurial/wireprotov1peer.py
--- a/mercurial/wireprotov1peer.py
+++ b/mercurial/wireprotov1peer.py
@@ -445,7 +445,7 @@
         else:
             return changegroupmod.cg1unpacker(f, 'UN')
 
-    def unbundle(self, bundle, heads, url):
+    def unbundle(self, bundle, heads, url, force=None):
         '''Send cg (a readable file-like object representing the
         changegroup to push, typically a chunkbuffer object) to the
         remote server as a bundle.
@@ -464,10 +464,12 @@
                 ['hashed', hashlib.sha1(''.join(sorted(heads))).digest()])
         else:
             heads = wireprototypes.encodelist(heads)
-
+        args = {'heads': heads}
+        if force:
+            args['force'] = '1'
         if util.safehasattr(bundle, 'deltaheader'):
             # this a bundle10, do the old style call sequence
-            ret, output = self._callpush("unbundle", bundle, heads=heads)
+            ret, output = self._callpush("unbundle", bundle, **args)
             if ret == "":
                 raise error.ResponseError(
                     _('push failed:'), output)
@@ -481,7 +483,7 @@
                 self.ui.status(_('remote: '), l)
         else:
             # bundle2 push. Send a stream, fetch a stream.
-            stream = self._calltwowaystream('unbundle', bundle, heads=heads)
+            stream = self._calltwowaystream('unbundle', bundle, **args)
             ret = bundle2.getunbundler(self.ui, stream)
         return ret
 
diff --git a/mercurial/repository.py b/mercurial/repository.py
--- a/mercurial/repository.py
+++ b/mercurial/repository.py
@@ -191,7 +191,7 @@
         Successful result should be a generator of data chunks.
         """
 
-    def unbundle(bundle, heads, url):
+    def unbundle(bundle, heads, url, force=None):
         """Transfer repository data to the peer.
 
         This is how the bulk of data during a push is transferred.
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -306,14 +306,14 @@
         raise error.Abort(_('cannot perform stream clone against local '
                             'peer'))
 
-    def unbundle(self, bundle, heads, url):
+    def unbundle(self, bundle, heads, url, force=None):
         """apply a bundle on a repo
 
         This function handles the repo locking itself."""
         try:
             try:
                 bundle = exchange.readbundle(self.ui, bundle, None)
-                ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
+                ret = exchange.unbundle(self._repo, bundle, heads, 'push', url, force=force)
                 if util.safehasattr(ret, 'getchunks'):
                     # This is a bundle20 object, turn it into an unbundler.
                     # This little dance should be dropped eventually when the
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1159,6 +1159,7 @@
                     'bundle': stream,
                     'heads': ['force'],
                     'url': pushop.remote.url(),
+                    'force': pushop.force,
                 }).result()
         except error.BundleValueError as exc:
             raise error.Abort(_('missing support for %s') % exc)
@@ -2375,7 +2376,7 @@
         raise error.PushRaced('repository changed while %s - '
                               'please try again' % context)
 
-def unbundle(repo, cg, heads, source, url):
+def unbundle(repo, cg, heads, source, url, force=None):
     """Apply a bundle to a repo.
 
     this function makes sure the repo is locked during the application and have
@@ -2424,7 +2425,8 @@
 
                 op = bundle2.bundleoperation(repo, gettransaction,
                                              captureoutput=captureoutput,
-                                             source='push')
+                                             source='push',
+                                             force=force)
                 try:
                     op = bundle2.processbundle(repo, cg, op=op)
                 finally:
diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -299,7 +299,7 @@
     * a way to construct a bundle response when applicable.
     """
 
-    def __init__(self, repo, transactiongetter, captureoutput=True, source=''):
+    def __init__(self, repo, transactiongetter, captureoutput=True, source='', force=False):
         self.repo = repo
         self.ui = repo.ui
         self.records = unbundlerecords()
@@ -310,6 +310,7 @@
         # carries value that can modify part behavior
         self.modes = {}
         self.source = source
+        self.force = force
 
     def gettransaction(self):
         transaction = self._gettransaction()
@@ -2135,17 +2136,23 @@
     if bookmarksmode == 'apply':
         tr = op.gettransaction()
         bookstore = op.repo._bookmarks
+        allhooks = []
+        for book, node in changes:
+            old_node = bookstore.get(book, '')
+            if not op.force and old_node != '' and node is not None:
+                if not bookmarks.validdest(op.repo, op.repo[old_node], op.repo[node]):
+                    raise error.Abort(_('push rejected: bookmark "%s" has changed') % book,
+                                      hint=_("run 'hg pull', resolve conflicts, and push again"))
+            hookargs = tr.hookargs.copy()
+            hookargs['pushkeycompat'] = '1'
+            hookargs['namespace'] = 'bookmarks'
+            hookargs['key'] = book
+            hookargs['old'] = nodemod.hex(old_node)
+            hookargs['new'] = nodemod.hex(node if node is not None else '')
+            hookargs['force'] = op.force
+            allhooks.append(hookargs)
+
         if pushkeycompat:
-            allhooks = []
-            for book, node in changes:
-                hookargs = tr.hookargs.copy()
-                hookargs['pushkeycompat'] = '1'
-                hookargs['namespace'] = 'bookmarks'
-                hookargs['key'] = book
-                hookargs['old'] = nodemod.hex(bookstore.get(book, ''))
-                hookargs['new'] = nodemod.hex(node if node is not None else '')
-                allhooks.append(hookargs)
-
             for hookargs in allhooks:
                 op.repo.hook('prepushkey', throw=True,
                              **pycompat.strkwargs(hookargs))



To: idlsoft, #hg-reviewers, durin42
Cc: pulkit, durin42, valentin.gatienbaron, mercurial-devel


More information about the Mercurial-devel mailing list