0.3.14 gave zit log CHECKIN PATH an index and 0.3.15 made it durable. On a
submanifest repository both did nothing, and the reason is worth telling,
because the index looked like it was working the whole time.
The shape of the non-bug
A submanifest check-in is a tree: its file set is carried by S cards that point at sub-manifests, not by F cards on the check-in itself. The change-set capture that feeds the index diffs two flat file lists, and it refused a tree outright — "unknown at this layer" — because flattening a tree lives behind a facade the derive layer did not reach into, and a wrong file list is worse than an honest "I don't know".
So on a submanifest store the index recorded every check-in as unknown. That is not a crash and not a wrong answer. The index was built, was valid, was adopted on the next query, and answered nothing: a PATH query fell back to reconstructing each check-in, one manifest at a time, exactly as it had before any index existed. On a pkgsrc mirror of 218,444 tree check-ins that was about 39 minutes, every query, forever, while a decoded index header sat on disk looking healthy.
The tell was in the header the whole time: 218,048 of 218,444 check-ins listed as unknown. An index that knows nothing is indistinguishable from a fast one until you ask it how much it knows.
The fix is a substitution, not a new layer
The facade already had the seam. flattenTreeFromLogMemo takes a bare log
store and a caller-owned memo — it was written for a run of check-ins, because
a tree shares almost every node with its parent's and re-resolving them per
check-in is the cost the memo exists to avoid. The diff step now flattens a
tree through that seam on first sight, and manifestFilesOf hands the flattened
set to the same diff machinery, which never learns trees exist. A tree that
cannot be flattened — a sub-manifest absent, shunned, or corrupt — stays
unknown, which is the one case the old refusal was really protecting.
The index format moved to version 3. A version-2 blob built before this change recorded every tree check-in as unknown and would have been trusted as valid forever; it is refused and rebuilt once.
What it costs and what it buys
| pkgsrc submanifest mirror, 218,444 check-ins | before | after |
|---|---|---|
| check-ins the index cannot answer | 218,048 | 0 |
| a warm PATH query | ~39 min, every time | 1.09s |
| the index build | 39 min, and useless | 34 min, once |
The same history minted flat answers a PATH query in about the same second, so a submanifest store now gets what a flat store gets. Answers are identical to reconstruction, checked per path.
The build is honest work: it flattens each tree and diffs two complete file lists, so it costs files-per-check-in, not files-changed. On a wide tree that is real time spent once. Making it proportional to what actually changed — diffing the S cards and descending only into the sub-manifests whose hash moved — is the next step, tracked on its own.
Two smaller things in the same release
zit artifact now renders a tree check-in's S cards. The card printer had a
lane for every card letter except S, so a tree check-in displayed as a manifest
with no files at all while ls on the same hash listed thousands. That is also
why, more than once, a tree store looked empty or broken when it was neither.
And a saved login now authenticates a push that names no user. push --user
had always saved its derived secret, but autosync pushes with no arguments, and
the credential resolver returned anonymous the instant no user was named — so a
clone whose peer wanted a login pulled automatically and never pushed
automatically. The saved login now stands in, the way fossil reuses a
remembered one, and it never prompts, because there is no one at the keyboard
when autosync runs.