repair: add functionality to rebuild fncache
Currently, there is no way to recover from a missing or corrupt fncache
file in place (a clone is required). For certain use cases such as
servers and with large repositories, an in-place repair may be
desirable. This patch adds functionality for in-place repair of the
fncache.
The `hg debugrebuildfncache` command is introduced. It ensures the
fncache is up to date by reconstructing the fncache from all seen files
encountered during a brute force traversal of the repository's entire
history.
The command will add missing entries and will prune excess ones.
Currently, the command no-ops unless the repository has the fncache
requirement. The command could later grow the ability to "upgrade" an
existing repository to be fncache enabled, if desired.
When testing this patch on a local clone of the Firefox repository, it
removed a bunch of entries. Investigation revealed that removed entries
belonged to empty (0 byte size) .i filelogs. The functionality for
pruning fncache of stripped revlogs was introduced in
f49d60fa40a5, so
the presence of these entries likely predates this feature.
--- a/mercurial/commands.py Tue Jun 23 13:56:53 2015 -0400
+++ b/mercurial/commands.py Mon Jun 22 09:59:48 2015 -0700
@@ -21,7 +21,7 @@
import dagparser, context, simplemerge, graphmod, copies
import random
import setdiscovery, treediscovery, dagutil, pvec, localrepo
-import phases, obsolete, exchange, bundle2
+import phases, obsolete, exchange, bundle2, repair
import ui as uimod
table = {}
@@ -2728,6 +2728,11 @@
finally:
wlock.release()
+@command('debugrebuildfncache', [], '')
+def debugrebuildfncache(ui, repo):
+ """rebuild the fncache file"""
+ repair.rebuildfncache(ui, repo)
+
@command('debugrename',
[('r', 'rev', '', _('revision to debug'), _('REV'))],
_('[-r REV] FILE'))
--- a/mercurial/repair.py Tue Jun 23 13:56:53 2015 -0400
+++ b/mercurial/repair.py Mon Jun 22 09:59:48 2015 -0700
@@ -227,3 +227,72 @@
vfs.unlink(chgrpfile)
repo.destroyed()
+
+def rebuildfncache(ui, repo):
+ """Rebuilds the fncache file from repo history.
+
+ Missing entries will be added. Extra entries will be removed.
+ """
+ repo = repo.unfiltered()
+
+ if 'fncache' not in repo.requirements:
+ ui.warn(_('(not rebuilding fncache because repository does not '
+ 'support fncache\n'))
+ return
+
+ lock = repo.lock()
+ try:
+ fnc = repo.store.fncache
+ # Trigger load of fncache.
+ if 'irrelevant' in fnc:
+ pass
+
+ oldentries = set(fnc.entries)
+ newentries = set()
+ seenfiles = set()
+
+ repolen = len(repo)
+ for rev in repo:
+ ui.progress(_('changeset'), rev, total=repolen)
+
+ ctx = repo[rev]
+ for f in ctx.files():
+ # This is to minimize I/O.
+ if f in seenfiles:
+ continue
+ seenfiles.add(f)
+
+ i = 'data/%s.i' % f
+ d = 'data/%s.d' % f
+
+ if repo.store._exists(i):
+ newentries.add(i)
+ if repo.store._exists(d):
+ newentries.add(d)
+
+ ui.progress(_('changeset'), None)
+
+ addcount = len(newentries - oldentries)
+ removecount = len(oldentries - newentries)
+ for p in sorted(oldentries - newentries):
+ ui.write(_('removing %s\n') % p)
+ for p in sorted(newentries - oldentries):
+ ui.write(_('adding %s\n') % p)
+
+ if addcount or removecount:
+ ui.write(_('%d items added, %d removed from fncache\n') %
+ (addcount, removecount))
+ fnc.entries = newentries
+ fnc._dirty = True
+
+ tr = repo.transaction('fncache')
+ try:
+ fnc.write(tr)
+ tr.close()
+ finally:
+ tr.release()
+ else:
+ ui.write(_('fncache already up to date\n'))
+ finally:
+ lock.release()
+
--- a/tests/test-completion.t Tue Jun 23 13:56:53 2015 -0400
+++ b/tests/test-completion.t Mon Jun 22 09:59:48 2015 -0700
@@ -96,6 +96,7 @@
debugpushkey
debugpvec
debugrebuilddirstate
+ debugrebuildfncache
debugrename
debugrevlog
debugrevspec
@@ -254,6 +255,7 @@
debugpushkey:
debugpvec:
debugrebuilddirstate: rev
+ debugrebuildfncache:
debugrename: rev
debugrevlog: changelog, manifest, dir, dump
debugrevspec: optimize
--- a/tests/test-fncache.t Tue Jun 23 13:56:53 2015 -0400
+++ b/tests/test-fncache.t Mon Jun 22 09:59:48 2015 -0700
@@ -283,3 +283,98 @@
1 files, 1 changesets, 1 total revisions
$ cat .hg/store/fncache
data/y.i
+
+ $ cd ..
+
+debugrebuildfncache does nothing unless repo has fncache requirement
+
+ $ hg --config format.usefncache=false init nofncache
+ $ cd nofncache
+ $ hg debugrebuildfncache
+ (not rebuilding fncache because repository does not support fncache
+
+ $ cd ..
+
+debugrebuildfncache works on empty repository
+
+ $ hg init empty
+ $ cd empty
+ $ hg debugrebuildfncache
+ fncache already up to date
+ $ cd ..
+
+debugrebuildfncache on an up to date repository no-ops
+
+ $ hg init repo
+ $ cd repo
+ $ echo initial > foo
+ $ echo initial > .bar
+ $ hg commit -A -m initial
+ adding .bar
+ adding foo
+
+ $ cat .hg/store/fncache | sort
+ data/.bar.i
+ data/foo.i
+
+ $ hg debugrebuildfncache
+ fncache already up to date
+
+debugrebuildfncache restores deleted fncache file
+
+ $ rm -f .hg/store/fncache
+ $ hg debugrebuildfncache
+ adding data/.bar.i
+ adding data/foo.i
+ 2 items added, 0 removed from fncache
+
+ $ cat .hg/store/fncache | sort
+ data/.bar.i
+ data/foo.i
+
+Rebuild after rebuild should no-op
+
+ $ hg debugrebuildfncache
+ fncache already up to date
+
+A single missing file should get restored, an extra file should be removed
+
+ $ cat > .hg/store/fncache << EOF
+ > data/foo.i
+ > data/bad-entry.i
+ > EOF
+
+ $ hg debugrebuildfncache
+ removing data/bad-entry.i
+ adding data/.bar.i
+ 1 items added, 1 removed from fncache
+
+ $ cat .hg/store/fncache | sort
+ data/.bar.i
+ data/foo.i
+
+ $ cd ..
+
+Try a simple variation without dotencode to ensure fncache is ignorant of encoding
+
+ $ hg --config format.dotencode=false init nodotencode
+ $ cd nodotencode
+ $ echo initial > foo
+ $ echo initial > .bar
+ $ hg commit -A -m initial
+ adding .bar
+ adding foo
+
+ $ cat .hg/store/fncache | sort
+ data/.bar.i
+ data/foo.i
+
+ $ rm .hg/store/fncache
+ $ hg debugrebuildfncache
+ adding data/.bar.i
+ adding data/foo.i
+ 2 items added, 0 removed from fncache
+
+ $ cat .hg/store/fncache | sort
+ data/.bar.i
+ data/foo.i
--- a/tests/test-help.t Tue Jun 23 13:56:53 2015 -0400
+++ b/tests/test-help.t Mon Jun 22 09:59:48 2015 -0700
@@ -796,6 +796,8 @@
debugrebuilddirstate
rebuild the dirstate as it would look like for the given
revision
+ debugrebuildfncache
+ rebuild the fncache file
debugrename dump rename information
debugrevlog show data and statistics about a revlog
debugrevspec parse and apply a revision specification