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.
Lint Skipped |
Unit Tests Skipped |
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: |