diff --git a/hgext/narrow/TODO.rst b/hgext/narrow/TODO.rst --- a/hgext/narrow/TODO.rst +++ b/hgext/narrow/TODO.rst @@ -1,6 +1,3 @@ -Integration with the share extension needs improvement. Right now -we've seen some odd bugs. - Address commentary in manifest.excludedmanifestrevlog.add - specifically we should improve the collaboration with core so that add() never gets called on an excluded directory and we can improve diff --git a/hgext/narrow/narrowcommands.py b/hgext/narrow/narrowcommands.py --- a/hgext/narrow/narrowcommands.py +++ b/hgext/narrow/narrowcommands.py @@ -339,6 +339,8 @@ ('', 'clear', False, _('whether to replace the existing narrowspec')), ('', 'force-delete-local-changes', False, _('forces deletion of local changes when narrowing')), + ('', 'update-working-copy', False, + _('update working copy when the store has changed')), ] + commands.remoteopts, _('[OPTIONS]... [REMOTE]'), inferrepo=True) @@ -398,8 +400,9 @@ addedexcludes = narrowspec.parsepatterns(opts['addexclude']) removedexcludes = narrowspec.parsepatterns(opts['removeexclude']) + update_working_copy = opts['update_working_copy'] only_show = not (addedincludes or removedincludes or addedexcludes or - removedexcludes or newrules) + removedexcludes or newrules or update_working_copy) oldincludes, oldexcludes = repo.narrowpats @@ -428,6 +431,12 @@ fm.end() return 0 + if update_working_copy: + with repo.wlock(), repo.lock(), repo.transaction('narrow-wc') as tr: + narrowspec.updateworkingcopy(repo, tr) + narrowspec.copytoworkingcopy(repo, tr) + return 0 + if not widening and not narrowing: ui.status(_("nothing to widen or narrow\n")) return 0 diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -38,6 +38,7 @@ narrowspec, node, phases, + repository as repositorymod, scmutil, sshpeer, statichttprepo, @@ -331,6 +332,9 @@ template = ('[paths]\n' 'default = %s\n') destrepo.vfs.write('hgrc', util.tonativeeol(template % default)) + if repositorymod.NARROW_REQUIREMENT in sourcerepo.requirements: + with destrepo.wlock(): + narrowspec.copytoworkingcopy(destrepo, None) def _postshareupdate(repo, update, checkout=None): """Maybe perform a working directory update after a shared repo is created. @@ -731,7 +735,7 @@ local = destpeer.local() if local: if narrow: - with local.lock(): + with local.wlock(), local.lock(): local.setnarrowpats(storeincludepats, storeexcludepats) u = util.url(abspath) diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -1227,6 +1227,7 @@ def _narrowmatch(self): if repository.NARROW_REQUIREMENT not in self.requirements: return matchmod.always(self.root, '') + narrowspec.checkworkingcopynarrowspec(self) include, exclude = self.narrowpats return narrowspec.match(self.root, include=include, exclude=exclude) @@ -1251,7 +1252,14 @@ def setnarrowpats(self, newincludes, newexcludes): narrowspec.save(self, newincludes, newexcludes) + narrowspec.copytoworkingcopy(self, self.currenttransaction()) self.invalidate(clearfilecache=True) + # So the next access won't be considered a conflict + # TODO: It seems like there should be a way of doing this that + # doesn't involve replacing these attributes. + self.narrowpats = newincludes, newexcludes + self._narrowmatch = narrowspec.match(self.root, include=newincludes, + exclude=newexcludes) def __getitem__(self, changeid): if changeid is None: diff --git a/mercurial/narrowspec.py b/mercurial/narrowspec.py --- a/mercurial/narrowspec.py +++ b/mercurial/narrowspec.py @@ -13,12 +13,16 @@ from . import ( error, match as matchmod, + merge, repository, sparse, 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 @@ -157,6 +161,18 @@ spec = format(includepats, excludepats) repo.svfs.write(FILENAME, spec) +def copytoworkingcopy(repo, tr): + if tr: + def write(file): + spec = repo.svfs.read(FILENAME) + file.write(spec) + file.close() + tr.addfilegenerator('narrowspec', (DIRSTATE_FILENAME,), write, + location='plain') + else: + spec = repo.svfs.read(FILENAME) + repo.vfs.write(DIRSTATE_FILENAME, spec) + def savebackup(repo, backupname): if repository.NARROW_REQUIREMENT not in repo.requirements: return @@ -226,3 +242,57 @@ 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): + actions = merge.emptyactions() + addgaction = actions['g'].append + mf = repo['.'].manifest() + for f in files: + if not repo.wvfs.exists(f): + addgaction((f, (mf.flags(f), False), "narrowspec updated")) + merge.applyupdates(repo, actions, wctx=repo[None], + mctx=repo['.'], overwrite=False) + +def checkworkingcopynarrowspec(repo): + storespec = repo.svfs.tryread(FILENAME) + wcspec = repo.vfs.tryread(DIRSTATE_FILENAME) + if wcspec != storespec: + raise error.Abort(_("working copy's narrowspec is stale"), + hint=_("run 'hg tracked --update-working-copy'")) + +def updateworkingcopy(repo, tr): + oldspec = repo.vfs.tryread(DIRSTATE_FILENAME) + newspec = repo.svfs.tryread(FILENAME) + + 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) + + repo.narrowpats = newincludes, newexcludes + repo._narrowmatch = newmatch + pctx = repo['.'] + newfiles = [f for f in pctx.manifest().walk(addedmatch) if f not in ds] + for f in newfiles: + ds.normallookup(f) + _writeaddedfiles(repo, pctx, newfiles) + + ds.write(tr) diff --git a/tests/test-narrow-debugcommands.t b/tests/test-narrow-debugcommands.t --- a/tests/test-narrow-debugcommands.t +++ b/tests/test-narrow-debugcommands.t @@ -6,6 +6,7 @@ > path:foo > [exclude] > EOF + $ cp .hg/store/narrowspec .hg/narrowspec.dirstate $ echo treemanifest >> .hg/requires $ echo narrowhg-experimental >> .hg/requires $ mkdir -p foo/bar 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 @@ -75,13 +75,20 @@ deleting meta/d5/00manifest.i (tree !) $ hg -R main tracked I path:d7 + $ hg -R main files + abort: working copy's narrowspec is stale + (run 'hg tracked --update-working-copy') + [255] + $ hg -R main tracked --update-working-copy + not deleting possibly dirty file d3/f + not deleting possibly dirty file d3/g + not deleting possibly dirty file d5/f # d1/f, d3/f, d3/g and d5/f should no longer be reported $ hg -R main files 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 | sort - main/d1/f main/d3/f main/d3/g main/d5/f @@ -102,16 +109,20 @@ I path:d1 I path:d3 I path:d7 + $ hg -R main files + abort: working copy's narrowspec is stale + (run 'hg tracked --update-working-copy') + [255] + $ hg -R main tracked --update-working-copy # d1/f, d3/f 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 @@ -130,3 +141,30 @@ checking files checked 11 changesets with 3 changes to 3 files $ cd .. + +Dirstate should be left alone when upgrading from version of hg that didn't support narrow+share + + $ hg share main share-upgrade + updating working directory + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd share-upgrade + $ echo x >> d1/f + $ echo y >> d3/g + $ hg add d3/g + $ hg rm d7/f + $ hg st + M d1/f + A d3/g + R d7/f +Make it look like a repo from before narrow+share was supported + $ rm .hg/narrowspec.dirstate + $ hg st + abort: working copy's narrowspec is stale + (run 'hg tracked --update-working-copy') + [255] + $ hg tracked --update-working-copy + $ hg st + M d1/f + A d3/g + R d7/f + $ cd .. diff --git a/tests/test-narrow-trackedcmd.t b/tests/test-narrow-trackedcmd.t --- a/tests/test-narrow-trackedcmd.t +++ b/tests/test-narrow-trackedcmd.t @@ -107,6 +107,8 @@ --clear whether to replace the existing narrowspec --force-delete-local-changes forces deletion of local changes when narrowing + --update-working-copy update working copy when the store has + changed -e --ssh CMD specify ssh command to use --remotecmd CMD specify hg command to run on the remote side --insecure do not verify server certificate (ignoring