bookmarks: keep bookmarks in .hg/store if new config set
authorMartin von Zweigbergk <martinvonz@google.com>
Wed, 15 May 2019 22:09:02 -0700
changeset 42325 526750cdd02d
parent 42319 b162229ebe0d
child 42326 5d4ec64a6fcb
bookmarks: keep bookmarks in .hg/store if new config set Bookmarks storage consists of two parts: (1) the set of bookmarks and their positions, and (2) the current bookmark. The former can get updated by exchange, while the latter cannot. However, they are both stored in directly .hg/ and protected by repo.wlock(). As a result, ugly workarounds were needed. This patch introduces a new config option to store the set of bookmarks and their positions in .hg/store/ but still storing the current bookmark directory in .hg/. The config option only takes effect at repo creation time. It results in a new requirement being set. Differential Revision: https://phab.mercurial-scm.org/D6387
hgext/share.py
mercurial/bookmarks.py
mercurial/configitems.py
mercurial/help/config.txt
mercurial/localrepo.py
mercurial/store.py
tests/test-help.t
tests/test-share-bookmarks.t
--- a/hgext/share.py	Thu May 16 08:15:20 2019 +0900
+++ b/hgext/share.py	Wed May 15 22:09:02 2019 -0700
@@ -125,6 +125,10 @@
 
 def _hassharedbookmarks(repo):
     """Returns whether this repo has shared bookmarks"""
+    if bookmarks.bookmarksinstore(repo):
+        # Kind of a lie, but it means that we skip our custom reads and writes
+        # from/to the source repo.
+        return False
     try:
         shared = repo.vfs.read('shared').splitlines()
     except IOError as inst:
--- a/mercurial/bookmarks.py	Thu May 16 08:15:20 2019 +0900
+++ b/mercurial/bookmarks.py	Wed May 15 22:09:02 2019 -0700
@@ -33,6 +33,14 @@
 # custom styles
 activebookmarklabel = 'bookmarks.active bookmarks.current'
 
+BOOKMARKS_IN_STORE_REQUIREMENT = 'bookmarksinstore'
+
+def bookmarksinstore(repo):
+    return BOOKMARKS_IN_STORE_REQUIREMENT in repo.requirements
+
+def bookmarksvfs(repo):
+    return repo.svfs if bookmarksinstore(repo) else repo.vfs
+
 def _getbkfile(repo):
     """Hook so that extensions that mess with the store can hook bm storage.
 
@@ -40,7 +48,7 @@
     bookmarks or the committed ones. Other extensions (like share)
     may need to tweak this behavior further.
     """
-    fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks')
+    fp, pending = txnutil.trypending(repo.root, bookmarksvfs(repo), 'bookmarks')
     return fp
 
 class bmstore(object):
@@ -91,8 +99,11 @@
                         # ValueError:
                         # - node in nm, for non-20-bytes entry
                         # - split(...), for string without ' '
-                        repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
-                                     % pycompat.bytestr(line))
+                        bookmarkspath = '.hg/bookmarks'
+                        if bookmarksinstore(repo):
+                            bookmarkspath = '.hg/store/bookmarks'
+                        repo.ui.warn(_('malformed line in %s: %r\n')
+                                     % (bookmarkspath, pycompat.bytestr(line)))
         except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
@@ -192,8 +203,9 @@
         """record that bookmarks have been changed in a transaction
 
         The transaction is then responsible for updating the file content."""
+        location = '' if bookmarksinstore(self._repo) else 'plain'
         tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
-                            location='plain')
+                            location=location)
         tr.hookargs['bookmark_moved'] = '1'
 
     def _writerepo(self, repo):
@@ -203,9 +215,14 @@
             rbm.active = None
             rbm._writeactive()
 
-        with repo.wlock():
-            with repo.vfs('bookmarks', 'w', atomictemp=True,
-                          checkambig=True) as f:
+        if bookmarksinstore(repo):
+            vfs = repo.svfs
+            lock = repo.lock()
+        else:
+            vfs = repo.vfs
+            lock = repo.wlock()
+        with lock:
+            with vfs('bookmarks', 'w', atomictemp=True, checkambig=True) as f:
                 self._write(f)
 
     def _writeactive(self):
@@ -428,7 +445,11 @@
     return d
 
 def pushbookmark(repo, key, old, new):
-    with repo.wlock(), repo.lock(), repo.transaction('bookmarks') as tr:
+    if bookmarksinstore(repo):
+        wlock = util.nullcontextmanager()
+    else:
+        wlock = repo.wlock()
+    with wlock, repo.lock(), repo.transaction('bookmarks') as tr:
         marks = repo._bookmarks
         existing = hex(marks.get(key, ''))
         if existing != old and existing != new:
