Skip to content
zit Artifact [80c0f72ecd]
Login

Artifact [80c0f72ecd]

Artifact 80c0f72ecda9ba24529eef3b4faa8217d0d066cc6e3c557d8d2d00f369cc31dc:


zit 0.3.32

zit 0.3.32 makes zit purge undo take the repository's write lock before it restores anything, like every other command that writes the store. It also moves the lock into the storage layer, so no future writer can append without it, and adds the tests that should have caught the bug in the first place.

No artifact, tag namespace, sync message, or on-disk repository format changed.

purge undo waits its turn

A zit repository is one append-only file, and every writer takes an exclusive lock on it before appending: commit, clone, pull, a server receiving a push, config and user-table writes, tags, tickets, wiki and forum posts. Two writers that are not serialised both scan the file for its end and both append there, so the second writes on top of the first. The overwriting frames are complete and checksum-valid, so neither zit verify nor crash recovery can see that anything happened: one of the two writes is simply gone.

zit purge undo restores buried artifacts by appending them, but it opened the store without that lock. Run while a commit, a pull loop or a server push was writing to the same repository, from this checkout or another worktree of the same store, either the restored artifacts or the other writer's check-in could be lost without a trace.

It now opens the store the way every other writer does, so it waits for a concurrent writer to finish rather than racing it. The same change makes it resolve a detached worktree's store, which the old direct open did not.

The regression test holds the repository's write lock, starts zit purge undo, and checks that the store is byte-for-byte unchanged a second and a half later. It then releases the lock and checks that the undo completes and the restored store verifies. Against the old code, the store changes while the lock is held and the test fails.

The lock now lives where the appends happen

The purge undo bug was possible because the lock was taken by each command when it opened the file, and one command opened it a different way. The log itself now takes the lock: opening a store for appending takes the exclusive lock first, and only then reads the file's length and scans for its end. A store opened for reading has no append path at all, so a writer that opens the file the wrong way fails loudly instead of writing unlocked.

A source check in the test suite also fails if any code outside the one shared opener opens the repository file for writing.

Tests for the whole class, not the one bug

The deterministic simulation already swept crashes, torn writes and faulty disks, but always with a single writer, and a lock that no one contends for is never exercised. A new sweep runs two to four writers against one simulated log, interleaved step by step, through a file that models the lock as the operating system provides it. Every writer follows a real command's order: find the file's length, open the log, append, commit, close. Another writer can run between any two of those steps.

The simulated file reports an append by a handle that does not hold the lock, or an append that does not land at the file's end, at the moment it happens. Each writer must also see every artifact committed before it took the lock, and must read back what it just wrote, including deltas against other writers' artifacts. At the end, every committed artifact must be present and identical through a fresh reader. Removing the lock fails this sweep, and so does reading the file's length before taking the lock.

An end-to-end test also runs commits from two worktrees, tag writes and user table writes against one store at once, and checks that every one of them landed and the store verifies.

Compatibility

Qualification

zig fmt --check, zig build test, e2e, smoke, git-import-e2e, git-export-e2e, and dst, plus the three-runner CI battery.