diff --git a/gix-merge/src/blob/builtin_driver/text/function.rs b/gix-merge/src/blob/builtin_driver/text/function.rs index d657e3797f5..f37431b4c3b 100644 --- a/gix-merge/src/blob/builtin_driver/text/function.rs +++ b/gix-merge/src/blob/builtin_driver/text/function.rs @@ -50,7 +50,7 @@ pub fn merge<'a>( input, CollectHunks { side: Side::Current, - hunks: Default::default(), + hunks: Vec::new(), }, ); @@ -115,65 +115,60 @@ pub fn merge<'a>( let last_hunk = last_hunk(front_hunks, our_hunks, their_hunks, back_hunks); write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out); write_hunks(front_hunks, input, ¤t_tokens, out); - if their_hunks.is_empty() { - write_hunks(our_hunks, input, ¤t_tokens, out); - } else if our_hunks.is_empty() { - write_hunks(their_hunks, input, ¤t_tokens, out); - } else { - // DEVIATION: this makes tests (mostly) pass, but probably is very different from what Git does. - let hunk_storage; - let nl = detect_line_ending( - if front_hunks.is_empty() { - hunk_storage = Hunk { - before: ancestor_integrated_until..first_hunk.before.start, - after: Default::default(), - side: Side::Ancestor, - }; - std::slice::from_ref(&hunk_storage) - } else { - front_hunks - }, - input, - ¤t_tokens, - ) - .or_else(|| detect_line_ending(our_hunks, input, ¤t_tokens)) - .unwrap_or(b"\n".into()); - match style { - ConflictStyle::Merge => { - if contains_lines(our_hunks) || contains_lines(their_hunks) { + // DEVIATION: this makes tests (mostly) pass, but probably is very different from what Git does. + let hunk_storage; + let nl = detect_line_ending( + if front_hunks.is_empty() { + hunk_storage = Hunk { + before: ancestor_integrated_until..first_hunk.before.start, + after: Default::default(), + side: Side::Ancestor, + }; + std::slice::from_ref(&hunk_storage) + } else { + front_hunks + }, + input, + ¤t_tokens, + ) + .or_else(|| detect_line_ending(our_hunks, input, ¤t_tokens)) + .unwrap_or(b"\n".into()); + match style { + ConflictStyle::Merge => { + if contains_lines(our_hunks) || contains_lines(their_hunks) { + resolution = Resolution::Conflict; + write_conflict_marker(out, b'<', current_label, marker_size, nl); + write_hunks(our_hunks, input, ¤t_tokens, out); + write_conflict_marker(out, b'=', None, marker_size, nl); + write_hunks(their_hunks, input, ¤t_tokens, out); + write_conflict_marker(out, b'>', other_label, marker_size, nl); + } + } + ConflictStyle::Diff3 | ConflictStyle::ZealousDiff3 => { + if contains_lines(our_hunks) || contains_lines(their_hunks) { + if hunks_differ_in_diff3(style, our_hunks, their_hunks, input, ¤t_tokens) { resolution = Resolution::Conflict; write_conflict_marker(out, b'<', current_label, marker_size, nl); write_hunks(our_hunks, input, ¤t_tokens, out); + let ancestor_hunk = Hunk { + before: first_hunk.before.start..last_hunk.before.end, + after: Default::default(), + side: Side::Ancestor, + }; + let ancestor_hunk = std::slice::from_ref(&ancestor_hunk); + let ancestor_nl = detect_line_ending_or_nl(ancestor_hunk, input, ¤t_tokens); + write_conflict_marker(out, b'|', ancestor_label, marker_size, ancestor_nl); + write_hunks(ancestor_hunk, input, ¤t_tokens, out); write_conflict_marker(out, b'=', None, marker_size, nl); write_hunks(their_hunks, input, ¤t_tokens, out); write_conflict_marker(out, b'>', other_label, marker_size, nl); - } - } - ConflictStyle::Diff3 | ConflictStyle::ZealousDiff3 => { - if contains_lines(our_hunks) || contains_lines(their_hunks) { - if hunks_differ_in_diff3(style, our_hunks, their_hunks, input, ¤t_tokens) { - resolution = Resolution::Conflict; - write_conflict_marker(out, b'<', current_label, marker_size, nl); - write_hunks(our_hunks, input, ¤t_tokens, out); - let ancestor_hunk = Hunk { - before: first_hunk.before.start..last_hunk.before.end, - after: Default::default(), - side: Side::Ancestor, - }; - let ancestor_hunk = std::slice::from_ref(&ancestor_hunk); - let ancestor_nl = detect_line_ending_or_nl(ancestor_hunk, input, ¤t_tokens); - write_conflict_marker(out, b'|', ancestor_label, marker_size, ancestor_nl); - write_hunks(ancestor_hunk, input, ¤t_tokens, out); - write_conflict_marker(out, b'=', None, marker_size, nl); - write_hunks(their_hunks, input, ¤t_tokens, out); - write_conflict_marker(out, b'>', other_label, marker_size, nl); - } else { - write_hunks(our_hunks, input, ¤t_tokens, out); - } + } else { + write_hunks(our_hunks, input, ¤t_tokens, out); } } } } + write_hunks(back_hunks, input, ¤t_tokens, out); ancestor_integrated_until = last_hunk.before.end; } diff --git a/gix-merge/src/blob/builtin_driver/text/utils.rs b/gix-merge/src/blob/builtin_driver/text/utils.rs index 54a6f23a35f..0b1aa7e1dfa 100644 --- a/gix-merge/src/blob/builtin_driver/text/utils.rs +++ b/gix-merge/src/blob/builtin_driver/text/utils.rs @@ -163,10 +163,9 @@ fn ancestor_hunk(start: u32, num_lines: u32) -> Hunk { /// actually different remain. Note that we have to compare the resolved values, not only the tokens, /// so `current_tokens` is expected to be known to the `input` (and its `interner`). /// Hunks from all input arrays maybe removed in the process from the front and back, in case they -/// are entirely equal to what's in `hunk`. Note also that `a_hunks` and `b_hunks` are treated to be consecutive, -/// so [`fill_ancestor()`] must have been called beforehand, and are assumed to covert the same space in the -/// ancestor buffer. -/// Use `mode` to determine how hunks may be handled. +/// are entirely equal to each other. +/// Note also that `a_hunks` and `b_hunks` are treated to be consecutive, so [`fill_ancestor()`] must +/// have been called beforehand, and are assumed to cover the same space in the ancestor buffer. /// /// Return a new vector of all the hunks that were removed from front and back, with partial hunks inserted, /// along with the amount of hunks that go front, with the remaining going towards the back. @@ -418,7 +417,7 @@ fn write_tokens( /// Find all hunks in `iter` which aren't from the same side as `hunk` and intersect with it. /// Also put `hunk` into `input` so it's the first item, and possibly put more hunks of the side of `hunk` so /// `iter` doesn't have any overlapping hunks left. -/// Return `true` if `intersecting` is non-empty after the operation, indicating overlapping hunks were found. +/// Return `Some` if `intersecting` is non-empty after the operation, indicating overlapping hunks were found. pub fn take_intersecting( iter: &mut Peekable>, input: &mut Vec, diff --git a/gix-merge/tests/fixtures/generated-archives/text-baseline.tar b/gix-merge/tests/fixtures/generated-archives/text-baseline.tar index c0601e39ede..f018b81162f 100644 Binary files a/gix-merge/tests/fixtures/generated-archives/text-baseline.tar and b/gix-merge/tests/fixtures/generated-archives/text-baseline.tar differ diff --git a/gix-merge/tests/fixtures/generated-archives/tree-baseline.tar b/gix-merge/tests/fixtures/generated-archives/tree-baseline.tar index 98895fade8f..76c18ba6fdf 100644 Binary files a/gix-merge/tests/fixtures/generated-archives/tree-baseline.tar and b/gix-merge/tests/fixtures/generated-archives/tree-baseline.tar differ diff --git a/gix-merge/tests/fixtures/text-baseline.sh b/gix-merge/tests/fixtures/text-baseline.sh index a943d92c3d3..e6ece7585ce 100755 --- a/gix-merge/tests/fixtures/text-baseline.sh +++ b/gix-merge/tests/fixtures/text-baseline.sh @@ -19,6 +19,10 @@ function baseline() { echo "$theirs" "$base" "$ours" "${output}" "$@" >> baseline-reversed.cases } +function write_lines () { + printf "%s\n" "$@" +} + mkdir simple (cd simple echo -e "line1-changed-by-both\nline2-to-be-changed-in-incoming" > ours.blob @@ -396,6 +400,27 @@ mkdir no-change-remove cp ours.blob theirs.blob ) +mkdir simple-conflict +(cd simple-conflict + touch base.blob + write_lines a c >ours.blob + write_lines a b c >theirs.blob +) + +mkdir simple-conflict-2 +(cd simple-conflict-2 + touch base.blob + write_lines a b c d >ours.blob + write_lines a b c >theirs.blob +) + +mkdir simple-conflict-3 +(cd simple-conflict-3 + touch base.blob + write_lines b c >ours.blob + write_lines a b c >theirs.blob +) + mkdir complex (cd complex cat <base.blob @@ -627,7 +652,10 @@ mkdir line-ending-change ) -for dir in simple \ +for dir in simple-conflict-3 \ + simple-conflict-2 \ + simple-conflict \ + simple \ multi-change \ clear-ours \ clear-theirs \ diff --git a/gix-merge/tests/fixtures/tree-baseline.sh b/gix-merge/tests/fixtures/tree-baseline.sh index cc3a8ed2471..595598c725a 100755 --- a/gix-merge/tests/fixtures/tree-baseline.sh +++ b/gix-merge/tests/fixtures/tree-baseline.sh @@ -596,13 +596,40 @@ git init rename-within-rename-2 git checkout expected write_lines 1 2 3 4 5 6 >a/x.f - write_lines 1 2 3 4 5 6 >a/sub/y.f +# write_lines 1 2 3 4 5 6 >a/sub/y.f + echo -ne "1\n2\n3\n4\n5\n<<<<<<< A\n=======\n6\n>>>>>>> B\n" >a/sub/y.f git mv a/sub a/sub-renamed git mv a a-renamed git commit -am "tracked both renames, applied all modifications by merge" - # This means there are no conflicts actually. + # In reverse, it finds a rewrite/rewrite case which gives a base to the merge, hence it passes. + # It should of course be reversible… . + git checkout -b expected-reversed B + write_lines 1 2 3 4 5 6 >a/x.f + write_lines 1 2 3 4 5 6 >a/sub-renamed/y.f + git mv a a-renamed + git commit -am "need to hack this unfortunately" + + + rm .git/index + git update-index --index-info < crate::Result { let root = gix_testtools::scripted_fixture_read_only("text-baseline.sh")?; for (baseline, diverging, expected_percentage) in [ - ("baseline-reversed.cases", DIVERGING_REVERSED, 11), - ("baseline.cases", DIVERGING, 11), + ("baseline.cases", DIVERGING, 10), + ("baseline-reversed.cases", DIVERGING_REVERSED, 10), ] { let cases = std::fs::read_to_string(root.join(baseline))?; let mut out = Vec::new(); @@ -392,7 +392,7 @@ mod text { fn read_blob(root: &Path, rela_path: &str) -> BString { std::fs::read(root.join(rela_path)) - .unwrap_or_else(|_| panic!("Failed to read '{rela_path}' in '{}'", root.display())) + .unwrap_or_else(|err| panic!("Failed to read '{rela_path}' in '{}': {err}", root.display())) .into() } } diff --git a/gix-merge/tests/merge/tree/baseline.rs b/gix-merge/tests/merge/tree/baseline.rs index 7401c052e7c..df5c7ce650c 100644 --- a/gix-merge/tests/merge/tree/baseline.rs +++ b/gix-merge/tests/merge/tree/baseline.rs @@ -300,7 +300,7 @@ pub struct DebugIndexEntry<'a> { stage: gix_index::entry::Stage, } -pub fn clear_entries(state: &gix_index::State) -> Vec> { +pub fn debug_entries(state: &gix_index::State) -> Vec> { state .entries() .iter() diff --git a/gix-merge/tests/merge/tree/mod.rs b/gix-merge/tests/merge/tree/mod.rs index 5c66142b48f..0a328bc0f23 100644 --- a/gix-merge/tests/merge/tree/mod.rs +++ b/gix-merge/tests/merge/tree/mod.rs @@ -26,7 +26,7 @@ fn run_baseline() -> crate::Result { let cases = std::fs::read_to_string(root.join("baseline.cases"))?; let mut actual_cases = 0; let mut skipped_tree_resolve_cases = 0; - // let new_test = Some("tree-to-non-tree-with-rename-A-B"); + // let new_test = Some("rename-within-rename-2-A-B-deviates"); let new_test = None; for baseline::Expectation { root, @@ -122,9 +122,9 @@ fn run_baseline() -> crate::Result { actual.index_changed_after_applying_conflicts(&mut actual_index, conflicts_like_in_git, RemovalMode::Prune); pretty_assertions::assert_eq!( - baseline::clear_entries(&actual_index), - baseline::clear_entries(&expected_index), - "{case_name}: index mismatch\n{:#?}\n{:#?}", + baseline::debug_entries(&actual_index), + baseline::debug_entries(&expected_index), + "{case_name}: index mismatch\nOur conflicts {:#?}\nGit conflicts {:#?}", actual.conflicts, merge_info.conflicts );