--- a/mercurial/configitems.py	Thu May 16 08:15:20 2019 +0900
+++ b/mercurial/configitems.py	Wed May 15 22:09:02 2019 -0700
@@ -676,6 +676,9 @@
     default=None,
     generic=True,
 )
+coreconfigitem('format', 'bookmarks-in-store',
+    default=False,
+)
 coreconfigitem('format', 'chunkcachesize',
     default=None,
 )
--- a/mercurial/help/config.txt	Thu May 16 08:15:20 2019 +0900
+++ b/mercurial/help/config.txt	Wed May 15 22:09:02 2019 -0700
@@ -879,6 +879,15 @@
 
     On some system, Mercurial installation may lack `zstd` supports. Default is `zlib`.
 
+``bookmarks-in-store``
+    Store bookmarks in .hg/store/. This means that bookmarks are shared when
+    using `hg share` regardless of the `-B` option.
+
+    Repositories with this on-disk format require Mercurial version 5.1.
+
+    Disabled by default.
+
+
 ``graph``
 ---------
 
--- a/mercurial/localrepo.py	Thu May 16 08:15:20 2019 +0900
+++ b/mercurial/localrepo.py	Wed May 15 22:09:02 2019 -0700
@@ -122,6 +122,25 @@
     def join(self, obj, fname):
         return obj.sjoin(fname)
 
