.
C An\sindex\sthat\ssurvives\sa\scommit
D 2026-09-14T16:50:47.236
E 2026-09-14T16:50:47.236 3f58202e745aaa7453cb5fa090f31d624a24dbb8
N text/x-markdown
U dmestas
W 3971
# An index that survives a commit
0.3.14 gave `zit log CHECKIN PATH` an index. One file's history on a mirror
of SQLite went from over a minute to under two seconds, and the release
notes had a table to prove it.
The table was true and the index was nearly useless.
## What was missing
The index is a fact the derive already computes and throws away: for each
check-in, the set of paths whose content moved against the primary parent.
Inverted by path, that is a file's history. It is stored as a `.zit-cache`
member, keyed — like the cluster and disposition maps — to the log tail and
the shun fingerprint, so a store that has moved on is never answered from a
stale map.
Keyed to the tail is the problem. A key that says *this index describes a
log ending at T* has exactly one useful answer to give: yes, or no. Commit
once and the answer is no, and the only thing the code could do with no was
throw the file away and walk all 36,702 check-ins again.
So the index helped precisely when nothing was happening. On a repository
anyone was actually working in — which is the only kind worth indexing —
the first PATH query after every commit paid the full rebuild. The
benchmark measured a case the benchmark had created.
## What changed
The stored index now records one more number: `n_rids`, how many log
entries it was built from. Validation moved from an exact tail match to a
prefix match, which is the whole idea in one line — an index built over a
shorter log is still a correct index *over that prefix*. It does not have
to be discarded. It has to be extended.
A query now adopts what is on disk, walks only the entries past `n_rids`,
folds the two together, and stores the union. Three cases, one path
through the code:
| on disk | walked |
|---|---|
| nothing | every check-in |
| an index over the whole log | none |
| an index over the log minus your last five commits | five |
On the SQLite mirror (131,558 artifacts), `log --long -n 5 README.md`:
```
no index 36,702 check-ins walked 55.4s
index, unchanged 0 check-ins walked 0.52s
a different path 0 check-ins walked 0.51s
```
The third line is the one that matters more than it looks. The index is
per-repository, not per-path: asking about a second file costs nothing,
because the work was never about the file you asked for.
## The test that would have caught it
There wasn't one, and that is the honest lesson here. Every 0.3.14 test
asserted the answer — byte-identical output against reconstruction, for
every tracked path. All of them passed. None of them asserted the *cost*,
so the version that rebuilt from scratch on every commit looked exactly
like the version that didn't.
`ZIT_FILEINDEX_WALK=1` now prints how many check-ins the walk covered, and
an end-to-end test commits into a repository with a warm index and asserts
that number is exactly the number of check-ins added. A regression to
rebuild-everything fails the gate instead of showing up as a slow
afternoon six weeks later.
## What this is not
The index narrows the walk. It does not decide the answer. `captureFileFacts`
diffs at F-card level, so a delta manifest restating a path with the hash it
already had reads as a change there while the path's effective content did
not move — an over-report, never an omission. Every candidate the index
names is still confirmed by the change predicate, which is what keeps the
answer exact rather than merely fast. `ZIT_NO_FILE_INDEX=1` answers by
reconstruction alone, and the test comparing the two for every tracked path
is unchanged.
The cache stays advisory: delete it and you pay one rebuild. A v1 file from
0.3.14 is ignored rather than misread.
## What is still slow
About half a second of every figure above is opening the repository. With
the walk at zero that is now the entire cost of a warm PATH query, and it is
paid again by every single CLI invocation. It is the next thing, and it is
tracked on its own.
Z cea3f97608e6e62441fe5ca157801f65