From a7a8659383760ab76bc7c6f4a31c9e8fa1dafde5 Mon Sep 17 00:00:00 2001 From: Jochen Hunz Date: Sat, 18 Jul 2026 12:43:09 +0200 Subject: [PATCH] fix(merge): keep conflict markers on their own lines A conflicting hunk at end-of-file without a trailing newline glued the next marker onto its last content line, producing unparseable output. Matches git merge-file --diff3 behavior. Signed-off-by: Jochen Hunz --- src/merge/mod.rs | 6 ++++++ src/merge/tests.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/merge/mod.rs b/src/merge/mod.rs index 21f9197..320d834 100644 --- a/src/merge/mod.rs +++ b/src/merge/mod.rs @@ -604,6 +604,9 @@ fn add_conflict_marker( marker_len: usize, filename: Option<&str>, ) { + if !output.is_empty() && !output.ends_with('\n') { + output.push('\n'); + } for _ in 0..marker_len { output.push(marker); } @@ -684,6 +687,9 @@ fn add_conflict_marker_bytes( marker_len: usize, filename: Option<&[u8]>, ) { + if !output.is_empty() && output.last() != Some(&b'\n') { + output.push(b'\n'); + } for _ in 0..marker_len { output.push(marker); } diff --git a/src/merge/tests.rs b/src/merge/tests.rs index 99188df..7a3a8c6 100644 --- a/src/merge/tests.rs +++ b/src/merge/tests.rs @@ -404,3 +404,29 @@ fn delete_and_insert_conflict() { "MergeRange (Theirs::delete, Ours::insert) conflict" ); } + +#[test] +fn conflict_hunks_without_trailing_newline_keep_markers_on_own_lines() { + let base = "This is line 1.\nThis is line 2."; + let ours = "This is line 1.\nThis is line 2 changed."; + let theirs = "This is line 1.\nThis is line 2 also changed."; + + let expected = "\ +This is line 1. +<<<<<<< ours +This is line 2 changed. +||||||| original +This is line 2. +======= +This is line 2 also changed. +>>>>>>> theirs +"; + + assert_merge!( + base, + ours, + theirs, + Err(expected), + "file-final hunks without trailing newline", + ); +}