Summary
gix_merge::blob::builtin_driver::text::merge with the Myers diff algorithm produces a false conflict on certain inputs where git merge-file (also Myers-based) resolves cleanly. Switching to Histogram resolves the issue.
Reproduction
Given three versions of a file:
- base (71 lines) - original version
- ours (164 lines) - heavily expanded version with ~90 new lines inserted before a comment
- theirs (70 lines) - identical to base except one comment line deleted
The only change in base→theirs is the deletion of a single comment line:
--- base
+++ theirs
@@ -29,7 +29,6 @@
const uiState = inject(UI_STATE);
const claudeCodeService = inject(CLAUDE_CODE_SERVICE);
- // ── MCP config modal ─────────────────────────────────────────────
let mcpConfigModal = $state<CodegenMcpConfigModal>();
In base→ours, this same comment exists but is now at line 102 (instead of 32) because ~70 lines of new code were inserted above it. The content around it (let mcpConfigModal, const mcpClaudeConfigQuery) is unchanged.
Expected behavior (matches git merge-file)
Clean merge: keep all the new code from ours, apply the comment deletion from theirs. git merge-file exits 0 with no conflict markers.
git merge-file -p ours.svelte base.svelte theirs.svelte
# exit 0, 163 lines, 0 conflict markers
Actual behavior
gix_merge::blob::builtin_driver::text::merge with Algorithm::Myers reports Resolution::Conflict:
<<<<<<< ours
// ── Actions ──────────────────────────────────────────────────────
async function onAbort() { ... }
async function sendMessage(prompt) { ... }
async function handleAnswerQuestion(answers) { ... }
// ── MCP config modal ─────────────────────────────────────────────
=======
>>>>>>> theirs
gix sees the comment deletion as conflicting with the large insertion above it. The conflict region spans lines 78-105 of the merged output.
Key finding: Histogram algorithm works correctly
// Myers: Resolution::Conflict (1 false conflict marker)
// Histogram: Resolution::Complete (0 conflicts) ✓
This suggests the Myers implementation in imara-diff produces different hunk boundaries than git's xdiff for this input, causing the 3-way merge to see overlapping regions where git doesn't.
Minimal reproduction code
use gix_merge::blob::builtin_driver::text;
let base = std::fs::read("base.svelte").unwrap();
let ours = std::fs::read("ours.svelte").unwrap();
let theirs = std::fs::read("theirs.svelte").unwrap();
let mut output = Vec::new();
let mut input = imara_diff::intern::InternedInput::new(&[][..], &[][..]);
// Myers: produces false conflict
let options = text::Options {
diff_algorithm: imara_diff::Algorithm::Myers,
conflict: text::Conflict::Keep {
style: text::ConflictStyle::Merge,
marker_size: std::num::NonZeroU8::new(7).unwrap(),
},
};
let labels = text::Labels {
ancestor: Some("base".into()),
current: Some("ours".into()),
other: Some("theirs".into()),
};
let resolution = text::function::merge(
&mut output, &mut input, labels, &ours, &base, &theirs, options,
);
assert!(matches!(resolution, gix_merge::blob::Resolution::Conflict));
// ^ This should be Resolution::Complete to match git behavior
Impact
This causes GitButler's commit amend operation to reject valid changes with CherryPickMergeConflict when the 3-way merge (base=workspace tree, ours=target commit tree, theirs=workspace+change) hits this code path. The user is unable to amend their change into the correct commit even though the merge is semantically clean.
Environment
- gix 0.80.0 (from
main branch, commit 1a72380a)
- imara-diff 0.1.8
- macOS (Darwin 25.3.0, aarch64)
Files
I'll attach the three .svelte files (base/ours/theirs) needed to reproduce.
Summary
gix_merge::blob::builtin_driver::text::mergewith the Myers diff algorithm produces a false conflict on certain inputs wheregit merge-file(also Myers-based) resolves cleanly. Switching to Histogram resolves the issue.Reproduction
Given three versions of a file:
The only change in base→theirs is the deletion of a single comment line:
In base→ours, this same comment exists but is now at line 102 (instead of 32) because ~70 lines of new code were inserted above it. The content around it (
let mcpConfigModal,const mcpClaudeConfigQuery) is unchanged.Expected behavior (matches
git merge-file)Clean merge: keep all the new code from ours, apply the comment deletion from theirs.
git merge-fileexits 0 with no conflict markers.git merge-file -p ours.svelte base.svelte theirs.svelte # exit 0, 163 lines, 0 conflict markersActual behavior
gix_merge::blob::builtin_driver::text::mergewithAlgorithm::MyersreportsResolution::Conflict:gix sees the comment deletion as conflicting with the large insertion above it. The conflict region spans lines 78-105 of the merged output.
Key finding: Histogram algorithm works correctly
This suggests the Myers implementation in imara-diff produces different hunk boundaries than git's xdiff for this input, causing the 3-way merge to see overlapping regions where git doesn't.
Minimal reproduction code
Impact
This causes GitButler's commit amend operation to reject valid changes with
CherryPickMergeConflictwhen the 3-way merge (base=workspace tree, ours=target commit tree, theirs=workspace+change) hits this code path. The user is unable to amend their change into the correct commit even though the merge is semantically clean.Environment
mainbranch, commit1a72380a)Files
I'll attach the three
.sveltefiles (base/ours/theirs) needed to reproduce.