Patchwork [1,of,3,V4] share: implement shared bookmark functionality

login
register
mail settings
Submitter Ryan McElroy
Date Dec. 13, 2014, 11:38 p.m.
Message ID <2690905597384d8748f0.1418513934@devbig105.prn2.facebook.com>
Download mbox | patch
Permalink /patch/7082/
State Accepted
Headers show

Comments

Ryan McElroy - Dec. 13, 2014, 11:38 p.m.
# HG changeset patch
# User Ryan McElroy <rmcelroy@fb.com>
# Date 1418509915 28800
#      Sat Dec 13 14:31:55 2014 -0800
# Node ID 2690905597384d8748f00b5ed36dfec4c8922051
# Parent  495bc1b65d25872324a0220354f048b220304bd1
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.
Pierre-Yves David - Dec. 14, 2014, 12:21 a.m.
On 12/13/2014 03:38 PM, Ryan McElroy wrote:
> # HG changeset patch
> # User Ryan McElroy <rmcelroy@fb.com>
> # Date 1418509915 28800
> #      Sat Dec 13 14:31:55 2014 -0800
> # Node ID 2690905597384d8748f00b5ed36dfec4c8922051
> # Parent  495bc1b65d25872324a0220354f048b220304bd1
> share: implement shared bookmark functionality

This patch, (and specifically its transaction related part) seems right 
to me.

I've pushed it to the clowncopter.

I'll let the bikeshredding legions unlishing on the UI patches.

I think Ryan is wide in in proposal of enabling it by default, but not 
changing the older share behavior. It can still break people that use 
share in automated temporary script to share private and public repo. 
But I do not think there is so much of such people out there to a be 
trouble.




>
> 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.
>
> diff --git a/hgext/share.py b/hgext/share.py
> --- a/hgext/share.py
> +++ b/hgext/share.py
> @@ -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 @@ def unshare(ui, repo):
>
>       # 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)
> diff --git a/tests/test-share.t b/tests/test-share.t
> --- a/tests/test-share.t
> +++ b/tests/test-share.t
> @@ -128,6 +128,175 @@ check that a change does not propagate
>
>     $ 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
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel
>

Patch

diff --git a/hgext/share.py b/hgext/share.py
--- a/hgext/share.py
+++ b/hgext/share.py
@@ -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 @@  def unshare(ui, repo):
 
     # 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)
diff --git a/tests/test-share.t b/tests/test-share.t
--- a/tests/test-share.t
+++ b/tests/test-share.t
@@ -128,6 +128,175 @@  check that a change does not propagate
 
   $ 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