diff --git a/hgext/narrow/__init__.py b/hgext/narrow/__init__.py --- a/hgext/narrow/__init__.py +++ b/hgext/narrow/__init__.py @@ -77,7 +77,7 @@ narrowrepo.wraprepo(repo) if changegroup.NARROW_REQUIREMENT in repo.requirements: narrowcopies.setup(repo) - narrowdirstate.setup(repo) + narrowdirstate.reposetup(repo) narrowpatch.setup(repo) narrowwirepeer.reposetup(repo) @@ -92,6 +92,7 @@ extensions.wrapfunction(verifymod.verifier, '__init__', _verifierinit) extensions.wrapfunction(hg, 'postshare', narrowrepo.wrappostshare) extensions.wrapfunction(hg, 'copystore', narrowrepo.unsharenarrowspec) + narrowdirstate.extsetup() templatekeyword = narrowtemplates.templatekeyword revsetpredicate = narrowtemplates.revsetpredicate diff --git a/hgext/narrow/narrowdirstate.py b/hgext/narrow/narrowdirstate.py --- a/hgext/narrow/narrowdirstate.py +++ b/hgext/narrow/narrowdirstate.py @@ -9,6 +9,7 @@ from mercurial.i18n import _ from mercurial import ( + changegroup, dirstate, error, extensions, @@ -17,12 +18,36 @@ util as hgutil, ) -def setup(repo): +# Mapping of root:str to repo for repos that have the narrow requirement +# specified. +_rootrepomap = {} + +def _getrepo(ds): + """Check if narrow is enabled for repo associated with `ds`; return repo.""" + return _rootrepomap.get(ds._root, None) + +def reposetup(repo): + """Record that we want to do narrowing on this repo. + + We need to wrap the dirstate functions only once - when using chg, reposetup + is potentially called multiple times on the same repo for the lifetime of + the process, so we wrap in extsetup() and do a check in every function for + whether or not it's enabled for that repo, in case the same chg process is + being used in a repo that's *not* narrowed. + """ + # This should have been done by __init__.py already, but let's be paranoid. + assert changegroup.NARROW_REQUIREMENT in repo.requirements + + global _rootrepomap + _rootrepomap[repo.root] = repo + +def extsetup(): """Add narrow spec dirstate ignore, block changes outside narrow spec.""" def walk(orig, self, match, subrepos, unknown, ignored, full=True, narrowonly=True): - if narrowonly: + repo = _getrepo(self) + if repo and narrowonly: # hack to not exclude explicitly-specified paths so that they can # be warned later on e.g. dirstate.add() em = matchmod.exact(match._root, match._cwd, match.files()) @@ -36,6 +61,10 @@ editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge'] for func in editfuncs: def _wrapper(orig, self, *args): + repo = _getrepo(self) + if not repo: + return orig(self, *args) + dirstate = repo.dirstate narrowmatch = repo.narrowmatch() for f in args: @@ -46,7 +75,8 @@ extensions.wrapfunction(dirstate.dirstate, func, _wrapper) def filterrebuild(orig, self, parent, allfiles, changedfiles=None): - if changedfiles is None: + repo = _getrepo(self) + if repo and changedfiles is None: # Rebuilding entire dirstate, let's filter allfiles to match the # narrowspec. allfiles = [f for f in allfiles if repo.narrowmatch()(f)] @@ -59,8 +89,9 @@ return backupname.replace('dirstate', narrowspec.FILENAME) def restorebackup(orig, self, tr, backupname): - self._opener.rename(_narrowbackupname(backupname), narrowspec.FILENAME, - checkambig=True) + if _getrepo(self): + self._opener.rename(_narrowbackupname(backupname), + narrowspec.FILENAME, checkambig=True) orig(self, tr, backupname) extensions.wrapfunction(dirstate.dirstate, 'restorebackup', restorebackup) @@ -68,15 +99,17 @@ def savebackup(orig, self, tr, backupname): orig(self, tr, backupname) - narrowbackupname = _narrowbackupname(backupname) - self._opener.tryunlink(narrowbackupname) - hgutil.copyfile(self._opener.join(narrowspec.FILENAME), - self._opener.join(narrowbackupname), hardlink=True) + if _getrepo(self): + narrowbackupname = _narrowbackupname(backupname) + self._opener.tryunlink(narrowbackupname) + hgutil.copyfile(self._opener.join(narrowspec.FILENAME), + self._opener.join(narrowbackupname), hardlink=True) extensions.wrapfunction(dirstate.dirstate, 'savebackup', savebackup) def clearbackup(orig, self, tr, backupname): orig(self, tr, backupname) - self._opener.unlink(_narrowbackupname(backupname)) + if _getrepo(self): + self._opener.unlink(_narrowbackupname(backupname)) extensions.wrapfunction(dirstate.dirstate, 'clearbackup', clearbackup) diff --git a/tests/test-narrow-expanddirstate.t b/tests/test-narrow-expanddirstate.t --- a/tests/test-narrow-expanddirstate.t +++ b/tests/test-narrow-expanddirstate.t @@ -56,6 +56,8 @@ > from mercurial import patch > from mercurial import util as hgutil > + > _repo = None + > > def expandnarrowspec(ui, repo, newincludes=None): > if not newincludes: > return @@ -72,16 +74,16 @@ > for f in repo[b'.'].manifest().walk(added): > repo.dirstate.normallookup(f) > - > def makeds(ui, repo): + > def makeds(ui): > def wrapds(orig, self): > ds = orig(self) > class expandingdirstate(ds.__class__): > @hgutil.propertycache > def _map(self): > ret = super(expandingdirstate, self)._map - > with repo.wlock(), repo.lock(), repo.transaction( + > with _repo.wlock(), _repo.lock(), _repo.transaction( > b'expandnarrowspec'): - > expandnarrowspec(ui, repo, + > expandnarrowspec(ui, _repo, > encoding.environ.get(b'DIRSTATEINCLUDES')) > return ret > ds.__class__ = expandingdirstate @@ -89,11 +91,20 @@ > return wrapds > > def reposetup(ui, repo): + > # Normally there'd be checks that this is a narrowed localrepo, and uses + > # of _repo would skip it if it's not specified; we aren't doing that for + > # this toy version. + > global _repo + > assert _repo is None or _repo.root == repo.root, "%s %s" % ( + > _repo.root, repo.root) + > _repo = repo + > + > def extsetup(ui): > extensions.wrapfilecache(localrepo.localrepository, b'dirstate', - > makeds(ui, repo)) + > makeds(ui)) > def overridepatch(orig, *args, **kwargs): - > with repo.wlock(): - > expandnarrowspec(ui, repo, encoding.environ.get(b'PATCHINCLUDES')) + > with _repo.wlock(): + > expandnarrowspec(ui, _repo, encoding.environ.get(b'PATCHINCLUDES')) > return orig(*args, **kwargs) > > extensions.wrapfunction(patch, b'patch', overridepatch)