From 8ebcba623eb16776b8f241bfdb20e4d46b636134 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Thu, 5 Mar 2026 20:22:59 +0100 Subject: [PATCH] Add support for rewriting hunks if only specific lines match --- bin/mechanicaldiff.py | 81 +++++++++++++++++++++++++++++----------- test/mechanicaldiff.bats | 28 ++++++++++++++ 2 files changed, 87 insertions(+), 22 deletions(-) diff --git a/bin/mechanicaldiff.py b/bin/mechanicaldiff.py index f583fc8..25d00b9 100755 --- a/bin/mechanicaldiff.py +++ b/bin/mechanicaldiff.py @@ -4,9 +4,9 @@ import re import sys -def should_include_change( +def filter_change_block( change_lines: list[str], search: str, replace: str -) -> bool: +) -> tuple[list[str] | None, bool]: removed_lines = [] added_lines = [] phase = "minus" @@ -23,33 +23,66 @@ def should_include_change( added_lines.append(line[1:]) continue raise ValueError("Unexpected non-change line in change block.") + transformed_removed = [ re.sub(search, replace, line) for line in removed_lines ] - return transformed_removed == added_lines + if transformed_removed == added_lines: + return change_lines, False + + if len(removed_lines) != len(added_lines): + return None, True + + kept_lines = [] + for removed, added in zip(removed_lines, added_lines): + if re.sub(search, replace, removed) == added: + kept_lines.append(f"-{removed}") + kept_lines.append(f"+{added}") + else: + kept_lines.append(f" {removed}") + + if not kept_lines: + return None, True + + return kept_lines, True -def should_include_hunk(hunk_text: str, search: str, replace: str) -> bool: - lines = hunk_text.splitlines() - if not lines: - return True +def filter_hunk( + hunk_lines: list[str], search: str, replace: str +) -> tuple[list[str] | None, bool]: + if not hunk_lines: + return None, False - change = [] - for line in lines[1:]: + header = hunk_lines[0] + body = hunk_lines[1:] + output_body: list[str] = [] + left_out = False + + i = 0 + while i < len(body): + line = body[i] if line.startswith(("+", "-")): - change.append(line) + block = [] + while i < len(body) and body[i].startswith(("+", "-")): + block.append(body[i]) + i += 1 + kept_block, block_left_out = filter_change_block( + block, search, replace + ) + if kept_block: + output_body.extend(kept_block) + else: + left_out = True + if block_left_out: + left_out = True continue + output_body.append(line) + i += 1 - if change: - if not should_include_change(change, search, replace): - return False - change = [] + if not any(line.startswith(("+", "-")) for line in output_body): + return None, True - if change: - if not should_include_change(change, search, replace): - return False - - return True + return [header] + output_body, left_out def main() -> None: @@ -97,11 +130,15 @@ def main() -> None: kept_hunks = [] for hunk_lines in section["hunks"]: - hunk_text = "".join(hunk_lines) - if should_include_hunk(hunk_text, search, replace): - kept_hunks.append(hunk_lines) + filtered, hunk_left_out = filter_hunk( + hunk_lines, search, replace + ) + if filtered: + kept_hunks.append(filtered) else: left_out = True + if hunk_left_out: + left_out = True if not kept_hunks: continue diff --git a/test/mechanicaldiff.bats b/test/mechanicaldiff.bats index 1c768ea..13a01b0 100755 --- a/test/mechanicaldiff.bats +++ b/test/mechanicaldiff.bats @@ -128,3 +128,31 @@ setup() { assert_success assert_output "$(cat "$BATS_TEST_TMPDIR/diff")" } + +@test "keeps only matching line in a consecutive change block" { + cat >"$BATS_TEST_TMPDIR/old" <<-'EOF' + foo + bar + baz + EOF + sed \ + -e 's/foo/foo_changed/' \ + -e 's/bar/bar_changed/' \ + "$BATS_TEST_TMPDIR/old" >"$BATS_TEST_TMPDIR/new" + sed 's/foo/foo_changed/' "$BATS_TEST_TMPDIR/old" \ + >"$BATS_TEST_TMPDIR/new_kept" + + git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ + >"$BATS_TEST_TMPDIR/diff_full" || true + git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new_kept" \ + >"$BATS_TEST_TMPDIR/diff_expected" || true + + run mechanicaldiff.py foo foo_changed <"$BATS_TEST_TMPDIR/diff_full" + assert_failure + expected="$(sed \ + -e 's/new_kept/new/g' \ + -e '/^index /d' \ + "$BATS_TEST_TMPDIR/diff_expected")" + output_normalized="$(printf '%s\n' "$output" | sed -e '/^index /d')" + assert_equal "$expected" "$output_normalized" +}