Skip to content

Merge a file that changed in the project since the session started - #16

Merged
Kernel-Hunter merged 2 commits into
mainfrom
three-way-merge
Nov 1, 2026
Merged

Kernel-Hunter merged 2 commits into
mainfrom
three-way-merge

Conversation

@Kernel-Hunter

Copy link
Copy Markdown
Owner

Closes #14

With two agents in two sessions on one project editing the same file, the first apply went through and the second reported the file as a conflict. Apply now tries a three-way merge of the session's base, the project's current file and the session's copy before it calls the file a conflict.

A file is merged when it is a modified regular text file (a plain file on all three sides, 4 MB or less, outside .git) and the two sets of changes are in different lines. The merged file goes through the same steps as any other write: backup and journal entry first, then a temp file renamed into place. Before writing, Mudroom checks that the project file is still the one the merge was built from and that the backup hashes to it, so undo puts back exactly the bytes from before the apply. Undo still refuses if the file was edited again after the apply.

These stay conflicts, and nothing is written for them: changes to the same or neighbouring lines, permissions changed differently on both sides, binary files, symlinks, mode-only and type changes, delete against edit, add against add, and anything in .git.

Selected hunks work the same way. Base plus the chosen hunks is merged into the project's file, so a hunk still applies when the project changed somewhere else in the file.

mudroom apply prints merged instead of applied for these files, mudroom review marks them MERGE and mudroom hunks says the file will be merged. The app shows a merge icon in the list, a banner above the diff and the merge count in the result banner. ApplyReport has a new merged list. A merged rollback entry is flagged, so a later apply of the same session merges again instead of writing work/ over the result.

The merge is a small diff3 on top of the existing Myers diff in LineDiff.swift, with no new dependencies. Line terminators are part of each line, so CRLF stays CRLF and a missing final newline stays missing. Converting a whole file between CRLF and LF on one side conflicts with any edit on the other.

Tests are in MergeTests.swift. For the merge itself: separate changes, overlap, adjacent lines, the same change on both sides, CRLF, missing final newline and empty files. For apply: clean merge plus undo, two sessions on one project, overlap writes nothing, CRLF, missing newline, delete against edit, edit against delete, add against add, binary, symlink, mode-only, permissions, setuid, undo after a later edit, .git, hunk merges with step-by-step undo, the journal entry and the review marking. scripts/windows-smoke.ps1 now runs the two-session case through the real CLI on Windows CI.

One existing hunk test changed. Its human edit was in another part of the file, which now merges, so it now edits the hunk's own line to keep testing the conflict.

When a text file changed both in the project and in the session's copy,
apply now does a three-way merge of base/, the project's file and work/.
If the changes are in different lines the merged file is applied, shown
as "merged" in apply's output, `mudroom review`, `mudroom hunks` and the
app, and undo puts the project's file back byte for byte. Overlapping or
adjacent changes stay a conflict and nothing is written. Binary files,
symlinks, mode-only and type changes, delete against edit, add against
add, files over 4 MB and .git stay conflicts as before.

Selected hunks merge the same way, so a hunk still applies when the
project changed elsewhere in the file. A merged write is marked in the
rollback entry, so a later apply merges again instead of writing work/
over it. Files built in memory (hunks and merges) are now checked against
the project copy they were built from, and their backup is hashed, before
anything is written.

Closes #14
…mode bits

Reads of work/ on the merge path stop at the 4 MB text limit, so a file
that grew after the diff fails its hash check instead of being read whole.
Conflict reasons are escaped like paths before they reach a terminal. An
in-memory write keeps setuid/setgid only when it keeps the project's own
mode, and its journal entry records the expected result, so an undo after
a crash mid-write still recognises the file.
@Kernel-Hunter
Kernel-Hunter merged commit 4f098e2 into main Nov 1, 2026
10 checks passed
@Kernel-Hunter
Kernel-Hunter deleted the three-way-merge branch November 1, 2026 15:40
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.

Three-way merge when a file changed in the project since the session started

1 participant