+class mixedrepostorecache(_basefilecache):
+    """filecache for a mix files in .hg/store and outside"""
+    def __init__(self, *pathsandlocations):
+        # scmutil.filecache only uses the path for passing back into our
+        # join(), so we can safely pass a list of paths and locations
+        super(mixedrepostorecache, self).__init__(*pathsandlocations)
+        for path, location in pathsandlocations:
+            _cachedfiles.update(pathsandlocations)
+
+    def join(self, obj, fnameandlocation):
+        fname, location = fnameandlocation
+        if location == '':
+            return obj.vfs.join(fname)
+        else:
+            if location != 'store':
+                raise error.ProgrammingError('unexpected location: %s' %
+                                             location)
+            return obj.sjoin(fname)
+
 def isfilecached(repo, name):
     """check if a repo has already cached "name" filecache-ed property
 
@@ -891,6 +910,7 @@
         'treemanifest',
         REVLOGV2_REQUIREMENT,
         SPARSEREVLOG_REQUIREMENT,
+        bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
     }
     _basesupported = supportedformats | {
         'store',
@@ -1205,7 +1225,8 @@
         cls = repoview.newtype(self.unfiltered().__class__)
         return cls(self, name, visibilityexceptions)
 
-    @repofilecache('bookmarks', 'bookmarks.current')
+    @mixedrepostorecache(('bookmarks', ''), ('bookmarks.current', ''),
+                         ('bookmarks', 'store'))
     def _bookmarks(self):
         return bookmarks.bmstore(self)
 
@@ -1962,7 +1983,7 @@
                 (self.vfs, 'journal.dirstate'),
                 (self.vfs, 'journal.branch'),
                 (self.vfs, 'journal.desc'),
-                (self.vfs, 'journal.bookmarks'),
+                (bookmarks.bookmarksvfs(self), 'journal.bookmarks'),
                 (self.svfs, 'journal.phaseroots'))
 
     def undofiles(self):
@@ -1977,8 +1998,9 @@
                           encoding.fromlocal(self.dirstate.branch()))
         self.vfs.write("journal.desc",
                           "%d\n%s\n" % (len(self), desc))
-        self.vfs.write("journal.bookmarks",
-                          self.vfs.tryread("bookmarks"))
+        bookmarksvfs = bookmarks.bookmarksvfs(self)
+        bookmarksvfs.write("journal.bookmarks",
+                           bookmarksvfs.tryread("bookmarks"))
         self.svfs.write("journal.phaseroots",
                            self.svfs.tryread("phaseroots"))
 
@@ -2048,8 +2070,9 @@
         vfsmap = {'plain': self.vfs, '': self.svfs}
         transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
                              checkambigfiles=_cachedfiles)
-        if self.vfs.exists('undo.bookmarks'):
-            self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
+        bookmarksvfs = bookmarks.bookmarksvfs(self)
+        if bookmarksvfs.exists('undo.bookmarks'):
+            bookmarksvfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
         if self.svfs.exists('undo.phaseroots'):
             self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
         self.invalidate()
@@ -3003,6 +3026,9 @@
     if createopts.get('lfs'):
         requirements.add('lfs')
 
+    if ui.configbool('format', 'bookmarks-in-store'):
+        requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
+
     return requirements
 
 def filterknowncreateopts(ui, createopts):
--- a/mercurial/store.py	Thu May 16 08:15:20 2019 +0900
+++ b/mercurial/store.py	Wed May 15 22:09:02 2019 -0700
@@ -337,7 +337,7 @@
         mode = None
     return mode
 
-_data = ('narrowspec data meta 00manifest.d 00manifest.i'
+_data = ('bookmarks narrowspec data meta 00manifest.d 00manifest.i'
          ' 00changelog.d 00changelog.i phaseroots obsstore')
 
 def isrevlog(f, kind, st):
@@ -612,7 +612,7 @@
                     raise
 
     def copylist(self):
-        d = ('narrowspec data meta dh fncache phaseroots obsstore'
+        d = ('bookmarks narrowspec data meta dh fncache phaseroots obsstore'
              ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
         return (['requires', '00changelog.i'] +
                 ['store/' + f for f in d.split()])
--- a/tests/test-help.t	Thu May 16 08:15:20 2019 +0900
+++ b/tests/test-help.t	Wed May 15 22:09:02 2019 -0700
@@ -1513,6 +1513,8 @@
   
       "revlog-compression"
   
+      "bookmarks-in-store"
+  
       "profiling"
       -----------
   
--- a/tests/test-share-bookmarks.t	Thu May 16 08:15:20 2019 +0900
+++ b/tests/test-share-bookmarks.t	Wed May 15 22:09:02 2019 -0700
@@ -1,6 +1,13 @@
+#testcases vfs svfs
+
   $ echo "[extensions]"      >> $HGRCPATH
   $ echo "share = "          >> $HGRCPATH
 
+#if svfs
+  $ echo "[format]"                  >> $HGRCPATH
+  $ echo "bookmarks-in-store = yes " >> $HGRCPATH
+#endif
+
 prepare repo1
 
   $ hg init repo1
@@ -33,17 +40,21 @@
   $ cd ../repo2
   $ hg book bm2
   $ hg bookmarks
+     bm1                       2:c2e0ac586386 (svfs !)
    * bm2                       2:c2e0ac586386
   $ cd ../repo3
   $ hg bookmarks
      bm1                       2:c2e0ac586386
+     bm2                       2:c2e0ac586386 (svfs !)
   $ hg book bm3
   $ hg bookmarks
      bm1                       2:c2e0ac586386
+     bm2                       2:c2e0ac586386 (svfs !)
    * bm3                       2:c2e0ac586386
   $ cd ../repo1
   $ hg bookmarks
    * bm1                       2:c2e0ac586386
+     bm2                       2:c2e0ac586386 (svfs !)
      bm3                       2:c2e0ac586386
 
 check whether HG_PENDING makes pending changes only in relatd
@@ -70,14 +81,18 @@
   $ hg --config hooks.pretxnclose="sh $TESTTMP/checkbookmarks.sh" -q book bmX
   @repo1
      bm1                       2:c2e0ac586386
+     bm2                       2:c2e0ac586386 (svfs !)
      bm3                       2:c2e0ac586386
    * bmX                       2:c2e0ac586386
   @repo2
+     bm1                       2:c2e0ac586386 (svfs !)
    * bm2                       2:c2e0ac586386
+     bm3                       2:c2e0ac586386 (svfs !)
   @repo3
      bm1                       2:c2e0ac586386
+     bm2                       2:c2e0ac586386 (svfs !)
    * bm3                       2:c2e0ac586386
-     bmX                       2:c2e0ac586386
+     bmX                       2:c2e0ac586386 (vfs !)
   transaction abort!
   rollback completed
   abort: pretxnclose hook exited with status 1
@@ -92,11 +107,15 @@
   $ hg --config hooks.pretxnclose="sh $TESTTMP/checkbookmarks.sh" -q book bmX
   @repo1
    * bm1                       2:c2e0ac586386
+     bm2                       2:c2e0ac586386 (svfs !)
      bm3                       2:c2e0ac586386
   @repo2
+     bm1                       2:c2e0ac586386 (svfs !)
    * bm2                       2:c2e0ac586386
+     bm3                       2:c2e0ac586386 (svfs !)
   @repo3
      bm1                       2:c2e0ac586386
+     bm2                       2:c2e0ac586386 (svfs !)
      bm3                       2:c2e0ac586386
    * bmX                       2:c2e0ac586386
   transaction abort!
@@ -105,6 +124,11 @@
   [255]
   $ hg book bm3
 
+clean up bm2 since it's uninteresting (not shared in the vfs case and
+same as bm3 in the svfs case)
+  $ cd ../repo2
+  $ hg book -d bm2
+
   $ cd ../repo1
 
 test that commits work