From 826966f7ebe3ac85b0f5c011500a3f77bd65123f Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Tue, 4 Aug 2026 22:48:27 -0300 Subject: [PATCH] Handle empty (incl. DOS) context lines in metadata_only parsing --- tests/test_parser.py | 15 +++++++++++++++ unidiff/patch.py | 8 ++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index 9571ed0..b040cbe 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -99,6 +99,21 @@ def test_preserve_dos_line_endings_empty_line_type(self): self.assertEqual(modified_unicode_line.value, '\n') self.assertEqual(modified_unicode_line.line_type, ' ') + def test_metadata_only_with_empty_lines(self): + # regression test: the metadata_only fast path must treat a bare + # newline (including a DOS "\r\n") as an empty context line, matching + # the full parser. Previously it raised on such lines (sample5 has + # empty "\r\n" / "\n" context lines). + utf8_file = os.path.join(self.samples_dir, 'samples/sample5.diff') + with open(utf8_file, 'rb') as diff_file: + full = PatchSet(diff_file, encoding='utf-8') + with open(utf8_file, 'rb') as diff_file: + meta = PatchSet(diff_file, encoding='utf-8', metadata_only=True) + + self.assertEqual(len(meta), len(full)) + self.assertEqual((meta.added, meta.removed), (full.added, full.removed)) + self.assertEqual((meta.added, meta.removed), (6, 2)) + def test_print_hunks_without_gaps(self): with codecs.open(self.sample_file, 'r', encoding='utf-8') as diff_file: res = PatchSet(diff_file) diff --git a/unidiff/patch.py b/unidiff/patch.py index 32f0835..fc6b385 100644 --- a/unidiff/patch.py +++ b/unidiff/patch.py @@ -263,8 +263,12 @@ def _parse_hunk(self, header: str, diff: Iterator, encoding: Optional[str], line = line.decode(encoding) if metadata_only: - # quick line type detection, no regex required - line_type = line[0] if line else LINE_TYPE_CONTEXT + # quick line type detection, no regex required; a bare + # newline (including a DOS "\r\n") is an empty context line + if not line or line[0] in ('\r', '\n'): + line_type = LINE_TYPE_CONTEXT + else: + line_type = line[0] if line_type not in (LINE_TYPE_ADDED, LINE_TYPE_REMOVED, LINE_TYPE_CONTEXT,