share: implement shared bookmark functionality
authorRyan McElroy <rmcelroy@fb.com>
Sat, 13 Dec 2014 14:31:55 -0800
changeset 23548 141baca16059
parent 23547 21446f4d5c62
child 23549 609ecde2778f
share: implement shared bookmark functionality This does not cause any behavioral change unless a 'bookmarks.shared' marker file exists. A future change will add UI to create this file when a repository is shared.
hgext/share.py
tests/test-share.t
--- a/hgext/share.py	Sat Dec 13 13:56:05 2014 -0800
+++ b/hgext/share.py	Sat Dec 13 14:31:55 2014 -0800
@@ -6,7 +6,9 @@
 '''share a common history between several working directories'''
 
 from mercurial.i18n import _
-from mercurial import cmdutil, hg, util
+from mercurial import cmdutil, hg, util, extensions, bookmarks
+from mercurial.hg import repository, parseurl
+import errno
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
@@ -67,3 +69,61 @@
 
     # update store, spath, sopener and sjoin of repo
     repo.unfiltered().__init__(repo.baseui, repo.root)
+
+def extsetup(ui):
+    extensions.wrapfunction(bookmarks.bmstore, 'getbkfile', getbkfile)
+    extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
+    extensions.wrapfunction(bookmarks.bmstore, 'write', write)
+
+def _hassharedbookmarks(repo):
+    """Returns whether this repo has shared bookmarks"""
+    try:
+        repo.vfs.read('bookmarks.shared')
+        return True
+    except IOError, inst:
+        if inst.errno != errno.ENOENT:
+            raise
+        return False
+
+def _getsrcrepo(repo):
+    """
+    Returns the source repository object for a given shared repository.
+    If repo is not a shared repository, return None.
+    """
+    srcrepo = None
+    try:
+        # strip because some tools write with newline after
+        sharedpath = repo.vfs.read('sharedpath').strip()
+        # the sharedpath always ends in the .hg; we want the path to the repo
+        source = sharedpath.rsplit('/.hg', 1)[0]
+        srcurl, branches = parseurl(source)
+        srcrepo = repository(repo.ui, srcurl)
+    except IOError, inst:
+        if inst.errno != errno.ENOENT:
+            raise
+    return srcrepo
+
+def getbkfile(orig, self, repo):
+    if _hassharedbookmarks(repo):
+        srcrepo = _getsrcrepo(repo)
+        if srcrepo is not None:
+            repo = srcrepo
+    return orig(self, repo)
+
+def recordchange(orig, self, tr):
+    # Continue with write to local bookmarks file as usual
+    orig(self, tr)
+
+    if _hassharedbookmarks(self._repo):
+        srcrepo = _getsrcrepo(self._repo)
+        if srcrepo is not None:
+            category = 'share-bookmarks'
+            tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
+
+def write(orig, self):
+    # First write local bookmarks file in case we ever unshare
+    orig(self)
+    if _hassharedbookmarks(self._repo):
+        srcrepo = _getsrcrepo(self._repo)
+        if srcrepo is not None:
+            self._writerepo(srcrepo)
--- a/tests/test-share.t	Sat Dec 13 13:56:05 2014 -0800
+++ b/tests/test-share.t	Sat Dec 13 14:31:55 2014 -0800
@@ -128,6 +128,175 @@
 
   $ cd ..
 
