From 71cac3221b7733cf84310f722cebd7a015202c45 Mon Sep 17 00:00:00 2001 From: qodelicious <162776175+qodelicious@users.noreply.github.com> Date: Fri, 6 Nov 2026 21:41:42 +0000 Subject: [PATCH 1/2] diff: use the largest context length given, like GNU diff --- src/params.rs | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/params.rs b/src/params.rs index 9e3e5a3..3d1db63 100644 --- a/src/params.rs +++ b/src/params.rs @@ -301,9 +301,7 @@ pub fn parse_params>( }) => { if is_match { format = Some(Format::Context); - if context_count.is_some() { - context = context_count; - } + context = Some(largest_context(context, context_count)); if next_param_consumed { opts.next(); } @@ -320,9 +318,7 @@ pub fn parse_params>( }) => { if is_match { format = Some(Format::Unified); - if context_count.is_some() { - context = context_count; - } + context = Some(largest_context(context, context_count)); if next_param_consumed { opts.next(); } @@ -474,6 +470,13 @@ fn push_function_line_pattern(params: &mut Params, pattern: &str) -> Result<(), Ok(()) } +/// GNU diff uses the largest context length given, and a bare -c or -u +/// counts as a request for 3 lines. +fn largest_context(current: Option, given: Option) -> usize { + let given = given.unwrap_or(3); + current.map_or(given, |current| current.max(given)) +} + struct DiffStyleMatch { is_match: bool, context_count: Option, @@ -1362,4 +1365,28 @@ mod tests { assert_eq!(params.ignore_file_name_case, expected); } } + + #[test] + fn largest_context_count_wins() { + for (args, expected) in [ + (vec!["-U1", "-u"], 3), + (vec!["-u", "-U1"], 3), + (vec!["-U0", "-u"], 3), + (vec!["-U5", "-u"], 5), + (vec!["-U1", "-U2"], 2), + (vec!["-U2", "-U1"], 2), + (vec!["-C1", "-c"], 3), + (vec!["-c", "-C1"], 3), + (vec!["-C", "7", "-C2"], 7), + (vec!["--unified=1", "--unified"], 3), + (vec!["-U1"], 1), + (vec!["-C0"], 0), + ] { + let mut params = vec!["diff"]; + params.extend(&args); + params.extend(["foo", "bar"]); + let parsed = parse_params(params.iter().map(|x| os(x)).peekable()).unwrap(); + assert_eq!(parsed.context_count, expected, "{args:?}"); + } + } } From 59741a16456b7ac7c6acde9c5c542f15468bd925 Mon Sep 17 00:00:00 2001 From: qodelicious <162776175+qodelicious@users.noreply.github.com> Date: Sun, 8 Nov 2026 01:26:40 +0000 Subject: [PATCH 2/2] diff: move the context length tests to test_diff.rs Cover the -U1 -u case from #340, --context=1 --context and the -5u form, with the expected hunks checked against GNU diff 3.10. --- src/params.rs | 24 ---------------------- tests/by-util/test_diff.rs | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/params.rs b/src/params.rs index 3d1db63..9b7fe5e 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1365,28 +1365,4 @@ mod tests { assert_eq!(params.ignore_file_name_case, expected); } } - - #[test] - fn largest_context_count_wins() { - for (args, expected) in [ - (vec!["-U1", "-u"], 3), - (vec!["-u", "-U1"], 3), - (vec!["-U0", "-u"], 3), - (vec!["-U5", "-u"], 5), - (vec!["-U1", "-U2"], 2), - (vec!["-U2", "-U1"], 2), - (vec!["-C1", "-c"], 3), - (vec!["-c", "-C1"], 3), - (vec!["-C", "7", "-C2"], 7), - (vec!["--unified=1", "--unified"], 3), - (vec!["-U1"], 1), - (vec!["-C0"], 0), - ] { - let mut params = vec!["diff"]; - params.extend(&args); - params.extend(["foo", "bar"]); - let parsed = parse_params(params.iter().map(|x| os(x)).peekable()).unwrap(); - assert_eq!(parsed.context_count, expected, "{args:?}"); - } - } } diff --git a/tests/by-util/test_diff.rs b/tests/by-util/test_diff.rs index 10fc0a1..1557438 100644 --- a/tests/by-util/test_diff.rs +++ b/tests/by-util/test_diff.rs @@ -1114,6 +1114,47 @@ fn ed_format_dot_line_ends_text_once() -> Result<(), Box> Ok(()) } +#[test] +fn largest_context_length_wins() -> Result<(), Box> { + let tmp_dir = tempdir()?; + let old = tmp_dir.path().join("old"); + let new = tmp_dir.path().join("new"); + + // The case from issue #340, GNU diff merges both changes into one hunk. + std::fs::write(&old, "a\nb\nc\nd\ne\nf\ng\nh\n")?; + std::fs::write(&new, "a\nB\nc\nd\ne\nf\nG\nh\n")?; + let mut cmd = cargo_bin_cmd!("diffutils"); + cmd.arg("diff").arg("-U1").arg("-u").arg(&old).arg(&new); + cmd.assert() + .code(predicate::eq(1)) + .stdout(predicate::str::contains("\n@@ -1,8 +1,8 @@\n")); + + // With one changed line in the middle, the hunk header shows how many + // lines of context were used. + let lines: String = (1..=20).map(|i| format!("{i}\n")).collect(); + std::fs::write(&old, &lines)?; + std::fs::write(&new, lines.replace("\n10\n", "\nX\n"))?; + for (args, header) in [ + (vec!["-U1", "-u"], "\n@@ -7,7 +7,7 @@\n"), + (vec!["-u", "-U1"], "\n@@ -7,7 +7,7 @@\n"), + (vec!["-U5", "-u"], "\n@@ -5,11 +5,11 @@\n"), + (vec!["-U2", "-U1"], "\n@@ -8,5 +8,5 @@\n"), + (vec!["--unified=1", "--unified"], "\n@@ -7,7 +7,7 @@\n"), + (vec!["-5u"], "\n@@ -5,11 +5,11 @@\n"), + (vec!["-C1", "-c"], "\n*** 7,13 ****\n"), + (vec!["--context=1", "--context"], "\n*** 7,13 ****\n"), + (vec!["--context", "--context=1"], "\n*** 7,13 ****\n"), + (vec!["-5c"], "\n*** 5,15 ****\n"), + ] { + let mut cmd = cargo_bin_cmd!("diffutils"); + cmd.arg("diff").args(&args).arg(&old).arg(&new); + cmd.assert() + .code(predicate::eq(1)) + .stdout(predicate::str::contains(header)); + } + Ok(()) +} + #[test] fn function_lines() -> Result<(), Box> { let tmp_dir = tempdir()?;