The fact we were calling this from extensions was a sign that it
should live in core.
We were also able to remove some extra attribute aliases from the
share extension.
| durin42 |
| hg-reviewers |
The fact we were calling this from extensions was a sign that it
should live in core.
We were also able to remove some extra attribute aliases from the
share extension.
| Automatic diff as part of commit; lint not applicable. |
| Automatic diff as part of commit; unit tests not applicable. |
Fb's hg-experimental also uses a utility module just for this function, so I am +1 on moving this to core. https://bitbucket.org/facebook/hg-experimental/src/f27f094e91553d3cae5167c0b1c42ae940f888d5/hgext3rd/shareutil.py
| Path | Packages | |||
|---|---|---|---|---|
| M | hgext/journal.py (6 lines) | |||
| M | hgext/narrow/narrowrepo.py (7 lines) | |||
| M | hgext/narrow/narrowspec.py (9 lines) | |||
| M | hgext/share.py (27 lines) | |||
| M | mercurial/hg.py (18 lines) |
| lock, | lock, | ||||
| logcmdutil, | logcmdutil, | ||||
| node, | node, | ||||
| pycompat, | pycompat, | ||||
| registrar, | registrar, | ||||
| util, | util, | ||||
| ) | ) | ||||
| from . import share | |||||
| cmdtable = {} | cmdtable = {} | ||||
| command = registrar.command(cmdtable) | command = registrar.command(cmdtable) | ||||
| # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | ||||
| # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | ||||
| # be specifying the version(s) of Mercurial they are tested with, or | # be specifying the version(s) of Mercurial they are tested with, or | ||||
| # leave the attribute unspecified. | # leave the attribute unspecified. | ||||
| testedwith = 'ships-with-hg-core' | testedwith = 'ships-with-hg-core' | ||||
| orig(sourcerepo, destrepo, **kwargs) | orig(sourcerepo, destrepo, **kwargs) | ||||
| with destrepo.vfs('shared', 'a') as fp: | with destrepo.vfs('shared', 'a') as fp: | ||||
| fp.write('journal\n') | fp.write('journal\n') | ||||
| def unsharejournal(orig, ui, repo, repopath): | def unsharejournal(orig, ui, repo, repopath): | ||||
| """Copy shared journal entries into this repo when unsharing""" | """Copy shared journal entries into this repo when unsharing""" | ||||
| if (repo.path == repopath and repo.shared() and | if (repo.path == repopath and repo.shared() and | ||||
| util.safehasattr(repo, 'journal')): | util.safehasattr(repo, 'journal')): | ||||
| sharedrepo = share._getsrcrepo(repo) | sharedrepo = hg.sharedreposource(repo) | ||||
| sharedfeatures = _readsharedfeatures(repo) | sharedfeatures = _readsharedfeatures(repo) | ||||
| if sharedrepo and sharedfeatures > {'journal'}: | if sharedrepo and sharedfeatures > {'journal'}: | ||||
| # there is a shared repository and there are shared journal entries | # there is a shared repository and there are shared journal entries | ||||
| # to copy. move shared date over from source to destination but | # to copy. move shared date over from source to destination but | ||||
| # move the local file first | # move the local file first | ||||
| if repo.vfs.exists('namejournal'): | if repo.vfs.exists('namejournal'): | ||||
| journalpath = repo.vfs.join('namejournal') | journalpath = repo.vfs.join('namejournal') | ||||
| util.rename(journalpath, journalpath + '.bak') | util.rename(journalpath, journalpath + '.bak') | ||||
| self.user = util.getuser() | self.user = util.getuser() | ||||
| self.ui = repo.ui | self.ui = repo.ui | ||||
| self.vfs = repo.vfs | self.vfs = repo.vfs | ||||
| # is this working copy using a shared storage? | # is this working copy using a shared storage? | ||||
| self.sharedfeatures = self.sharedvfs = None | self.sharedfeatures = self.sharedvfs = None | ||||
| if repo.shared(): | if repo.shared(): | ||||
| features = _readsharedfeatures(repo) | features = _readsharedfeatures(repo) | ||||
| sharedrepo = share._getsrcrepo(repo) | sharedrepo = hg.sharedreposource(repo) | ||||
| if sharedrepo is not None and 'journal' in features: | if sharedrepo is not None and 'journal' in features: | ||||
| self.sharedvfs = sharedrepo.vfs | self.sharedvfs = sharedrepo.vfs | ||||
| self.sharedfeatures = features | self.sharedfeatures = features | ||||
| # track the current command for recording in journal entries | # track the current command for recording in journal entries | ||||
| @property | @property | ||||
| def command(self): | def command(self): | ||||
| commandstr = ' '.join( | commandstr = ' '.join( | ||||
| # narrowrepo.py - repository which supports narrow revlogs, lazy loading | # narrowrepo.py - repository which supports narrow revlogs, lazy loading | ||||
| # | # | ||||
| # Copyright 2017 Google, Inc. | # Copyright 2017 Google, Inc. | ||||
| # | # | ||||
| # This software may be used and distributed according to the terms of the | # This software may be used and distributed according to the terms of the | ||||
| # GNU General Public License version 2 or any later version. | # GNU General Public License version 2 or any later version. | ||||
| from __future__ import absolute_import | from __future__ import absolute_import | ||||
| from mercurial import ( | from mercurial import ( | ||||
| bundlerepo, | bundlerepo, | ||||
| hg, | |||||
| localrepo, | localrepo, | ||||
| match as matchmod, | match as matchmod, | ||||
| scmutil, | scmutil, | ||||
| ) | ) | ||||
| from .. import ( | |||||
| share, | |||||
| ) | |||||
| from . import ( | from . import ( | ||||
| narrowrevlog, | narrowrevlog, | ||||
| narrowspec, | narrowspec, | ||||
| ) | ) | ||||
| # When narrowing is finalized and no longer subject to format changes, | # When narrowing is finalized and no longer subject to format changes, | ||||
| # we should move this to just "narrow" or similar. | # we should move this to just "narrow" or similar. | ||||
| REQUIREMENT = 'narrowhg-experimental' | REQUIREMENT = 'narrowhg-experimental' | ||||
| def wrappostshare(orig, sourcerepo, destrepo, **kwargs): | def wrappostshare(orig, sourcerepo, destrepo, **kwargs): | ||||
| orig(sourcerepo, destrepo, **kwargs) | orig(sourcerepo, destrepo, **kwargs) | ||||
| if REQUIREMENT in sourcerepo.requirements: | if REQUIREMENT in sourcerepo.requirements: | ||||
| with destrepo.wlock(): | with destrepo.wlock(): | ||||
| with destrepo.vfs('shared', 'a') as fp: | with destrepo.vfs('shared', 'a') as fp: | ||||
| fp.write(narrowspec.FILENAME + '\n') | fp.write(narrowspec.FILENAME + '\n') | ||||
| def unsharenarrowspec(orig, ui, repo, repopath): | def unsharenarrowspec(orig, ui, repo, repopath): | ||||
| if (REQUIREMENT in repo.requirements | if (REQUIREMENT in repo.requirements | ||||
| and repo.path == repopath and repo.shared()): | and repo.path == repopath and repo.shared()): | ||||
| srcrepo = share._getsrcrepo(repo) | srcrepo = hg.sharedreposource(repo) | ||||
| with srcrepo.vfs(narrowspec.FILENAME) as f: | with srcrepo.vfs(narrowspec.FILENAME) as f: | ||||
| spec = f.read() | spec = f.read() | ||||
| with repo.vfs(narrowspec.FILENAME, 'w') as f: | with repo.vfs(narrowspec.FILENAME, 'w') as f: | ||||
| f.write(spec) | f.write(spec) | ||||
| return orig(ui, repo, repopath) | return orig(ui, repo, repopath) | ||||
| def wraprepo(repo, opts_narrow): | def wraprepo(repo, opts_narrow): | ||||
| """Enables narrow clone functionality on a single local repository.""" | """Enables narrow clone functionality on a single local repository.""" | ||||
| # narrowspec.py - methods for working with a narrow view of a repository | # narrowspec.py - methods for working with a narrow view of a repository | ||||
| # | # | ||||
| # Copyright 2017 Google, Inc. | # Copyright 2017 Google, Inc. | ||||
| # | # | ||||
| # This software may be used and distributed according to the terms of the | # This software may be used and distributed according to the terms of the | ||||
| # GNU General Public License version 2 or any later version. | # GNU General Public License version 2 or any later version. | ||||
| from __future__ import absolute_import | from __future__ import absolute_import | ||||
| import errno | import errno | ||||
| from mercurial.i18n import _ | from mercurial.i18n import _ | ||||
| from mercurial import ( | from mercurial import ( | ||||
| error, | error, | ||||
| hg, | |||||
| match as matchmod, | match as matchmod, | ||||
| util, | util, | ||||
| ) | ) | ||||
| from .. import ( | |||||
| share, | |||||
| ) | |||||
| FILENAME = 'narrowspec' | FILENAME = 'narrowspec' | ||||
| def _parsestoredpatterns(text): | def _parsestoredpatterns(text): | ||||
| """Parses the narrowspec format that's stored on disk.""" | """Parses the narrowspec format that's stored on disk.""" | ||||
| patlist = None | patlist = None | ||||
| includepats = [] | includepats = [] | ||||
| excludepats = [] | excludepats = [] | ||||
| for l in text.splitlines(): | for l in text.splitlines(): | ||||
| return matchmod.match(root, '', [], include=include or [], | return matchmod.match(root, '', [], include=include or [], | ||||
| exclude=exclude or []) | exclude=exclude or []) | ||||
| def needsexpansion(includes): | def needsexpansion(includes): | ||||
| return [i for i in includes if i.startswith('include:')] | return [i for i in includes if i.startswith('include:')] | ||||
| def load(repo): | def load(repo): | ||||
| if repo.shared(): | if repo.shared(): | ||||
| repo = share._getsrcrepo(repo) | repo = hg.sharedreposource(repo) | ||||
| try: | try: | ||||
| spec = repo.vfs.read(FILENAME) | spec = repo.vfs.read(FILENAME) | ||||
| except IOError as e: | except IOError as e: | ||||
| # Treat "narrowspec does not exist" the same as "narrowspec file exists | # Treat "narrowspec does not exist" the same as "narrowspec file exists | ||||
| # and is empty". | # and is empty". | ||||
| if e.errno == errno.ENOENT: | if e.errno == errno.ENOENT: | ||||
| # Without this the next call to load will use the cached | # Without this the next call to load will use the cached | ||||
| # non-existence of the file, which can cause some odd issues. | # non-existence of the file, which can cause some odd issues. | ||||
| repo.invalidate(clearfilecache=True) | repo.invalidate(clearfilecache=True) | ||||
| return set(), set() | return set(), set() | ||||
| raise | raise | ||||
| return _parsestoredpatterns(spec) | return _parsestoredpatterns(spec) | ||||
| def save(repo, includepats, excludepats): | def save(repo, includepats, excludepats): | ||||
| spec = format(includepats, excludepats) | spec = format(includepats, excludepats) | ||||
| if repo.shared(): | if repo.shared(): | ||||
| repo = share._getsrcrepo(repo) | repo = hg.sharedreposource(repo) | ||||
| repo.vfs.write(FILENAME, spec) | repo.vfs.write(FILENAME, spec) | ||||
| def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes): | def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes): | ||||
| r""" Restricts the patterns according to repo settings, | r""" Restricts the patterns according to repo settings, | ||||
| results in a logical AND operation | results in a logical AND operation | ||||
| :param req_includes: requested includes | :param req_includes: requested includes | ||||
| :param req_excludes: requested excludes | :param req_excludes: requested excludes | ||||
| error, | error, | ||||
| extensions, | extensions, | ||||
| hg, | hg, | ||||
| registrar, | registrar, | ||||
| txnutil, | txnutil, | ||||
| util, | util, | ||||
| ) | ) | ||||
| repository = hg.repository | |||||
| parseurl = hg.parseurl | |||||
| cmdtable = {} | cmdtable = {} | ||||
| command = registrar.command(cmdtable) | command = registrar.command(cmdtable) | ||||
| # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | ||||
| # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | ||||
| # be specifying the version(s) of Mercurial they are tested with, or | # be specifying the version(s) of Mercurial they are tested with, or | ||||
| # leave the attribute unspecified. | # leave the attribute unspecified. | ||||
| testedwith = 'ships-with-hg-core' | testedwith = 'ships-with-hg-core' | ||||
| try: | try: | ||||
| shared = repo.vfs.read('shared').splitlines() | shared = repo.vfs.read('shared').splitlines() | ||||
| except IOError as inst: | except IOError as inst: | ||||
| if inst.errno != errno.ENOENT: | if inst.errno != errno.ENOENT: | ||||
| raise | raise | ||||
| return False | return False | ||||
| return hg.sharedbookmarks in shared | return hg.sharedbookmarks in shared | ||||
| def _getsrcrepo(repo): | |||||
| """ | |||||
| Returns the source repository object for a given shared repository. | |||||
| If repo is not a shared repository, return None. | |||||
| """ | |||||
| if repo.sharedpath == repo.path: | |||||
| return None | |||||
| if util.safehasattr(repo, 'srcrepo') and repo.srcrepo: | |||||
| return repo.srcrepo | |||||
| # the sharedpath always ends in the .hg; we want the path to the repo | |||||
| source = repo.vfs.split(repo.sharedpath)[0] | |||||
| srcurl, branches = parseurl(source) | |||||
| srcrepo = repository(repo.ui, srcurl) | |||||
| repo.srcrepo = srcrepo | |||||
| return srcrepo | |||||
| def getbkfile(orig, repo): | def getbkfile(orig, repo): | ||||
| if _hassharedbookmarks(repo): | if _hassharedbookmarks(repo): | ||||
| srcrepo = _getsrcrepo(repo) | srcrepo = hg.sharedreposource(repo) | ||||
| if srcrepo is not None: | if srcrepo is not None: | ||||
| # just orig(srcrepo) doesn't work as expected, because | # just orig(srcrepo) doesn't work as expected, because | ||||
| # HG_PENDING refers repo.root. | # HG_PENDING refers repo.root. | ||||
| try: | try: | ||||
| fp, pending = txnutil.trypending(repo.root, repo.vfs, | fp, pending = txnutil.trypending(repo.root, repo.vfs, | ||||
| 'bookmarks') | 'bookmarks') | ||||
| if pending: | if pending: | ||||
| # only in this case, bookmark information in repo | # only in this case, bookmark information in repo | ||||
| # See also https://www.mercurial-scm.org/wiki/SharedRepository | # See also https://www.mercurial-scm.org/wiki/SharedRepository | ||||
| return orig(repo) | return orig(repo) | ||||
| def recordchange(orig, self, tr): | def recordchange(orig, self, tr): | ||||
| # Continue with write to local bookmarks file as usual | # Continue with write to local bookmarks file as usual | ||||
| orig(self, tr) | orig(self, tr) | ||||
| if _hassharedbookmarks(self._repo): | if _hassharedbookmarks(self._repo): | ||||
| srcrepo = _getsrcrepo(self._repo) | srcrepo = hg.sharedreposource(self._repo) | ||||
| if srcrepo is not None: | if srcrepo is not None: | ||||
| category = 'share-bookmarks' | category = 'share-bookmarks' | ||||
| tr.addpostclose(category, lambda tr: self._writerepo(srcrepo)) | tr.addpostclose(category, lambda tr: self._writerepo(srcrepo)) | ||||
| def writerepo(orig, self, repo): | def writerepo(orig, self, repo): | ||||
| # First write local bookmarks file in case we ever unshare | # First write local bookmarks file in case we ever unshare | ||||
| orig(self, repo) | orig(self, repo) | ||||
| if _hassharedbookmarks(self._repo): | if _hassharedbookmarks(self._repo): | ||||
| srcrepo = _getsrcrepo(self._repo) | srcrepo = hg.sharedreposource(self._repo) | ||||
| if srcrepo is not None: | if srcrepo is not None: | ||||
| orig(self, srcrepo) | orig(self, srcrepo) | ||||
| >>> defaultdest(b'http://example.org/foo/') | >>> defaultdest(b'http://example.org/foo/') | ||||
| 'foo' | 'foo' | ||||
| ''' | ''' | ||||
| path = util.url(source).path | path = util.url(source).path | ||||
| if not path: | if not path: | ||||
| return '' | return '' | ||||
| return os.path.basename(os.path.normpath(path)) | return os.path.basename(os.path.normpath(path)) | ||||
| def sharedreposource(repo): | |||||
| """Returns repository object for source repository of a shared repo. | |||||
| If repo is not a shared repository, returns None. | |||||
| """ | |||||
| if repo.sharedpath == repo.path: | |||||
| return None | |||||
| if util.safehasattr(repo, 'srcrepo') and repo.srcrepo: | |||||
| return repo.srcrepo | |||||
| # the sharedpath always ends in the .hg; we want the path to the repo | |||||
| source = repo.vfs.split(repo.sharedpath)[0] | |||||
| srcurl, branches = parseurl(source) | |||||
| srcrepo = repository(repo.ui, srcurl) | |||||
| repo.srcrepo = srcrepo | |||||
| return srcrepo | |||||
| def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None, | def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None, | ||||
| relative=False): | relative=False): | ||||
| '''create a shared repository''' | '''create a shared repository''' | ||||
| if not islocal(source): | if not islocal(source): | ||||
| raise error.Abort(_('can only share local repositories')) | raise error.Abort(_('can only share local repositories')) | ||||
| if not dest: | if not dest: | ||||