From d712a7eea2de2ff4cebd5c7f84146e83b0335d02 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Thu, 5 Mar 2026 21:09:06 +0100 Subject: [PATCH 01/10] Just return empty lists instead of None --- bin/mechanicaldiff.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/bin/mechanicaldiff.py b/bin/mechanicaldiff.py index 11fc6c5..03b4804 100755 --- a/bin/mechanicaldiff.py +++ b/bin/mechanicaldiff.py @@ -6,7 +6,7 @@ import sys def filter_change_block( change_lines: list[str], search: str, replace: str -) -> list[str] | None: +) -> list[str]: removed_lines = [] added_lines = [] phase = "minus" @@ -31,7 +31,7 @@ def filter_change_block( return change_lines if len(removed_lines) != len(added_lines): - return None + return [] kept_lines = [] for removed, added in zip(removed_lines, added_lines): @@ -42,16 +42,16 @@ def filter_change_block( kept_lines.append(f" {removed}") if not kept_lines: - return None + return [] return kept_lines def filter_hunk( hunk_lines: list[str], search: str, replace: str -) -> list[str] | None: +) -> list[str]: if not hunk_lines: - return None, False + return [] header = hunk_lines[0] body = hunk_lines[1:] @@ -73,7 +73,7 @@ def filter_hunk( i += 1 if not any(line.startswith(("+", "-")) for line in output_body): - return None + return [] return [header] + output_body @@ -120,12 +120,10 @@ def main() -> None: kept_hunks = [] for hunk_lines in section["hunks"]: filtered = filter_hunk(hunk_lines, search, replace) - if filtered: - if filtered != hunk_lines: - left_out = True - kept_hunks.append(filtered) - elif hunk_lines: + if filtered != hunk_lines: left_out = True + if filtered: + kept_hunks.append(filtered) if not kept_hunks: left_out = True From c103d70078fedf7fc963029d838569e2ce9f4ca6 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Thu, 5 Mar 2026 21:11:29 +0100 Subject: [PATCH 02/10] Add LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..165238e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Sven van Heugten + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 3d814b5b15c185233a7617096ed2ff8a74c5a47e Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Thu, 5 Mar 2026 21:21:35 +0100 Subject: [PATCH 03/10] Get rid of the left_out variable --- bin/mechanicaldiff.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/bin/mechanicaldiff.py b/bin/mechanicaldiff.py index 03b4804..77633de 100755 --- a/bin/mechanicaldiff.py +++ b/bin/mechanicaldiff.py @@ -85,7 +85,8 @@ def main() -> None: search = sys.argv[1] replace = sys.argv[2] - lines = sys.stdin.read().splitlines(keepends=True) + input_data = sys.stdin.read() + lines = input_data.splitlines(keepends=True) preamble_lines = [] sections = [] current = None @@ -114,27 +115,23 @@ def main() -> None: output_lines = [] output_lines.extend(preamble_lines) - left_out = False - for section in sections: kept_hunks = [] for hunk_lines in section["hunks"]: filtered = filter_hunk(hunk_lines, search, replace) - if filtered != hunk_lines: - left_out = True if filtered: kept_hunks.append(filtered) if not kept_hunks: - left_out = True continue output_lines.extend(section["header"]) for hunk_lines in kept_hunks: output_lines.extend(hunk_lines) - sys.stdout.write("".join(output_lines)) - if left_out: + output_data = "".join(output_lines) + sys.stdout.write(output_data) + if output_data != input_data: raise SystemExit(1) From 0b3d0d8f46a0dbed386063c6bf66d19dab7f1047 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Fri, 6 Mar 2026 04:08:21 +0100 Subject: [PATCH 04/10] Ignore leading and trailing blank lines for comparison ```git-check-assertions run test/mechanicaldiff.bats assert_success git checkout HEAD~ bin run test/mechanicaldiff.bats assert_failure assert_output --partial "-- command failed --" ``` --- bin/mechanicaldiff.py | 12 +++++++++++- test/mechanicaldiff.bats | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/bin/mechanicaldiff.py b/bin/mechanicaldiff.py index 77633de..32d3b96 100755 --- a/bin/mechanicaldiff.py +++ b/bin/mechanicaldiff.py @@ -4,6 +4,16 @@ import re import sys +def trim_blank_ends(lines: list[str]) -> list[str]: + start = 0 + end = len(lines) + while start < end and lines[start] == "\n": + start += 1 + while end > start and lines[end - 1] == "\n": + end -= 1 + return lines[start:end] + + def filter_change_block( change_lines: list[str], search: str, replace: str ) -> list[str]: @@ -27,7 +37,7 @@ def filter_change_block( transformed_removed = [ re.sub(search, replace, line) for line in removed_lines ] - if transformed_removed == added_lines: + if trim_blank_ends(transformed_removed) == trim_blank_ends(added_lines): return change_lines if len(removed_lines) != len(added_lines): diff --git a/test/mechanicaldiff.bats b/test/mechanicaldiff.bats index beb4853..d5c2748 100755 --- a/test/mechanicaldiff.bats +++ b/test/mechanicaldiff.bats @@ -143,6 +143,24 @@ setup() { assert_output "$(cat "$BATS_TEST_TMPDIR/diff")" } +@test "ignores leading and trailing blank lines for comparison" { + cat >"$BATS_TEST_TMPDIR/old" <<-'EOF' + + prefix foo suffix + + EOF + cat >"$BATS_TEST_TMPDIR/new" <<-'EOF' + prefix bar suffix + EOF + + git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ + >"$BATS_TEST_TMPDIR/diff" || true + + run mechanicaldiff.py foo bar <"$BATS_TEST_TMPDIR/diff" + 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 From 2aebae7aff41a68c514561009c7df29e176b8571 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Fri, 6 Mar 2026 04:24:20 +0100 Subject: [PATCH 05/10] Add test where replacement removes entire line ```git-check-assertions run test/mechanicaldiff.bats assert_success git checkout HEAD~2 bin run test/mechanicaldiff.bats assert_failure assert_output --partial "-- command failed --" ``` --- test/mechanicaldiff.bats | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/mechanicaldiff.bats b/test/mechanicaldiff.bats index d5c2748..acefc62 100755 --- a/test/mechanicaldiff.bats +++ b/test/mechanicaldiff.bats @@ -143,6 +143,25 @@ setup() { assert_output "$(cat "$BATS_TEST_TMPDIR/diff")" } +@test "matches when replacement removes entire line" { + cat >"$BATS_TEST_TMPDIR/old" <<-'EOF' + alpha + foo + omega + EOF + cat >"$BATS_TEST_TMPDIR/new" <<-'EOF' + alpha + omega + EOF + + git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ + >"$BATS_TEST_TMPDIR/diff" || true + + run mechanicaldiff.py foo "" <"$BATS_TEST_TMPDIR/diff" + assert_success + assert_output "$(cat "$BATS_TEST_TMPDIR/diff")" +} + @test "ignores leading and trailing blank lines for comparison" { cat >"$BATS_TEST_TMPDIR/old" <<-'EOF' From 3dd0d626c65fd81f2697389e75efef7aad2aa088 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Fri, 6 Mar 2026 04:29:23 +0100 Subject: [PATCH 06/10] Add README --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cbcbe4 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# mechanicaldiff + +`mechanicaldiff` lets you filter down a diff to _only_ the parts that make a specific mechanical change, so you can commit those changes separately. + +Usage: `git diff | mechanicaldiff ` + +It differs from [grepdiff](https://linux.die.net/man/1/grepdiff) in that it doesn’t just search for the lines that contain the given `pattern`, but also checks that the _only_ change in that line is that `pattern` was replaced with the given `replacement`. + +It differs from simply replacing `pattern` with `replacement` in the entire repository in that it filters down an _existing_ diff, which means that the resulting diff doesn’t make the mechanical change everywhere, but only in the lines that your original diff also made it. From c55e0276a6f016a29d2b136b46b7b5d3e552173d Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Fri, 6 Mar 2026 05:28:19 +0100 Subject: [PATCH 07/10] mechanicaldiff.py -> mechanicaldiff --- bin/{mechanicaldiff.py => mechanicaldiff} | 0 default.nix | 4 ++-- test/mechanicaldiff.bats | 26 +++++++++++------------ 3 files changed, 15 insertions(+), 15 deletions(-) rename bin/{mechanicaldiff.py => mechanicaldiff} (100%) diff --git a/bin/mechanicaldiff.py b/bin/mechanicaldiff similarity index 100% rename from bin/mechanicaldiff.py rename to bin/mechanicaldiff diff --git a/default.nix b/default.nix index 79ed058..6f0526a 100644 --- a/default.nix +++ b/default.nix @@ -46,7 +46,7 @@ stdenv.mkDerivation { checkPhase = '' runHook preCheck - flake8 bin/mechanicaldiff.py + flake8 bin/mechanicaldiff shellcheck test/mechanicaldiff.bats shfmt -d test/mechanicaldiff.bats bats test @@ -55,7 +55,7 @@ stdenv.mkDerivation { installPhase = '' mkdir -p $out/bin - cp $src/bin/mechanicaldiff.py $out/bin/mechanicaldiff + cp $src/bin/mechanicaldiff $out/bin/mechanicaldiff chmod +x $out/bin/mechanicaldiff ''; } diff --git a/test/mechanicaldiff.bats b/test/mechanicaldiff.bats index acefc62..ea2c5f9 100755 --- a/test/mechanicaldiff.bats +++ b/test/mechanicaldiff.bats @@ -15,7 +15,7 @@ setup() { printf "%s\n" "bar" >"$BATS_TEST_TMPDIR/new" diff_output="$(git diff --no-index "$BATS_TEST_TMPDIR/old" \ "$BATS_TEST_TMPDIR/new" || true)" - run mechanicaldiff.py foo bar <<<"$diff_output" + run mechanicaldiff foo bar <<<"$diff_output" assert_success assert_output "$diff_output" } @@ -25,7 +25,7 @@ setup() { printf "%s\n" "baz" >"$BATS_TEST_TMPDIR/new" git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ >"$BATS_TEST_TMPDIR/diff" || true - run mechanicaldiff.py foo bar <"$BATS_TEST_TMPDIR/diff" + run mechanicaldiff foo bar <"$BATS_TEST_TMPDIR/diff" assert_failure assert_output "" } @@ -39,7 +39,7 @@ setup() { diff_output="$(git diff --no-index "$BATS_TEST_TMPDIR/old" \ "$BATS_TEST_TMPDIR/new" || true)" - run mechanicaldiff.py foo bar <<<"$diff_output" + run mechanicaldiff foo bar <<<"$diff_output" assert_failure assert_output "" } @@ -75,7 +75,7 @@ setup() { "$BATS_TEST_TMPDIR/new_kept" \ >"$BATS_TEST_TMPDIR/diff_expected" || true - run mechanicaldiff.py "beta" "beta_changed" <"$BATS_TEST_TMPDIR/diff_full" + run mechanicaldiff "beta" "beta_changed" <"$BATS_TEST_TMPDIR/diff_full" assert_failure assert_output --partial "-beta" assert_output --partial "+beta_changed" @@ -96,15 +96,15 @@ setup() { cat "$BATS_TEST_TMPDIR/diff_one" "$BATS_TEST_TMPDIR/diff_two" \ >"$BATS_TEST_TMPDIR/diff_all" - run mechanicaldiff.py foo bar <"$BATS_TEST_TMPDIR/diff_one" + run mechanicaldiff foo bar <"$BATS_TEST_TMPDIR/diff_one" assert_success output_one="$output" - run mechanicaldiff.py foo bar <"$BATS_TEST_TMPDIR/diff_two" + run mechanicaldiff foo bar <"$BATS_TEST_TMPDIR/diff_two" assert_failure output_two="$output" - run mechanicaldiff.py foo bar <"$BATS_TEST_TMPDIR/diff_all" + run mechanicaldiff foo bar <"$BATS_TEST_TMPDIR/diff_all" assert_failure assert_output "${output_one}${output_two}" } @@ -122,7 +122,7 @@ setup() { git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ >"$BATS_TEST_TMPDIR/diff" || true - run mechanicaldiff.py 'foo\((\d+)\)' 'foo-\1' <"$BATS_TEST_TMPDIR/diff" + run mechanicaldiff 'foo\((\d+)\)' 'foo-\1' <"$BATS_TEST_TMPDIR/diff" assert_success assert_output "$(cat "$BATS_TEST_TMPDIR/diff")" } @@ -138,7 +138,7 @@ setup() { git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ >"$BATS_TEST_TMPDIR/diff" || true - run mechanicaldiff.py foo bar <"$BATS_TEST_TMPDIR/diff" + run mechanicaldiff foo bar <"$BATS_TEST_TMPDIR/diff" assert_success assert_output "$(cat "$BATS_TEST_TMPDIR/diff")" } @@ -157,7 +157,7 @@ setup() { git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ >"$BATS_TEST_TMPDIR/diff" || true - run mechanicaldiff.py foo "" <"$BATS_TEST_TMPDIR/diff" + run mechanicaldiff foo "" <"$BATS_TEST_TMPDIR/diff" assert_success assert_output "$(cat "$BATS_TEST_TMPDIR/diff")" } @@ -175,7 +175,7 @@ setup() { git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ >"$BATS_TEST_TMPDIR/diff" || true - run mechanicaldiff.py foo bar <"$BATS_TEST_TMPDIR/diff" + run mechanicaldiff foo bar <"$BATS_TEST_TMPDIR/diff" assert_success assert_output "$(cat "$BATS_TEST_TMPDIR/diff")" } @@ -198,7 +198,7 @@ setup() { 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" + run mechanicaldiff foo foo_changed <"$BATS_TEST_TMPDIR/diff_full" assert_failure expected="$(sed \ -e 's/new_kept/new/g' \ @@ -222,7 +222,7 @@ setup() { git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ >"$BATS_TEST_TMPDIR/diff_full" || true - run mechanicaldiff.py qux quux <"$BATS_TEST_TMPDIR/diff_full" + run mechanicaldiff qux quux <"$BATS_TEST_TMPDIR/diff_full" assert_failure assert_output "" } From 057686a6b97b9406876f3af60702aa5688bba8a0 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Fri, 6 Mar 2026 17:01:00 +0100 Subject: [PATCH 08/10] Fix changes sometimes disappearing entirely ```git-check-assertions run test/mechanicaldiff.bats assert_success git checkout HEAD~ bin run test/mechanicaldiff.bats assert_failure assert_output --partial "not ok 9 keeps matching change with an extra removed line" assert_output --partial "not ok 10 keeps matching change with an extra added line" ``` --- bin/mechanicaldiff | 10 ++++--- default.nix | 2 ++ flake.nix | 1 + test/mechanicaldiff.bats | 65 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/bin/mechanicaldiff b/bin/mechanicaldiff index 32d3b96..8ac824d 100755 --- a/bin/mechanicaldiff +++ b/bin/mechanicaldiff @@ -40,11 +40,13 @@ def filter_change_block( if trim_blank_ends(transformed_removed) == trim_blank_ends(added_lines): return change_lines - if len(removed_lines) != len(added_lines): - return [] - kept_lines = [] - for removed, added in zip(removed_lines, added_lines): + max_len = max(len(removed_lines), len(added_lines)) + for idx in range(max_len): + removed = removed_lines[idx] if idx < len(removed_lines) else None + added = added_lines[idx] if idx < len(added_lines) else None + if removed is None: + break if re.sub(search, replace, removed) == added: kept_lines.append(f"-{removed}") kept_lines.append(f"+{added}") diff --git a/default.nix b/default.nix index 6f0526a..54cf645 100644 --- a/default.nix +++ b/default.nix @@ -7,6 +7,7 @@ python3Packages, shellcheck-minimal, shfmt, + patchutils_0_4_2, }: let @@ -42,6 +43,7 @@ stdenv.mkDerivation { python3Packages.flake8 shellcheck-minimal shfmt + patchutils_0_4_2 ]; checkPhase = '' diff --git a/flake.nix b/flake.nix index 0593261..387b65c 100644 --- a/flake.nix +++ b/flake.nix @@ -26,6 +26,7 @@ pkgs.python3Packages.flake8 pkgs.shellcheck pkgs.shfmt + pkgs.patchutils_0_4_2 ]; }; } diff --git a/test/mechanicaldiff.bats b/test/mechanicaldiff.bats index ea2c5f9..0f6358c 100755 --- a/test/mechanicaldiff.bats +++ b/test/mechanicaldiff.bats @@ -162,6 +162,71 @@ setup() { assert_output "$(cat "$BATS_TEST_TMPDIR/diff")" } +@test "keeps matching change with an extra removed line" { + cat >"$BATS_TEST_TMPDIR/old" <<-'EOF' + foo + bar + baz + EOF + cat >"$BATS_TEST_TMPDIR/new" <<-'EOF' + foo_changed + baz + EOF + + git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ + >"$BATS_TEST_TMPDIR/diff" || true + + cat >"$BATS_TEST_TMPDIR/new_kept" <<-'EOF' + foo_changed + bar + baz + EOF + git diff --no-index "$BATS_TEST_TMPDIR/old" \ + "$BATS_TEST_TMPDIR/new_kept" \ + >"$BATS_TEST_TMPDIR/diff_expected" || true + + run mechanicaldiff foo foo_changed <"$BATS_TEST_TMPDIR/diff" + assert_failure + expected="$(sed \ + -e 's/new_kept/new/g' \ + -e '/^index /d' \ + "$BATS_TEST_TMPDIR/diff_expected")" + output_normalized="$(printf '%s\n' "$output" | recountdiff | sed -e '/^index /d')" + assert_equal "$expected" "$output_normalized" +} + +@test "keeps matching change with an extra added line" { + cat >"$BATS_TEST_TMPDIR/old" <<-'EOF' + foo + baz + EOF + cat >"$BATS_TEST_TMPDIR/new" <<-'EOF' + foo_changed + bar + baz + EOF + + git diff --no-index "$BATS_TEST_TMPDIR/old" "$BATS_TEST_TMPDIR/new" \ + >"$BATS_TEST_TMPDIR/diff" || true + + cat >"$BATS_TEST_TMPDIR/new_kept" <<-'EOF' + foo_changed + baz + EOF + git diff --no-index "$BATS_TEST_TMPDIR/old" \ + "$BATS_TEST_TMPDIR/new_kept" \ + >"$BATS_TEST_TMPDIR/diff_expected" || true + + run mechanicaldiff foo foo_changed <"$BATS_TEST_TMPDIR/diff" + assert_failure + expected="$(sed \ + -e 's/new_kept/new/g' \ + -e '/^index /d' \ + "$BATS_TEST_TMPDIR/diff_expected")" + output_normalized="$(printf '%s\n' "$output" | recountdiff | sed -e '/^index /d')" + assert_equal "$expected" "$output_normalized" +} + @test "ignores leading and trailing blank lines for comparison" { cat >"$BATS_TEST_TMPDIR/old" <<-'EOF' From 05fe8f56dfe0877a647a34e535cf3353ddc6ab80 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Fri, 6 Mar 2026 17:26:19 +0100 Subject: [PATCH 09/10] Fix order of arguments to assert_equal --- test/mechanicaldiff.bats | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/mechanicaldiff.bats b/test/mechanicaldiff.bats index 0f6358c..82c7ea1 100755 --- a/test/mechanicaldiff.bats +++ b/test/mechanicaldiff.bats @@ -192,7 +192,7 @@ setup() { -e '/^index /d' \ "$BATS_TEST_TMPDIR/diff_expected")" output_normalized="$(printf '%s\n' "$output" | recountdiff | sed -e '/^index /d')" - assert_equal "$expected" "$output_normalized" + assert_equal "$output_normalized" "$expected" } @test "keeps matching change with an extra added line" { @@ -224,7 +224,7 @@ setup() { -e '/^index /d' \ "$BATS_TEST_TMPDIR/diff_expected")" output_normalized="$(printf '%s\n' "$output" | recountdiff | sed -e '/^index /d')" - assert_equal "$expected" "$output_normalized" + assert_equal "$output_normalized" "$expected" } @test "ignores leading and trailing blank lines for comparison" { @@ -270,7 +270,7 @@ setup() { -e '/^index /d' \ "$BATS_TEST_TMPDIR/diff_expected")" output_normalized="$(printf '%s\n' "$output" | sed -e '/^index /d')" - assert_equal "$expected" "$output_normalized" + assert_equal "$output_normalized" "$expected" } @test "drops hunk when all pairs become context" { From 5a05a067acba7d6b5696bf6e29fc26c6d7e8b833 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Fri, 6 Mar 2026 17:31:27 +0100 Subject: [PATCH 10/10] Document the need to use recountdiff --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3cbcbe4..012eeb9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ `mechanicaldiff` lets you filter down a diff to _only_ the parts that make a specific mechanical change, so you can commit those changes separately. -Usage: `git diff | mechanicaldiff ` +Usage: `git diff | mechanicaldiff | recountdiff` It differs from [grepdiff](https://linux.die.net/man/1/grepdiff) in that it doesn’t just search for the lines that contain the given `pattern`, but also checks that the _only_ change in that line is that `pattern` was replaced with the given `replacement`.