-
-
Notifications
You must be signed in to change notification settings - Fork 440
Description
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 markersActual 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 behaviorImpact
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
mainbranch, commit1a72380a) - 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.