From 768e2b997d7e6bc9dcdcafb33894e888ebd7c4db Mon Sep 17 00:00:00 2001 From: dawn Date: Sat, 24 Oct 2026 04:55:53 +0300 Subject: [PATCH] gix-imara-diff: count a too-common line in both runs before dropping it like git Signed-off-by: dawn --- .../crates/gitmirror-xrpc/src/format_patch.rs | 9 ++++++ .../gix-imara-diff/src/myers/preprocess.rs | 32 ++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/gitmirror/crates/gitmirror-xrpc/src/format_patch.rs b/gitmirror/crates/gitmirror-xrpc/src/format_patch.rs index 1ae726943..ac4a4cc36 100644 --- a/gitmirror/crates/gitmirror-xrpc/src/format_patch.rs +++ b/gitmirror/crates/gitmirror-xrpc/src/format_patch.rs @@ -1583,6 +1583,15 @@ mod tests { ); } + /// git counts the common line itself in both runs + #[test] + fn a_common_line_needs_more_unmatched_neighbours_to_be_dropped_like_git() { + edit_reads_like_git( + "{ return NULL; }\n\nvoid cleanup_shm (xmmsc_vis_unixshm_t *t) {}\nint read_start_shm (xmmsc_vis_unixshm_t *t, int blocking, xmmsc_vischunk_t **dest) {}\nvoid read_finish_shm (xmmsc_vis_unixshm_t *t, xmmsc_vischunk_t *dest) {}\n", + "\n\n\n\n", + ); + } + #[test] fn similarity_floors_to_a_percent_like_git() { for max in 1..=2000u32 { diff --git a/knot2/third_party/gix-imara-diff/src/myers/preprocess.rs b/knot2/third_party/gix-imara-diff/src/myers/preprocess.rs index 49187bce9..0edbd49eb 100644 --- a/knot2/third_party/gix-imara-diff/src/myers/preprocess.rs +++ b/knot2/third_party/gix-imara-diff/src/myers/preprocess.rs @@ -130,8 +130,9 @@ fn prune_unmatched_tokens( fn should_prune_common_line(token_status: &[Occurrences], pos: usize) -> bool { const WINDOW_SIZE: usize = 100; + // like git's `xdl_clean_mmatch()`, both runs count the line itself as common let mut unmatched_before = 0; - let mut common_before = 0; + let mut common_before = 1; let start = pos.saturating_sub(WINDOW_SIZE); for status in token_status[start..pos].iter().rev() { @@ -150,10 +151,10 @@ fn should_prune_common_line(token_status: &[Occurrences], pos: usize) -> bool { return false; } - let end = token_status.len().min(pos + WINDOW_SIZE); + let end = token_status.len().min(pos + 1 + WINDOW_SIZE); let mut unmatched_after = 0; - let mut common_after = 0; - for status in token_status[pos..end].iter() { + let mut common_after = 1; + for status in token_status[pos + 1..end].iter() { match status { Occurrences::None => { unmatched_after += 1; @@ -204,4 +205,27 @@ mod tests { let (occurrences1, _) = token_occurrences((&whole1, &whole2), &whole1[8..], &whole2[8..]); assert!(matches!(occurrences1[0], Occurrences::Common)); } + + #[test] + fn a_common_line_is_pruned_past_three_unmatched_per_common_counting_itself_in_both_runs() { + let (none, common) = (Occurrences::None, Occurrences::Common); + let status = [none, none, none, common, none, none, none]; + assert!(!should_prune_common_line(&status, 3)); + let status = [none, none, none, common, none, none, none, none]; + assert!(should_prune_common_line(&status, 3)); + } + + #[test] + fn the_run_after_a_common_line_covers_the_next_100_lines() { + let mut status = vec![Occurrences::Some; 240]; + let pos = 100; + status[pos - 35..pos - 32].fill(Occurrences::None); + status[pos - 32..=pos].fill(Occurrences::Common); + status[pos + 1..=pos + 100].fill(Occurrences::None); + assert!(should_prune_common_line(&status, pos)); + + status[pos - 35] = Occurrences::Some; + status[pos + 101] = Occurrences::None; + assert!(!should_prune_common_line(&status, pos)); + } } -- 2.54.0