diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -1215,6 +1215,7 @@ def _narrowmatch(self): if repository.NARROW_REQUIREMENT not in self.requirements: return matchmod.always(self.root, '') + narrowspec.updatesharenarrowspec(self) include, exclude = self.narrowpats return narrowspec.match(self.root, include=include, exclude=exclude) diff --git a/mercurial/narrowspec.py b/mercurial/narrowspec.py --- a/mercurial/narrowspec.py +++ b/mercurial/narrowspec.py @@ -18,7 +18,10 @@ util, ) +# The file in .hg/store/ that indicates which paths exit in the store FILENAME = 'narrowspec' +# The file in .hg/ that indicates which paths exit in the dirstate +DIRSTATE_FILENAME = 'narrowspec.dirstate' # Pattern prefixes that are allowed in narrow patterns. This list MUST # only contain patterns that are fast and safe to evaluate. Keep in mind @@ -226,3 +229,75 @@ else: res_includes = set(req_includes) return res_includes, res_excludes, invalid_includes + +# These two are extracted for extensions (specifically for Google's CitC file +# system) +def _deletecleanfiles(repo, files): + for f in files: + repo.wvfs.unlinkpath(f) + +def _writeaddedfiles(repo, pctx, files): + for f in files: + if not repo.wvfs.exists(f): + repo.wvfs.write(f, pctx[f].data()) + +def updatesharenarrowspec(repo): + # Check if we have already compared the working copy's narrowspec to the + # store's narrowspec. This prevents infinite recursion with manifestlog + # creation. + if getattr(repo, '_sharenarrowspecchecked', False): + return + repo._sharenarrowspecchecked = True + + oldspec = repo.vfs.tryread(DIRSTATE_FILENAME) + newspec = repo.svfs.tryread(FILENAME) + if not oldspec: + # There was no narrowspec for the working copy, only one for the + # store. That should only happen on repos created before we had + # support for narrow+share. Assume the dirstate already matches + # the store's narrowspec and write the store's narrowspec to + # the working copy narrowspec. + try: + with repo.wlock(wait=False): + repo.vfs.write(DIRSTATE_FILENAME, newspec) + except error.LockError: + pass + return + elif newspec == oldspec: + return + + try: + wlock = repo.wlock(False) + except error.LockError: + # We cannot update the dirstate and .hg/narrowspec.dirstate this + # time. That's fine, we'll try again on the next hg invocation. + return + with wlock: + oldincludes, oldexcludes = parseconfig(repo.ui, oldspec) + newincludes, newexcludes = parseconfig(repo.ui, newspec) + oldmatch = match(repo.root, include=oldincludes, exclude=oldexcludes) + newmatch = match(repo.root, include=newincludes, exclude=newexcludes) + addedmatch = matchmod.differencematcher(newmatch, oldmatch) + removedmatch = matchmod.differencematcher(oldmatch, newmatch) + + ds = repo.dirstate + lookup, status = ds.status(removedmatch, subrepos=[], ignored=False, + clean=True, unknown=False) + _deletecleanfiles(repo, status.clean) + trackeddirty = lookup + status.modified + status.added + for f in sorted(trackeddirty): + repo.ui.status(_('not deleting possibly dirty file %s\n') % f) + for f in status.clean + trackeddirty: + ds.drop(f) + + pctx = repo['.'] + newfiles = list(pctx.manifest().walk(addedmatch)) + # TODO: should probably use the merge.update() code here + # so the file-writing is parallelized + for f in newfiles: + if f not in ds: + ds.normallookup(f) + _writeaddedfiles(repo, pctx, newfiles) + + ds.write(repo.currenttransaction()) + repo.vfs.write(DIRSTATE_FILENAME, newspec) diff --git a/tests/test-narrow-share.t b/tests/test-narrow-share.t --- a/tests/test-narrow-share.t +++ b/tests/test-narrow-share.t @@ -77,11 +77,13 @@ I path:d7 # d1/f, d3/f, d3/g and d5/f should no longer be reported $ hg -R main files + not deleting possibly dirty file d3/f + not deleting possibly dirty file d3/g + not deleting possibly dirty file d5/f main/d7/f # d1/f should no longer be there, d3/f should be since it was dirty, d3/g should be there since # it was added, and d5/f should be since we couldn't be sure it was clean $ find main/d* -type f - main/d1/f main/d3/g main/d3/f main/d5/f @@ -105,17 +107,44 @@ $ hg -R main files main/d1/f main/d3/f - main/d3/g main/d7/f # d1/f, d3/f, d3/g should be back $ hg -R main files main/d1/f main/d3/f - main/d3/g main/d7/f # d3/f should be modified (not clobbered by the widening), and d3/g should be untracked $ hg -R main st --all M d3/f - A d3/g + ? d3/g C d1/f C d7/f + +Check that failure to get the lock is not a problem + +#if unix-permissions system-sh + $ cat >> sleep10 << EOF + > sleep 10 + > EOF + $ chmod +x sleep10 + $ hg -R main --config hooks.update="$TESTTMP/sleep10" up 9 -C > /dev/null 2>&1 & + $ echo $! > $DAEMON_PIDS + $ hg -R share1 tracked --removeinclude d7 -q +# d7/f is no longer reported + $ hg -R main files + main/d1/f + main/d3/f +# but d7/f is still in the dirstate because we failed to update it earlier + $ hg -R main debugdirstate --no-dates + n 644 2 * d1/f (glob) + n 644 2 * d3/f (glob) + n 644 2 * d7/f (glob) + $ killdaemons.py +# Now the update should work + $ hg -R main st + ? d3/g + $ hg -R main debugdirstate --no-dates + n 644 2 * d1/f (glob) + n 644 2 * d3/f (glob) + +#endif