C A\stree\sis\sstill\sa\sfile\shistory D 2026-09-16T08:43:20.918 E 2026-09-16T08:43:20.918 e72f92ca3f4a23ffeb5308b887e04ea0c1f65ac6 N text/x-markdown U dmestas W 3883 # 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. Z 5af3b4e72ea9a7e28b2f0dca63ae5945