Changes touching this path
- the tree writers no longer remove the file they just wrote on a recorded case-only rename: materialize_target and materialize_promoted wrote the target tree and then removed each old path the new tree no longer holds by name, so where the filesystem folds case, as this windows machine and a default macos volume do, the removal took off disk what the write had just put there, and loot undo over such a rename reported pruned 1 path off disk, left the directory empty and made the next status read the recorded path as deleted, which loot move and converge did too, each reproduced by hand with the pre-change binary. both writers now prune through loot_core::disk_entry::prune_unwritten, the one home for the file and directory-entry rule apply-patch has taken since #2007 and #2014: an old path that is the directory entry of a path just written is renamed onto the written spelling, which keeps the bytes and gives the last name component the recorded case, the old name of a hard link is still removed by name, NotFound is a path that was not on disk rather than a path pruned, and any other removal failure is an error naming the path, where materialize_promoted discarded the error and materialize_target counted a failed removal as pruned either way. the identity helpers move out of apply_patch.rs into the new module, drop_empty_parents is shared with the view prune, and the prune makes no filesystem call beyond its removals unless an old path and a written path fold alike. the new pins went red with the entry rule removed (cli 1 passed, 3 failed; core 4 passed, 1 failed), with the old path kept and not renamed (cli 1 passed, 3 failed; core 4 passed, 1 failed) and with the removal error discarded and counted anyway (core 3 passed, 2 failed; the cli pins stay green, since none of them fails a removal). the perf gate dry run reads every counter the same before and after and says no move, ADR 0022 carries the amendment, ADR 0082 section 4 says where the rule lives, CONTEXT.md names it beside the apply-patch rule and in the re-pointing entry, and the workspace suite is green (#2011)
de9fb5b0 · dbf3dbe6… - cherry-pick, revert and a surface that settles a stale-disk debt no longer destroy a case-only rename where the filesystem folds case, and the first no longer records the loss: #2011 routed two tree writers through loot_core::disk_entry and wrote that every writer which removes a path it may just have written asks that module, while two that did not were sitting in the tree as the sentence was written. Workspace::apply_change_delta, the one core cherry-pick and revert share, walked the merged actions as a BTreeMap and so wrote A.txt and then removed a.txt by name, which where case folds is the file it had just written, and it snapshots, so loot revert over a recorded case-only rename reported both paths converged over an empty directory and minted a change recording 1 deletion. Workspace::stale_debt_the_surface_left, the one scoped deletion a surface is allowed, compared the written set by name, and a case-only rename preserves the bytes, so the digest guard that exists to protect the operator matched the file the surface had just written: loot surface printed the path it had surfaced, left the directory empty, and the next loot status read it as deleted, which is the reproduction #2011 declined on the false reading that a surface prunes nothing. Each was reproduced by hand through the spawned binary first and then as a new spawned pin, and the pair went red over an empty listing before the change (4 passed, 2 failed) and is green after (6 passed); with the delta writer put back to removing by name they go red again (5 passed, 1 failed) and so does the new census (0 passed, 1 failed), and the stale-debt prune put back reddens the same two with the same counts. Both writers prune through prune_unwritten now. Which working-tree removals are weighed stops being a sentence: workspace.rs carries a census that reads every removal joined onto a working-tree root out of loot-cli/src and loot-core/src, names what each one removes and what the walk cannot see, and it named the offending function under each of those two mutations, went red at its own classifier control with ROOTS emptied and went red with a name dropped from its expected set (0 passed, 1 failed each). one_entry refuses a path that is or passes through a symbolic link now, since file_id follows links and on Windows two paths with one id were otherwise one entry, so fs::rename would have moved a link over the file it points at; the check is the symlink_on apply-patch already made, moved into the module, pinned with a junction here and red without it (5 passed, 1 failed). The hard-link pin names its links A.txt and a.txt so the fold map reaches the entry rule at all, where old.txt and new.txt never called it, and it skips a filesystem that folds case with a note; it stays unix-only and its red-first has not run here, though the body type-checks and runs on windows with the cfg lifted (7 passed). The four case probes become one loot_core::disk_entry::folds_case, and with it answering no the entry pin and two apply-patch case pins go red (5 passed, 1 failed; 18 passed, 2 failed). enclosing_fn moves into source_walk beside the #1929 census that asked it first and strips any visibility, after pub(super) made the new census name the wrong function. ADR 0022 carries the correction and its prunes-nothing claim is gone, ADR 0082 section 4 states where the probe and the link refusal live, CONTEXT.md names both writers and the census, the surface.rs and materialize_target notes stop claiming the two prunes answer the same set of failures, and the rewrap leftovers in ADR 0022, CONTEXT.md and the account page are fixed. The perf gate dry run reads every counter the same before and after and says no move both times, the site gate is green, and the workspace suite is green (3824 passed) (#2018)
baec4112 · dbf3dbe6…diff - review sweep 15 fix-up: the evolog dash is on an empty FIRST LINE, the subject rule now has one home, and the surface prune says plainly that it discards. loot evolog --porcelain dashes its last column when the message first line is empty, which an empty message and a message whose body starts under a blank one both are, and three places said an empty message: the doc above the encoder, the ADR 0023 amendment of 2026-09-17 and CONTEXT.md. The wording moved rather than the bytes, because dashing on an empty message instead is a porcelain byte change for a message that opens with a newline, and a new spawned pin, a_message_whose_first_line_is_empty_is_the_dash_too, puts a run under the sentence: the file reads 5 passed, and with the whole message put back in the column it is 2 passed, 3 failed, the single-line control among the two that stay green. render::subject_line was billed as the one home while six call sites derived that first line inline, the four the ticket named plus the shortlog author rollup and the missing-object listing, so log --oneline, heads, show, range-diff and those two all ask it now, and the near-cousins that each do something more, the note ellipsis, the revert subject and the loot-first PR title, are named beside it rather than routed. The control is the spawned binary: 28 outputs over two repos, one carrying a multi-line message and one a blank first line, are byte-identical before and after, and with subject_line returning the whole message 10 of 11 of them move and the loot-cli lib suite reads 1219 passed, 4 failed, naming the shortlog rollup and the oneline row among them. The surface stale-debt prune keeps the removal error it discards, which #2018 declined and its closing note then reported as fixed, and the doc above it now says the let _ is deliberate, that apply_change_delta propagates where this one does not, and why: the delta writer snapshots, so a refused removal would be recorded as a deletion the disk never took. The removal census in workspace.rs states what its matcher is blind to, a type-annotated binding, a call split over lines and a path derived from a root-joined one, and names disk_entry::drop_empty_parents, whose remove_dir climbs from a dest its caller joined and which the fold cannot reach, because remove_dir takes an empty directory only and a directory holding a file the writer just wrote is not empty; widening the matcher would not have reached that call, so the blindness is stated instead. CONTEXT.md gives that census its crate and spelling scope. migration_serial records that hold blocks and is not re-entrant, and that it serialises migration runs rather than pg_authid, naming the fixed-name CREATE ROLE in tests/rls.rs that writes the shared catalog outside it; Held loses its Option and its Drop impl, since dropping the field is what the compiler already does. The migration census compares a BTreeSet the way the removal census does: with the const reordered it stays green where the Vec compare it replaced goes red (0 passed, 1 failed), and a name dropped from the const reddens it (0 passed, 1 failed). pg/tests.rs cross-references its own source walk and fn finder to source_walk in loot-cli and states where the pair differs. The folds_case helpers in two test files become folds_case_under, so neither shadows the disk_entry rule it asks. The rewrap leftover in account.tsx that #2018 moved rather than fixed is rewrapped with its neighbours, and so is a second one mid-paragraph in CONTEXT.md that the same rewrap reached. The forge suite is green against a throwaway 17.0 cluster (469 passed over nine binaries, no skip note) and its lock case goes red with the session dropped rather than held (0 passed, 1 failed), the site gate is green (643 passed, every surface under its ceiling), and the workspace suite is green (3833 passed) (#2023)
c38e3299 · dbf3dbe6…diff
Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.