diff --git a/mercurial/configitems.py b/mercurial/configitems.py --- a/mercurial/configitems.py +++ b/mercurial/configitems.py @@ -781,6 +781,9 @@ b'format', b'exp-use-side-data', default=False, experimental=True, ) coreconfigitem( + b'format', b'exp-share-safe', default=False, experimental=True, +) +coreconfigitem( b'format', b'internal-phase', default=False, experimental=True, ) coreconfigitem( diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -449,6 +449,11 @@ # The repository use persistent nodemap for the changelog and the manifest. NODEMAP_REQUIREMENT = b'persistent-nodemap' +# A repository with share implemented safely. The repository has different +# store and working copy requirements i.e. both `.hg/requires` and +# `.hg/store/requires` are present. +SHARESAFE_REQUIREMENT = b'exp-sharesafe' + # Functions receiving (ui, features) that extensions can register to impact # the ability to load repositories with custom requirements. Only # functions defined in loaded extensions are called. @@ -529,6 +534,23 @@ raise requirements = set() + # if .hg/requires contains the exp-sharesafe requirement, it means + # there exists a `.hg/store/requires` too and we should read it + # TODO: make this code more stricter by checking whether store exists + # and other checks + if SHARESAFE_REQUIREMENT in requirements: + if hgvfs.exists(b'sharedpath'): + # This is a shared repo + sharedsource = hgvfs.read(b'sharedpath') + storevfs = vfsmod.vfs(vfsmod.vfs(sharedsource).join(b'store')) + else: + storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True) + try: + store_requirements = set(storevfs.read(b'requires').splitlines()) + requirements |= store_requirements + except IOError as e: + pass + # The .hg/hgrc file may load extensions or contain config options # that influence repository construction. Attempt to load it and # process any new extensions that it may have pulled in. @@ -1034,6 +1056,7 @@ SPARSEREVLOG_REQUIREMENT, NODEMAP_REQUIREMENT, bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT, + SHARESAFE_REQUIREMENT, } _basesupported = supportedformats | { b'store', @@ -1302,7 +1325,12 @@ self._writerequirements() def _writerequirements(self): - scmutil.writerequires(self.vfs, self.requirements) + if SHARESAFE_REQUIREMENT in self.requirements: + with self.lock(): + scmutil.writerequires(self.svfs, self.requirements) + scmutil.writerequires(self.vfs, set([SHARESAFE_REQUIREMENT])) + else: + scmutil.writerequires(self.vfs, self.requirements) # Don't cache auditor/nofsauditor, or you'll end up with reference cycle: # self -> auditor -> self._checknested -> self @@ -3637,6 +3665,11 @@ if ui.configbool(b'format', b'use-persistent-nodemap'): requirements.add(NODEMAP_REQUIREMENT) + # if share-safe is enabled, let's create the new repository with the new + # requirement + if ui.configbool(b'format', b'exp-share-safe'): + requirements.add(SHARESAFE_REQUIREMENT) + return requirements @@ -3760,7 +3793,19 @@ b'layout', ) - scmutil.writerequires(hgvfs, requirements) + if SHARESAFE_REQUIREMENT in requirements and b'store' in requirements: + # TODO: make sure that the source repository has sharesafe requirement # too + if 'sharedrepo' in createopts: + req = 'shared' if 'shared' in requirements else 'relshared' + scmutil.writerequires(hgvfs, set([SHARESAFE_REQUIREMENT, req])) + else: + scmutil.writerequires(hgvfs, set([SHARESAFE_REQUIREMENT])) + storevfs = vfsmod.vfs(hgvfs.join(b'store'), cacheaudited=True) + scmutil.writerequires( + storevfs, requirements - set([SHARESAFE_REQUIREMENT]) + ) + else: + scmutil.writerequires(hgvfs, requirements) # Write out file telling readers where to find the shared store. if b'sharedrepo' in createopts: diff --git a/mercurial/store.py b/mercurial/store.py --- a/mercurial/store.py +++ b/mercurial/store.py @@ -375,7 +375,7 @@ _data = ( b'bookmarks narrowspec data meta 00manifest.d 00manifest.i' - b' 00changelog.d 00changelog.i phaseroots obsstore' + b' 00changelog.d 00changelog.i phaseroots obsstore requires' ) @@ -447,7 +447,7 @@ yield x def copylist(self): - return [b'requires'] + _data.split() + return _data.split() def write(self, tr): pass @@ -687,7 +687,7 @@ def copylist(self): d = ( b'bookmarks narrowspec data meta dh fncache phaseroots obsstore' - b' 00manifest.d 00manifest.i 00changelog.d 00changelog.i' + b' 00manifest.d 00manifest.i 00changelog.d 00changelog.i requires' ) return [b'requires', b'00changelog.i'] + [ b'store/' + f for f in d.split() diff --git a/tests/test-share-safe.t b/tests/test-share-safe.t new file mode 100644 --- /dev/null +++ b/tests/test-share-safe.t @@ -0,0 +1,69 @@ +setup + + $ cat >> $HGRCPATH < [extensions] + > share = + > [format] + > exp-share-safe = True + > EOF + +prepare source repo + + $ hg init source + $ cd source + $ cat .hg/requires + exp-sharesafe + $ cat .hg/store/requires + dotencode + fncache + generaldelta + revlogv1 + sparserevlog + store + $ hg debugrequirements + dotencode + exp-sharesafe + fncache + generaldelta + revlogv1 + sparserevlog + store + + $ echo a > a + $ hg ci -Aqm "added a" + $ echo b > b + $ hg ci -Aqm "added b" + $ cd .. + +Create a shared repo and check the requirements are shared and read correctly + $ hg share source shared1 + updating working directory + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd shared1 + $ cat .hg/requires + exp-sharesafe + shared + + $ hg debugrequirements -R ../source + dotencode + exp-sharesafe + fncache + generaldelta + revlogv1 + sparserevlog + store + + $ hg debugrequirements + dotencode + exp-sharesafe + fncache + generaldelta + revlogv1 + shared + sparserevlog + store + + $ echo c > c + $ hg ci -Aqm "added c" + + $ hg unshare