Skip to content
zit Check-in [7235a86bcd]
Login

Check-in [7235a86bcd]

Overview
Comment:mv: a reversal cancels the rename record instead of replacing it

`mv --help` advertises `zit mv NEW OLD` as the way to undo a rename, so it is the sequence a user is most likely to run. It left the pending record replaced rather than cleared: OLD->NEW->OLD queued a §4.7.6 old-name F card asserting OLD was formerly NEW -- a name no manifest has ever carried. The next commit with any real change would write that false claim about the past into a permanent artifact, where it cannot be taken back. A rename and its inverse are a no-op, so the pair now nets the record out.

This survived the guard because the guard's oracle was indirect: commit reports "nothing to commit" on CONTENT alone, so a stranded record passes it silently and surfaces only at the next commit that carries a change. The record is now read back directly.

mv's move errors render through `reasonOf` rather than `@errorName`, so EACCES reads "permission denied" instead of "AccessDenied". This was deferred as errno-sweep work, but the mapping already exists and `init` and `clean` print through it -- reuse, not new scope. A raw Zig error name is an internal identifier, not a diagnostic.

Timelines: family | ancestors | descendants | both | blockers
Files: files | file ages | folders
SHA3-256: 7235a86bcd769aa76338fdbe827f12625177bba0f7dc78bbb7ef7eeebc45669e
User & Date: dmestas on 2026-07-19 17:27:18.120
Other Links: branch diff | manifest | tags
Context
2026-07-19
17:32
Merge blockers: a failed mv changes nothing, and its recovery is exact (B4)

mv recorded a rename in the managed set and persisted it before attempting the filesystem move, with no rollback. A move into a nonexistent directory left the set naming a path that held no file: status invented a missing entry, commit refused over it, and the recovery mv --help documents did not work.

mv now validates that the destination's parent exists and refuses before touching anything, so the failing case records nothing.

Two further defects on the same path. An inverse rename replaced the pending record instead of dropping it, so 'mv a b' followed by 'mv b a' left a b-to-a rename queued; the next commit carrying a real change would then emit an old-name F card asserting the file was formerly named b, a name never committed. That is false provenance in a permanent artifact, produced by the exact sequence the help text advertises as recovery. mvRecord now nets an inverse rename back out.

The error path printed raw Zig error names — a user saw 'AccessDenied' where clean and init already print 'permission denied' through reasonOf. It now uses the same mapping.

A residual window remains: a crash between the move and the record write is not atomic, documented at the site. Full working-tree crash consistency is a separate concern. check-in: 33a2ceb6d1 user: dmestas tags: trunk

17:27
mv: a reversal cancels the rename record instead of replacing it

`mv --help` advertises `zit mv NEW OLD` as the way to undo a rename, so it is the sequence a user is most likely to run. It left the pending record replaced rather than cleared: OLD->NEW->OLD queued a §4.7.6 old-name F card asserting OLD was formerly NEW -- a name no manifest has ever carried. The next commit with any real change would write that false claim about the past into a permanent artifact, where it cannot be taken back. A rename and its inverse are a no-op, so the pair now nets the record out.

This survived the guard because the guard's oracle was indirect: commit reports "nothing to commit" on CONTENT alone, so a stranded record passes it silently and surfaces only at the next commit that carries a change. The record is now read back directly.

mv's move errors render through `reasonOf` rather than `@errorName`, so EACCES reads "permission denied" instead of "AccessDenied". This was deferred as errno-sweep work, but the mapping already exists and `init` and `clean` print through it -- reuse, not new scope. A raw Zig error name is an internal identifier, not a diagnostic. closed check-in: 7235a86bcd user: dmestas tags: blockers

16:38
mv: move the working file before recording; failed mv records nothing

mv recorded the rename durably before attempting the filesystem move and rolled nothing back when it failed, leaving the set naming a path that does not exist and commit ready to record a rename that never happened. The reversal mv --help promises -- "run zit mv NEW OLD" -- failed too: it saw the old path still occupied and refused the collision.

mvCommit now renames first, then records; if the record fails the file is renamed back and nothing is persisted. Move-first beats record-first because undoing a durable write needs a second durable write, which can itself fail, nesting a failure inside the failure handler. Renaming a file back cannot realistically fail -- the source directory and name are still free. Build the guarantee on the rollback that cannot fail.

mvCheckLegal gains a dest-parent-exists check: stat of a path under a missing directory fails, so the old collision check read "absent" and waved it through. Residual window: a crash between move and record leaves the file at NEW with the set naming OLD, recoverable with add NEW + rm OLD.

ENOENT and EACCES were both rendered "input/output error". check-in: d7dd9ea30b user: dmestas tags: blockers

Changes