diff mercurial/repair.py @ 25652:2882d6886919

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.
author Gregory Szorc <gregory.szorc@gmail.com>
date Mon, 22 Jun 2015 09:59:48 -0700
parents ff955e7b1085
children 328739ea70c3
line wrap: on
line diff
--- 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()
+