bookmark: fix remote bookmark deletion when the push is raced stable
authorPierre-Yves David <pierre-yves.david@octobus.net>
Tue, 11 Jun 2024 03:05:20 +0200
branchstable
changeset 51504 553eb132366f
parent 51503 fcc149f3fdcb
child 51505 3cf9e52f5e27
bookmark: fix remote bookmark deletion when the push is raced Before this patch, running `hg push -B book` to push the `book` bookmark sideway at the same time as a commit making it moving forward might result in the removal of the bookmark remotely. After this changeset, the push can still be raced, but to remove deletion happens. This is progress.
mercurial/exchange.py
tests/test-bookmarks-push-race.t
--- a/mercurial/exchange.py	Tue Jun 11 03:03:47 2024 +0200
+++ b/mercurial/exchange.py	Tue Jun 11 03:05:20 2024 +0200
@@ -717,13 +717,19 @@
             if bookmod.isdivergent(b):
                 pushop.ui.warn(_(b'cannot push divergent bookmark %s!\n') % b)
                 pushop.bkresult = 2
+            elif pushed and repo[scid].rev() not in pushed:
+                # in case of race or secret
+                msg = _(b'cannot push bookmark X without its revision: %s!\n')
+                pushop.ui.warn(msg % b)
+                pushop.bkresult = 2
             else:
                 pushop.outbookmarks.append((b, b'', scid))
     # search for overwritten bookmark
     for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
         if b in explicit:
             explicit.remove(b)
-            pushop.outbookmarks.append((b, dcid, scid))
+            if not pushed or repo[scid].rev() in pushed:
+                pushop.outbookmarks.append((b, dcid, scid))
     # search for bookmark to delete
     for b, scid, dcid in adddst:
         if b in explicit:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-bookmarks-push-race.t	Tue Jun 11 03:05:20 2024 +0200
@@ -0,0 +1,153 @@
+============================================
+Testing various race condition while pushing
+============================================
+
+  $ cat << EOF >> $HGRCPATH
+  > [command-templates]
+  > log={rev}:{node|short} {desc|firstline} {bookmarks}
+  > [ui]
+  > timeout = 20
+  > [phases]
+  > publish=False
+  > EOF
+
+Initial Setup
+=============
+
+  $ hg init dst
+  $ echo a > dst/a-file
+  $ hg --cwd dst add a-file
+  $ hg --cwd dst commit -m root
+  $ hg --cwd dst bookmark my-book
+  $ hg --cwd dst bookmarks
+   * my-book                   0:a64e49638499
+  $ hg --cwd dst log -G
+  @  0:a64e49638499 root my-book
+  
+
+  $ hg clone ssh://user@dummy/dst src
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  new changesets a64e49638499 (1 drafts)
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg --cwd src update my-book
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark my-book)
+  $ hg --cwd src log -G
+  @  0:a64e49638499 root my-book
+  
+
+  $ echo b > src/a-file
+  $ hg --cwd src commit -m cA0_
+  $ hg --cwd src log -G
+  @  1:e89d3a6ed79b cA0_ my-book
+  |
+  o  0:a64e49638499 root
+  
+
+Race condition while pushing a forward moving bookmarks
+=======================================================
+
+This is currently slightly broken as we eventually don't push the bookmark.
+However at least we do not delete the remote one.
+
+  $ echo c > src/a-file
+  $ hg --cwd src push -B my-book --config hooks.prelock="hg commit -m cA1_"
+  pushing to ssh://user@dummy/dst
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  $ hg --cwd src log -G
+  @  2:08d837bbfe8d cA1_ my-book
+  |
+  o  1:e89d3a6ed79b cA0_
+  |
+  o  0:a64e49638499 root
+  
+  $ hg --cwd dst log -G
+  o  1:e89d3a6ed79b cA0_
+  |
+  @  0:a64e49638499 root my-book
+  
+
+create a side-moving bookmark
+Race condition while pushing a side moving bookmarks
+=======================================================
+
+resynchronize the repo and setup test
+-------------------------------------
+
+  $ hg --cwd src push -B my-book
+  pushing to ssh://user@dummy/dst
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  updating bookmark my-book
+  $ hg --cwd dst log -G
+  o  2:08d837bbfe8d cA1_ my-book
+  |
+  o  1:e89d3a6ed79b cA0_
+  |
+  @  0:a64e49638499 root
+  
+
+  $ hg --cwd src up 'desc("root")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (leaving bookmark my-book)
+  $ echo d > src/a-file
+  $ hg --cwd src commit -m cB0_
+  created new head
+  $ hg --cwd src bookmark --force my-book
+  $ echo e > src/a-file
+  $ hg --cwd src log -G
+  @  3:726401661fe5 cB0_ my-book
+  |
+  | o  2:08d837bbfe8d cA1_
+  | |
+  | o  1:e89d3a6ed79b cA0_
+  |/
+  o  0:a64e49638499 root
+  
+
+Push the bookmark while a commit is being made
+----------------------------------------------
+
+This is currently slightly broken as we eventually don't push the bookmark.
+However at least we do not delete the remote one.
+
+  $ hg --cwd src push -f -r 'desc("cB0_")' -B my-book --config hooks.prelock="hg commit -m cB1_"
+  pushing to ssh://user@dummy/dst
+  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 --cwd src log -G
+  @  4:a7f9cbf631a0 cB1_ my-book
+  |
+  o  3:726401661fe5 cB0_
+  |
+  | o  2:08d837bbfe8d cA1_
+  | |
+  | o  1:e89d3a6ed79b cA0_
+  |/
+  o  0:a64e49638499 root
+  
+
+  $ hg --cwd dst log -G
+  o  3:726401661fe5 cB0_
+  |
+  | o  2:08d837bbfe8d cA1_ my-book
+  | |
+  | o  1:e89d3a6ed79b cA0_
+  |/
+  @  0:a64e49638499 root
+