Skip to content
zit A tree is still a file history
Login

A tree is still a file history

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.