Artifact 3b5f8e1f5da7571b30b52d484320be5a2daad052d455a5b459f31272387db7e4:
- File docs/RELEASE-NOTES-0.3.12.md — part of check-in [5a962c69ff] at 2026-09-11 18:49:10 on branch trunk — 0.3.12: version bump and release notes (user: dmestas, size: 3768) [annotate] [blame] [check-ins using]
zit 0.3.12
zit 0.3.12 makes sync announce only what its clusters do not already say.
A pull or push between two peers used to open with one igot line for
every held artifact — 904,898 lines, about 63 MB per direction, per round
on pkgsrc, whether or not anything had changed. zit now indexes the
cluster artifacts it holds, announces only the unclustered tail, and mints
clusters of its own where fossil does. The same no-op pull sends 4.7 KB.
No artifact, tag namespace, sync message, or on-disk format changed. The
cluster index lives in .zit-cache/ and is rebuilt from the log if lost.
Bounded announcement over the cluster index (zit#184)
A cluster artifact (M cards and a Z card, nothing else) is igot in bulk:
once a peer folds an artifact into a cluster it stops announcing it
individually, and a later cluster names the earlier one, so the whole
repository is reachable from the newest. zit consumed clusters on receipt
but never used them on send and never minted any. Now:
- Each sync open resolves a small index of the held clusters into what they already name (not announced again) and what they promise that this repository never collected (seeded as phantoms, so an interrupted pull resumes from what its own clusters announced — the recovery fossil gets from durable phantom rows, which zit does not write).
- The server announces held ∧ ¬clustered (fossil's
send_unclustered);pragma send-catalogrestores the full inventory (the RFC's resync state);pragma req-clustersemits every cluster's igot, enclosed per §5.3. A pushed cluster's unheld members are interned so the same reply begs for them. - The client announces held ∧ ¬clustered; clone is exempt as before.
- A pull reply mints clusters — sorted, ≤800 M cards, md5 Z — once 100 or more held artifacts are unclustered, at fossil's point and thresholds. A repository born in zit clusters itself the first time anyone pulls from it.
- Promises a held cluster makes that the peer cannot keep (private or
shunned upstream) are reported once as a warning; the pull still says
already in syncand exits 0. They are requests the run made on its own initiative, not expectations the peer set — the same rule that already keeps a shunned phantom from failing a pull. - Absent or stale index → the old full announcement. Bytes, never correctness.
Measured, no-op pull, release build, loopback:
| store | artifacts | clusters | request before | request after | warm pull |
|---|---|---|---|---|---|
| pkgsrc (fossil-imported, flat) | 904,898 | 2,439 | ~63 MB | 4,748 B | 5.4s |
| sqlite (fossil-imported) | 131,558 | 1,004 | ~9.2 MB | 1,183 B | 0.74s |
| zit-native, 161 artifacts | 161 | 0 → 1 minted | 161 igot | 201 B | — |
The index costs one walk of the store the first time a sync opens it
(pkgsrc 210s, sqlite 19s — once), then a ~100 KB sidecar adopted with no
walk. Evidence seam: ZIT_SYNC_STATS=1 prints rounds, bytes each way,
clusters indexed, and rids walked.
Compatibility
- Sync protocol unchanged; a fossil peer sees a client that announces its unclustered tail and a server that mints clusters — fossil's own shape.
- No on-disk or wire format changed. Fossil RFC revision 00 remains the target.
- No command or flag changed. One new stderr line on a pull whose clusters promise artifacts the peer cannot supply.
Qualification
zig build test, e2e, smoke (Fossil interop against a real fossil),
muscle (41 differential steps), git-import-e2e, git-export-e2e, and
dst, plus the three-runner CI battery. The table's before figures are the
0.3.11 announcement volume (one 70-byte igot line per held artifact); the
after figures are ZIT_SYNC_STATS on the same stores.