changeset 18851:a60963c02f92

pull: list bookmarks before pulling changesets (issue3873) Consider a bookmark B that exists both locally and remotely. If B is updated remotely, and then a pull is performed where the pull set contains the new location of B, the bookmark is updated locally. However, if remote B is updated in the middle of a pull to a location not in the pull set, the bookmark won't be updated locally at all. To fix this, list bookmarks before pulling in changesets, not after. This still leaves a race open if B gets moved in between listing bookmarks and pulling in changesets, but the race window is much smaller. Fixing the race properly would require a bundle format upgrade. test-hook.t's output changes because we no longer do two listkeys calls during pull, just one. test-pull-http.t's output changes because we now search for bookmarks before searching for changes.
author Siddharth Agarwal <sid0@fb.com>
date Fri, 29 Mar 2013 19:54:06 -0700
parents 442c0cb8287a
children 300844cb1a56
files mercurial/bookmarks.py mercurial/commands.py mercurial/subrepo.py tests/test-bookmarks-pushpull.t tests/test-hook.t tests/test-pull-http.t
diffstat 6 files changed, 40 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/bookmarks.py	Fri Mar 29 19:52:02 2013 -0700
+++ b/mercurial/bookmarks.py	Fri Mar 29 19:54:06 2013 -0700
@@ -221,9 +221,8 @@
     finally:
         w.release()
 
-def updatefromremote(ui, repo, remote, path):
+def updatefromremote(ui, repo, remotemarks, path):
     ui.debug("checking for updated bookmarks\n")
-    remotemarks = remote.listkeys('bookmarks')
     changed = False
     localmarks = repo._bookmarks
     for k in sorted(remotemarks):
--- a/mercurial/commands.py	Fri Mar 29 19:52:02 2013 -0700
+++ b/mercurial/commands.py	Fri Mar 29 19:54:06 2013 -0700
@@ -4496,10 +4496,11 @@
     ui.status(_('pulling from %s\n') % util.hidepassword(source))
     revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
 
+    remotebookmarks = other.listkeys('bookmarks')
+
     if opts.get('bookmark'):
         if not revs:
             revs = []
-        remotebookmarks = other.listkeys('bookmarks')
         for b in opts['bookmark']:
             if b not in remotebookmarks:
                 raise util.Abort(_('remote bookmark %s not found!') % b)
@@ -4514,7 +4515,7 @@
             raise util.Abort(err)
 
     modheads = repo.pull(other, heads=revs, force=opts.get('force'))
-    bookmarks.updatefromremote(ui, repo, other, source)
+    bookmarks.updatefromremote(ui, repo, remotebookmarks, source)
     if checkout:
         checkout = str(repo.changelog.rev(other.lookup(checkout)))
     repo._subtoppath = source
--- a/mercurial/subrepo.py	Fri Mar 29 19:52:02 2013 -0700
+++ b/mercurial/subrepo.py	Fri Mar 29 19:54:06 2013 -0700
@@ -547,9 +547,10 @@
             else:
                 self._repo.ui.status(_('pulling subrepo %s from %s\n')
                                      % (subrelpath(self), srcurl))
+                remotebookmarks = other.listkeys('bookmarks')
                 self._repo.pull(other)
-                bookmarks.updatefromremote(self._repo.ui, self._repo, other,
-                                           srcurl)
+                bookmarks.updatefromremote(self._repo.ui, self._repo,
+                                           remotebookmarks, srcurl)
 
     @annotatesubrepoerror
     def get(self, state, overwrite=False):
--- a/tests/test-bookmarks-pushpull.t	Fri Mar 29 19:52:02 2013 -0700
+++ b/tests/test-bookmarks-pushpull.t	Fri Mar 29 19:54:06 2013 -0700
@@ -204,6 +204,39 @@
      Y                         3:f6fc62dde3c0
      Z                         1:0d2164f0ce0d
 
+update a bookmark in the middle of a client pulling changes
+
+  $ cd ..
+  $ hg clone -q a pull-race
+  $ hg clone -q pull-race pull-race2
+  $ cd pull-race
+  $ hg up -q Y
+  $ echo c4 > f2
+  $ hg ci -Am4
+  $ echo c5 > f3
+  $ cat <<EOF > .hg/hgrc
+  > [hooks]
+  > outgoing.makecommit = hg ci -Am5; echo committed in pull-race
+  > EOF
+  $ cd ../pull-race2
+  $ hg pull
+  pulling from $TESTTMP/pull-race (glob)
+  searching for changes
+  adding changesets
+  adding f3
+  committed in pull-race
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  updating bookmark Y
+  (run 'hg update' to get a working copy)
+  $ hg book
+   * @                         1:0d2164f0ce0d
+     X                         1:0d2164f0ce0d
+     Y                         4:b0a5eff05604
+     Z                         1:0d2164f0ce0d
+  $ cd ../b
+
 diverging a remote bookmark fails
 
   $ hg up -q 4e3505fd9583
--- a/tests/test-hook.t	Fri Mar 29 19:52:02 2013 -0700
+++ b/tests/test-hook.t	Fri Mar 29 19:54:06 2013 -0700
@@ -198,7 +198,6 @@
   listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
   no changes found
   listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
-  listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
   adding remote bookmark bar
   importing bookmark bar
   $ cd ../a
--- a/tests/test-pull-http.t	Fri Mar 29 19:52:02 2013 -0700
+++ b/tests/test-pull-http.t	Fri Mar 29 19:54:06 2013 -0700
@@ -58,7 +58,6 @@
 
   $ req
   pulling from http://localhost:$HGPORT/
-  searching for changes
   abort: authorization failed
   % serve errors