diff --git a/hgext/share.py b/hgext/share.py --- a/hgext/share.py +++ b/hgext/share.py @@ -125,6 +125,10 @@ def extsetup(ui): 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: diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py --- a/mercurial/bookmarks.py +++ b/mercurial/bookmarks.py @@ -33,6 +33,14 @@ from . import ( # 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 @@ def _getbkfile(repo): 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 @@ class bmstore(object): # 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 @@ class bmstore(object): """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 @@ class bmstore(object): 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 @@ def listbookmarks(repo): 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: diff --git a/mercurial/configitems.py b/mercurial/configitems.py --- a/mercurial/configitems.py +++ b/mercurial/configitems.py @@ -676,6 +676,9 @@ coreconfigitem('extdata', '.*', default=None, generic=True, ) +coreconfigitem('format', 'bookmarks-in-store', + default=False, +) coreconfigitem('format', 'chunkcachesize', default=None, ) diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt --- a/mercurial/help/config.txt +++ b/mercurial/help/config.txt @@ -879,6 +879,15 @@ https://www.mercurial-scm.org/wiki/Missi 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`` --------- diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -122,6 +122,25 @@ class storecache(_basefilecache): 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 @@ class localrepository(object): 'treemanifest', REVLOGV2_REQUIREMENT, SPARSEREVLOG_REQUIREMENT, + bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT, } _basesupported = supportedformats | { 'store', @@ -1205,7 +1225,8 @@ class localrepository(object): 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 @@ class localrepository(object): (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 @@ class localrepository(object): 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 @@ class localrepository(object): 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 @@ def newreporequirements(ui, createopts): 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): diff --git a/mercurial/store.py b/mercurial/store.py --- a/mercurial/store.py +++ b/mercurial/store.py @@ -337,7 +337,7 @@ def _calcmode(vfs): 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 @@ class fncachestore(basicstore): 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()]) diff --git a/tests/test-help.t b/tests/test-help.t --- a/tests/test-help.t +++ b/tests/test-help.t @@ -1513,6 +1513,8 @@ Separate sections from subsections "revlog-compression" + "bookmarks-in-store" + "profiling" ----------- diff --git a/tests/test-share-bookmarks.t b/tests/test-share-bookmarks.t --- a/tests/test-share-bookmarks.t +++ b/tests/test-share-bookmarks.t @@ -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 @@ test sharing bookmarks $ 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 @@ Therefore, this test scenario ignores ch $ 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 @@ src), because (1) HG_PENDING refers only $ 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 @@ src), because (1) HG_PENDING refers only [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