diff --git a/skills/kvb-git-commit-next/scripts/stage_hunks.py b/skills/kvb-git-commit-next/scripts/stage_hunks.py index 72d2060..1594135 100755 --- a/skills/kvb-git-commit-next/scripts/stage_hunks.py +++ b/skills/kvb-git-commit-next/scripts/stage_hunks.py @@ -21,6 +21,22 @@ import subprocess import sys from pathlib import Path +HEADER = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+\d+(?:,\d+)? @@(.*)$") + + +def renumber(hunks: list[list[str]]) -> list[list[str]]: + offset = 0 + out = [] + for hunk in hunks: + match = HEADER.match(hunk[0]) + start, count = int(match.group(1)), int(match.group(2) or 1) + added = sum(line.startswith("+") for line in hunk[1:]) + removed = sum(line.startswith("-") for line in hunk[1:]) + new_start = start + offset + (1 if count == 0 else 0) - (1 if added == 0 else 0) + out.append([f"@@ -{start},{count} +{new_start},{added} @@{match.group(3)}", *hunk[1:]]) + offset += added - removed + return out + def fail(message: str) -> None: print(f"stage_hunks: {message}", file=sys.stderr) @@ -78,7 +94,7 @@ def main() -> int: except re.error as error: fail(f"--match: {error}") header, hunks = split(git(repo, "diff", "--no-color", "-U0", "--", args.file)) - kept = select(hunks, pattern) + kept = renumber(select(hunks, pattern)) if not kept: print(f"stage_hunks: no change in {args.file} matches {args.match!r}", file=sys.stderr) return 1 diff --git a/tests/test_stage_hunks.py b/tests/test_stage_hunks.py index de579bf..d52af3b 100644 --- a/tests/test_stage_hunks.py +++ b/tests/test_stage_hunks.py @@ -55,6 +55,16 @@ class TestStageHunks(unittest.TestCase): unstaged = [line for line in self._git("diff", "-U0").splitlines() if line.startswith("+") and not line.startswith("+++")] self.assertEqual(unstaged, ["+beta one", "+beta two"]) + def test_kept_lines_land_where_they_stand_after_dropped_hunks(self): + self.file.write_text( + "one\nbeta one\ntwo\nthree\nalpha three\nbeta three\nfour\nfive\nalpha five\n", encoding="utf-8" + ) + result = self._run("alpha") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual( + self._git("show", ":notes.txt"), "one\ntwo\nthree\nalpha three\nfour\nfive\nalpha five\n" + ) + def test_no_match_exits_1_and_bad_pattern_exits_2(self): self.assertEqual(self._run("gamma").returncode, 1) self.assertEqual(self._run("(").returncode, 2)