+
+test sharing bookmarks (manually add bookmarks.shared file for now)
+
+  $ hg share repo1 repo3 && touch repo3/.hg/bookmarks.shared
+  updating working directory
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd repo1
+  $ hg bookmark bm1
+  $ hg bookmarks
+   * bm1                       2:c2e0ac586386
+  $ cd ../repo2
+  $ hg book bm2
+  $ hg bookmarks
+   * bm2                       3:0e6e70d1d5f1
+  $ cd ../repo3
+  $ hg bookmarks
+     bm1                       2:c2e0ac586386
+  $ hg book bm3
+  $ hg bookmarks
+     bm1                       2:c2e0ac586386
+   * bm3                       2:c2e0ac586386
+  $ cd ../repo1
+  $ hg bookmarks
+   * bm1                       2:c2e0ac586386
+     bm3                       2:c2e0ac586386
+
+test that commits work
+
+  $ echo 'shared bookmarks' > a
+  $ hg commit -m 'testing shared bookmarks'
+  $ hg bookmarks
+   * bm1                       3:b87954705719
+     bm3                       2:c2e0ac586386
+  $ cd ../repo3
+  $ hg bookmarks
+     bm1                       3:b87954705719
+   * bm3                       2:c2e0ac586386
+  $ echo 'more shared bookmarks' > a
+  $ hg commit -m 'testing shared bookmarks'
+  created new head
+  $ hg bookmarks
+     bm1                       3:b87954705719
+   * bm3                       4:62f4ded848e4
+  $ cd ../repo1
+  $ hg bookmarks
+   * bm1                       3:b87954705719
+     bm3                       4:62f4ded848e4
+  $ cd ..
+
+test pushing bookmarks works
+
+  $ hg clone repo3 repo4
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd repo4
+  $ hg boo bm4
+  $ echo foo > b
+  $ hg commit -m 'foo in b'
+  $ hg boo
+     bm1                       3:b87954705719
+     bm3                       4:62f4ded848e4
+   * bm4                       5:92793bfc8cad
+  $ hg push -B bm4
+  pushing to $TESTTMP/repo3 (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  exporting bookmark bm4
+  $ cd ../repo1
+  $ hg bookmarks
+   * bm1                       3:b87954705719
+     bm3                       4:62f4ded848e4
+     bm4                       5:92793bfc8cad
+  $ cd ../repo3
+  $ hg bookmarks
+     bm1                       3:b87954705719
+   * bm3                       4:62f4ded848e4
+     bm4                       5:92793bfc8cad
+  $ cd ..
+
+test behavior when sharing a shared repo
+
+  $ hg share repo3 repo5 && touch repo5/.hg/bookmarks.shared
+  updating working directory
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd repo5
+  $ hg book
+     bm1                       3:b87954705719
+     bm3                       4:62f4ded848e4
+     bm4                       5:92793bfc8cad
+  $ cd ..
+
+test what happens when an active bookmark is deleted
+
+  $ cd repo1
+  $ hg boo -d bm3
+  $ hg boo
+   * bm1                       3:b87954705719
+     bm4                       5:92793bfc8cad
+  $ cd ../repo3
+  $ hg boo
+     bm1                       3:b87954705719
+     bm4                       5:92793bfc8cad
+  $ cd ..
+
+verify that bookmarks are not written on failed transaction
+
+  $ cat > failpullbookmarks.py << EOF
+  > """A small extension that makes bookmark pulls fail, for testing"""
+  > from mercurial import extensions, exchange, error
+  > def _pullbookmarks(orig, pullop):
+  >     orig(pullop)
+  >     raise error.HookAbort('forced failure by extension')
+  > def extsetup(ui):
+  >     extensions.wrapfunction(exchange, '_pullbookmarks', _pullbookmarks)
+  > EOF
+  $ cd repo4
+  $ hg boo
+     bm1                       3:b87954705719
+     bm3                       4:62f4ded848e4
+   * bm4                       5:92793bfc8cad
+  $ cd ../repo3
+  $ hg boo
+     bm1                       3:b87954705719
+     bm4                       5:92793bfc8cad
+  $ hg --config "extensions.failpullbookmarks=$TESTTMP/failpullbookmarks.py" pull $TESTTMP/repo4
+  pulling from $TESTTMP/repo4 (glob)
+  searching for changes
+  no changes found
+  adding remote bookmark bm3
+  abort: forced failure by extension
+  [255]
+  $ hg boo
+     bm1                       3:b87954705719
+     bm4                       5:92793bfc8cad
+  $ hg pull $TESTTMP/repo4
+  pulling from $TESTTMP/repo4 (glob)
+  searching for changes
+  no changes found
+  adding remote bookmark bm3
+  $ hg boo
+     bm1                       3:b87954705719
+   * bm3                       4:62f4ded848e4
+     bm4                       5:92793bfc8cad
+  $ cd ..
+
+verify bookmark behavior after unshare
+
+  $ cd repo3
+  $ hg unshare
+  $ hg boo
+     bm1                       3:b87954705719
+   * bm3                       4:62f4ded848e4
+     bm4                       5:92793bfc8cad
+  $ hg boo -d bm4
+  $ hg boo bm5
+  $ hg boo
+     bm1                       3:b87954705719
+     bm3                       4:62f4ded848e4
+   * bm5                       4:62f4ded848e4
+  $ cd ../repo1
+  $ hg boo
+   * bm1                       3:b87954705719
+     bm3                       4:62f4ded848e4
+     bm4                       5:92793bfc8cad
+  $ cd ..
+
 Explicitly kill daemons to let the test exit on Windows
 
   $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS