diff --git a/mercurial/store.py b/mercurial/store.py --- a/mercurial/store.py +++ b/mercurial/store.py @@ -489,10 +489,20 @@ class _fncachevfs(vfsmod.abstractvfs, vf self.encode = encode def __call__(self, path, mode='r', *args, **kw): + encoded = self.encode(path) if mode not in ('r', 'rb') and (path.startswith('data/') or path.startswith('meta/')): - self.fncache.add(path) - return self.vfs(self.encode(path), mode, *args, **kw) + # do not trigger a fncache load when adding a file that already is + # known to exist. + notload = self.fncache.entries is None and self.vfs.exists(encoded) + if notload and 'a' in mode and not self.vfs.stat(encoded).st_size: + # when appending to an existing file, if the file has size zero, + # it should be considered as missing. Such zero-size files are + # the result of truncation when a transaction is aborted. + notload = False + if not notload: + self.fncache.add(path) + return self.vfs(encoded, mode, *args, **kw) def join(self, path): if path: diff --git a/tests/test-fncache.t b/tests/test-fncache.t --- a/tests/test-fncache.t +++ b/tests/test-fncache.t @@ -436,3 +436,73 @@ Try a simple variation without dotencode $ cat .hg/store/fncache | sort data/.bar.i data/foo.i + + $ cd .. + +In repositories that have accumulated a large number of files over time, the +fncache file is going to be large. If we possibly can avoid loading it, so much the better. +The cache should not loaded when committing changes to existing files, or when unbundling +changesets that only contain changes to existing files: + + $ cat > fncacheloadwarn.py << EOF + > from __future__ import absolute_import + > from mercurial import extensions, store + > + > def extsetup(ui): + > def wrapstore(orig, requirements, *args): + > store = orig(requirements, *args) + > if 'store' in requirements and 'fncache' in requirements: + > instrumentfncachestore(store, ui) + > return store + > extensions.wrapfunction(store, 'store', wrapstore) + > + > def instrumentfncachestore(fncachestore, ui): + > class instrumentedfncache(type(fncachestore.fncache)): + > def _load(self): + > ui.warn('fncache load triggered!\n') + > super(instrumentedfncache, self)._load() + > fncachestore.fncache.__class__ = instrumentedfncache + > EOF + + $ fncachextpath=`pwd`/fncacheloadwarn.py + $ hg init nofncacheload + $ cd nofncacheload + $ printf "[extensions]\nfncacheloadwarn=$fncachextpath\n" >> .hg/hgrc + +A new file should trigger a load, as we'd want to update the fncache set in that case: + + $ touch foo + $ hg ci -qAm foo + fncache load triggered! + +But modifying that file should not: + + $ echo bar >> foo + $ hg ci -qm foo + +If a transaction has been aborted, the zero-size truncated index file will +not prevent the fncache from being loaded; rather than actually abort +a transaction, we simulate the situation by creating a zero-size index file: + + $ touch .hg/store/data/bar.i + $ touch bar + $ hg ci -qAm bar + fncache load triggered! + +Unbundling should follow the same rules; existing files should not cause a load: + + $ hg clone -q . tobundle + $ echo 'new line' > tobundle/bar + $ hg -R tobundle ci -qm bar + $ hg -R tobundle bundle -q barupdated.hg + $ hg unbundle -q barupdated.hg + +but adding new files should: + + $ touch tobundle/newfile + $ hg -R tobundle ci -qAm newfile + $ hg -R tobundle bundle -q newfile.hg + $ hg unbundle -q newfile.hg + fncache load triggered! + + $ cd ..