diff --git a/lib/src/conflicts.rs b/lib/src/conflicts.rs index 754b6e74e..022d6fb81 100644 --- a/lib/src/conflicts.rs +++ b/lib/src/conflicts.rs @@ -58,6 +58,15 @@ pub const MIN_CONFLICT_MARKER_LEN: usize = 7; /// existing markers. const CONFLICT_MARKER_LEN_INCREMENT: usize = 4; +/// Comment for missing terminating newline in a term of a conflict. +const NO_EOL_COMMENT: &str = " [noeol]"; + +/// Comment for missing terminating newline in the "add" side of a diff. +const ADD_NO_EOL_COMMENT: &str = " [+noeol]"; + +/// Comment for missing terminating newline in the "remove" side of a diff. +const REMOVE_NO_EOL_COMMENT: &str = " [-noeol]"; + fn write_diff_hunks(hunks: &[DiffHunk], file: &mut dyn Write) -> io::Result<()> { for hunk in hunks { match hunk.kind { @@ -460,7 +469,7 @@ fn materialize_git_style_conflict( output, ConflictMarkerLineChar::ConflictStart, conflict_marker_len, - &format!("Side #1 ({conflict_info})"), + &format!("Side #1{} ({conflict_info})", maybe_no_eol_comment(left)), )?; write_and_ensure_newline(output, left)?; @@ -468,7 +477,7 @@ fn materialize_git_style_conflict( output, ConflictMarkerLineChar::GitAncestor, conflict_marker_len, - "Base", + &format!("Base{}", maybe_no_eol_comment(base)), )?; write_and_ensure_newline(output, base)?; @@ -485,7 +494,10 @@ fn materialize_git_style_conflict( output, ConflictMarkerLineChar::ConflictEnd, conflict_marker_len, - &format!("Side #2 ({conflict_info} ends)"), + &format!( + "Side #2{} ({conflict_info} ends)", + maybe_no_eol_comment(right) + ), )?; Ok(()) @@ -504,7 +516,11 @@ fn materialize_jj_style_conflict( output, ConflictMarkerLineChar::Add, conflict_marker_len, - &format!("Contents of side #{}", add_index + 1), + &format!( + "Contents of side #{}{}", + add_index + 1, + maybe_no_eol_comment(data) + ), )?; write_and_ensure_newline(output, data) }; @@ -515,7 +531,7 @@ fn materialize_jj_style_conflict( output, ConflictMarkerLineChar::Remove, conflict_marker_len, - &format!("Contents of {base_str}"), + &format!("Contents of {base_str}{}", maybe_no_eol_comment(data)), )?; write_and_ensure_newline(output, data) }; @@ -523,11 +539,26 @@ fn materialize_jj_style_conflict( // Write a diff from a negative term to a positive term let write_diff = |base_str: &str, add_index: usize, diff: &[DiffHunk], output: &mut dyn Write| { + let no_eol_remove = diff + .last() + .is_some_and(|diff_hunk| has_no_eol(diff_hunk.contents[0])); + let no_eol_add = diff + .last() + .is_some_and(|diff_hunk| has_no_eol(diff_hunk.contents[1])); + let no_eol_comment = match (no_eol_remove, no_eol_add) { + (true, true) => NO_EOL_COMMENT, + (true, _) => REMOVE_NO_EOL_COMMENT, + (_, true) => ADD_NO_EOL_COMMENT, + _ => "", + }; write_conflict_marker( output, ConflictMarkerLineChar::Diff, conflict_marker_len, - &format!("Changes from {base_str} to side #{}", add_index + 1), + &format!( + "Changes from {base_str} to side #{}{no_eol_comment}", + add_index + 1 + ), )?; write_diff_hunks(diff, output) }; @@ -596,6 +627,14 @@ fn materialize_jj_style_conflict( Ok(()) } +fn maybe_no_eol_comment(slice: &[u8]) -> &'static str { + if has_no_eol(slice) { + NO_EOL_COMMENT + } else { + "" + } +} + // Write a chunk of data, ensuring that it doesn't end with a line which is // missing its terminating newline. fn write_and_ensure_newline(output: &mut dyn Write, data: &[u8]) -> io::Result<()> { diff --git a/lib/tests/test_conflicts.rs b/lib/tests/test_conflicts.rs index 5a7a79dff..473e3a0df 100644 --- a/lib/tests/test_conflicts.rs +++ b/lib/tests/test_conflicts.rs @@ -629,9 +629,9 @@ fn test_materialize_conflict_no_newlines_at_eof() { insta::assert_snapshot!(materialized, @r###" <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + %%%%%%% Changes from base to side #1 [-noeol] -base - +++++++ Contents of side #2 + +++++++ Contents of side #2 [noeol] right >>>>>>> Conflict 1 of 1 ends "### @@ -2057,7 +2057,7 @@ fn test_update_conflict_from_content_no_eol() { +++++++ Contents of side #1 base left - %%%%%%% Changes from base to side #2 + %%%%%%% Changes from base to side #2 [noeol] -base +right >>>>>>> Conflict 2 of 2 ends @@ -2096,9 +2096,9 @@ fn test_update_conflict_from_content_no_eol() { +++++++ Contents of side #1 base left - ------- Contents of base + ------- Contents of base [noeol] base - +++++++ Contents of side #2 + +++++++ Contents of side #2 [noeol] right >>>>>>> Conflict 2 of 2 ends "## @@ -2134,11 +2134,11 @@ fn test_update_conflict_from_content_no_eol() { <<<<<<< Side #1 (Conflict 2 of 2) base left - ||||||| Base + ||||||| Base [noeol] base ======= right - >>>>>>> Side #2 (Conflict 2 of 2 ends) + >>>>>>> Side #2 [noeol] (Conflict 2 of 2 ends) "## ); // Parse with "diff" markers to ensure the file is actually parsed @@ -2197,15 +2197,15 @@ fn test_update_conflict_from_content_no_eol_in_diff_hunk() { <<<<<<< Conflict 1 of 1 +++++++ Contents of side #1 side - %%%%%%% Changes from base #1 to side #2 + %%%%%%% Changes from base #1 to side #2 [-noeol] add newline -line +line - %%%%%%% Changes from base #2 to side #3 + %%%%%%% Changes from base #2 to side #3 [+noeol] remove newline -line +line - %%%%%%% Changes from base #3 to side #4 + %%%%%%% Changes from base #3 to side #4 [noeol] no newline -line 1 +line 2 @@ -2253,7 +2253,7 @@ fn test_update_conflict_from_content_only_no_eol_change() { @r##" line 1 <<<<<<< Conflict 1 of 1 - %%%%%%% Changes from base to side #1 + %%%%%%% Changes from base to side #1 [+noeol] +line 2 +++++++ Contents of side #2 line 2