Skip to content

fix(switch): keep name-conflicted files that are in the target view's inherited state - #199

Merged
graywolf336 merged 3 commits into
devfrom
fix/view-switch-file-loss
Sep 21, 2026
Merged

graywolf336 merged 3 commits into
devfrom
fix/view-switch-file-loss

Conversation

@graywolf336

Copy link
Copy Markdown
Contributor

Summary

A view switch deleted files that were in the target view's inherited state when those files had a materialization name-conflict (two inodes visibly claiming the same path). In the reported incident, switching views repeatedly removed atomic-core/src/change/publication_gate.rs, atomic-core/src/pristine/txn/write/view.rs, and atomic-core/src/pristine/audit.rs from the working tree — breaking compilation (E0583 file not found for module …) and leaving a perpetually-modified state that blocked every subsequent switch with "Working copy has unrecorded changes".

Root cause

Repository::switch_view computes old/new visible file sets via visible_file_paths (materialize.rs) and Phase 2 removes old_files − new_files. But TREE is a single-valued path→inode index: when a second inode visibly claims the same path (a name conflict), visible_file_paths only sees the single TREE binding — whose introducing change may not be in the target view's visible set, even though the path is in the target's inherited state (via the other binding). The switch therefore classified a conflicted-but-inherited file as "old view only" and deleted it.

The materializer already walks REV_TREE to recover every inode claiming a path for its name-conflict detection (materialize.rs:485-531); visible_file_paths lacked the same recovery.

Fix

visible_file_paths now also walks REV_TREE and keeps a path when any inode binding it has its introducing change visible on the view (mirroring the materializer's conflict detection). Paths with a single binding behave identically.

Verification

  • New regression test switch_to_inheriting_child_keeps_name_conflicted_file (atomic-repository/src/repository/tests/switch_file_loss_tests.rs):
    • fails before the fix: switch deleted a name-conflicted file that is in the child's inherited state
    • passes after: the file survives the switch with its conflict markers intact
  • Full atomic-repository suite: 872 passed, 0 failed
  • cargo fmt --check + cargo clippy -D warnings: clean

Impact

  • No more working-tree corruption (file deletion) on view switch
  • No broken compilation from modules deleted out from under mod.rs
  • Per-intent view workflow unblocked
  • No wire/format/storage changes

… inherited state

A materialization name-conflict (two inodes visibly claiming one path)
made `visible_file_paths` drop the path: TREE is single-valued per path,
so only the single binding was considered, and the switch's Phase 2 then
classified the file as "old view only" and DELETED it from the working
tree — even though the path was in the target view's inherited state.
The deletion broke compilation and left a stuck modified state that
blocked every subsequent switch.

`visible_file_paths` now walks REV_TREE (mirroring the materializer's
name-conflict detection) and keeps a path when ANY inode binding it has
its introducing change visible on the view. Unaffected paths behave
identically.

Regression test: `switch_to_inheriting_child_keeps_name_conflicted_file`
fails pre-fix (file deleted), passes post-fix (file kept with markers).
Full atomic-repository suite green (872 tests).
…e loss

39_view_switch_name_conflict.sh drives the real CLI: two views create
the same path independently, insert feature→dev surfaces a name
conflict, a child view forks from dev, and switching into the child
must keep f.txt (with its conflict markers).

Verified: the scenario FAILS against origin/dev (file deleted) and
PASSES with the fix.
…DERS

Re-derived the fix from atomic's model: a view is just a FILTER over the
global graph — every change and node the view exposes already lives in
the graph; TREE is only a single-valued bookkeeping index. A path
belongs in the view's file set iff the filter renders a live chain at
it.

The previous REV_TREE pass kept a path on visible-introducing-change
alone. That fixed the name-conflict deletion, but a visible-but-
SUPERSEDED claimant (dead under the filter) could still keep a path the
view does not render — a stale file left in the working tree. The pass
now also requires `is_file_alive_via_retrieval` under the view's filter
— the same supersession-alive predicate the materializer's name-conflict
detection uses — so `visible_file_paths` agrees with materialize on
exactly what the view renders.

switch_file_loss suite + full atomic-repository suite (872) green.
@graywolf336

Copy link
Copy Markdown
Contributor Author

Relationship to #206

These two are complementary halves of the same name-conflict story, not duplicates:

Together: #199 keeps the conflict visible across switches; #206 keeps it safe to edit and resolve. #199's code doesn't overlap #206's files, but both touch the same problem area — see my longer note on #206.

@graywolf336
graywolf336 merged commit 2be8987 into dev Sep 21, 2026
8 checks passed
@graywolf336
graywolf336 deleted the fix/view-switch-file-loss branch September 21, 2026 17:22
geekgonecrazy added a commit that referenced this pull request Sep 21, 2026
graywolf336 added a commit that referenced this pull request Sep 21, 2026
…tch (gapless)

Two suites claimed number 39 (39_ambient_inode_views from #206 and
39_view_switch_name_conflict from #199). Keep the ambient suite at 39,
re-home the view-switch regression at 41 — the follow-on suites land at 42 and 43, keeping 01–43 gapless to restore unique numbering.
graywolf336 added a commit that referenced this pull request Sep 21, 2026
Two suites claimed number 39 (39_ambient_inode_views from #206 and
39_view_switch_name_conflict from #199). Keep the ambient suite at 39,
re-home the view-switch regression at 41. The session's other two
suites land at 42 (view_create_parent, #200) and 43
(record_status_name_conflict, #203), so the final numbering is gapless:
01 through 43.

No behavior change; run_all selects by prefix and no references point
at the old name.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants