From 7aeb0172244673579d1170de89a8cb59977c6bb9 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 20 Oct 2026 09:40:26 +0200 Subject: [PATCH] Ignore "No newline at end of file" markers in patches Git writes "\ No newline at end of file" after a line that has no newline. sebastian/diff parses this marker as an unchanged line, so PatchParser counted it as a line of the new file. Every added line after the marker got a number one too high. The content check then failed with a false "Is the patch up-to-date?" error, and check --patch and patch-coverage stopped. This happens when the old version of a file has no final newline and the change adds lines after its last line or edits its last line. Adding the missing final newline is enough. The check covers every file in the patch, so a .neon, .yml or .json file also triggers it. In a unified diff, only this marker starts with a backslash. PatchParser now removes such lines before it parses the patch. Co-Authored-By: Claude Code --- src/Utils/PatchParser.php | 9 ++++++ tests/Utils/PatchParserTest.php | 31 +++++++++++++++++++ .../_fixtures/PatchParser/NoNewlineAtEof.php | 16 ++++++++++ .../PatchParser/no-newline-at-eof.patch | 16 ++++++++++ 4 files changed, 72 insertions(+) create mode 100644 tests/Utils/PatchParserTest.php create mode 100644 tests/_fixtures/PatchParser/NoNewlineAtEof.php create mode 100644 tests/_fixtures/PatchParser/no-newline-at-eof.patch diff --git a/src/Utils/PatchParser.php b/src/Utils/PatchParser.php index e54a1c3..18d6ecf 100644 --- a/src/Utils/PatchParser.php +++ b/src/Utils/PatchParser.php @@ -3,6 +3,7 @@ namespace ShipMonk\CoverageGuard\Utils; use Composer\InstalledVersions; +use LogicException; use SebastianBergmann\Diff\Line; use SebastianBergmann\Diff\Parser as DiffParser; use ShipMonk\CoverageGuard\Config; @@ -12,6 +13,7 @@ use function file_exists; use function file_get_contents; use function is_file; +use function preg_replace; use function str_ends_with; use function str_starts_with; use function substr; @@ -63,6 +65,13 @@ public function getPatchChangedLines( throw new ErrorException("Failed to read patch file: {$patchFile}"); } + // sebastian/diff parses "\ No newline at end of file" as an unchanged line, which shifts numbers of the following lines + $patchContent = preg_replace('~^\\\\.*\R?~m', '', $patchContent); + + if ($patchContent === null) { + throw new LogicException('Failed to remove no-newline markers from patch file'); + } + $diffs = (new DiffParser())->parse($patchContent); $changes = []; diff --git a/tests/Utils/PatchParserTest.php b/tests/Utils/PatchParserTest.php new file mode 100644 index 0000000..9e997a4 --- /dev/null +++ b/tests/Utils/PatchParserTest.php @@ -0,0 +1,31 @@ +setGitRoot(__DIR__ . '/../../'); + + $patchParser = new PatchParser(__DIR__, new Printer($this->createStream(), noColor: true)); + $changes = $patchParser->getPatchChangedLines(__DIR__ . '/../_fixtures/PatchParser/no-newline-at-eof.patch', $config); + + $changedFile = realpath(__DIR__ . '/../_fixtures/PatchParser/NoNewlineAtEof.php'); + self::assertNotFalse($changedFile); + + self::assertSame([$changedFile => range(11, 16)], $changes); + } + +} diff --git a/tests/_fixtures/PatchParser/NoNewlineAtEof.php b/tests/_fixtures/PatchParser/NoNewlineAtEof.php new file mode 100644 index 0000000..f5afafd --- /dev/null +++ b/tests/_fixtures/PatchParser/NoNewlineAtEof.php @@ -0,0 +1,16 @@ +