diff --git a/tests/samples/debdiff.diff b/tests/samples/debdiff.diff new file mode 100644 index 0000000..a53efe9 --- /dev/null +++ b/tests/samples/debdiff.diff @@ -0,0 +1,7 @@ +diff -u -N original/added.txt new/added.txt +--- original/added.txt 1970-01-01 01:00:00.000000000 +0100 ++++ new/added.txt 2025-03-26 12:42:27.672906377 +0000 +@@ -0,0 +1 @@ ++new file +Binary files Binary files /t/p1/a.png and /t/p2/a.png differ +Binary files Binary files /t/p1/b.png and /t/p2/b.png differ diff --git a/tests/test_parser.py b/tests/test_parser.py index 5cda866..cf7598d 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -284,30 +284,58 @@ def test_parse_diff_with_new_and_modified_binary_files(self): self.assertFalse(res[0].is_removed_file) self.assertTrue(res[0].is_added_file) self.assertFalse(res[0].is_binary_file) + self.assertEqual(res[0].diff_line_no, 1) # second file is added self.assertFalse(res[1].is_modified_file) self.assertFalse(res[1].is_removed_file) self.assertTrue(res[1].is_added_file) self.assertTrue(res[1].is_binary_file) + self.assertEqual(res[1].diff_line_no, 4) # third file is modified self.assertTrue(res[2].is_modified_file) self.assertFalse(res[2].is_removed_file) self.assertFalse(res[2].is_added_file) self.assertTrue(res[2].is_binary_file) + self.assertEqual(res[2].diff_line_no, 8) # fourth file is removed self.assertFalse(res[3].is_modified_file) self.assertTrue(res[3].is_removed_file) self.assertFalse(res[3].is_added_file) self.assertTrue(res[3].is_binary_file) + self.assertEqual(res[3].diff_line_no, 11) # fifth empty file is added self.assertFalse(res[4].is_modified_file) self.assertFalse(res[4].is_removed_file) self.assertTrue(res[4].is_added_file) self.assertFalse(res[4].is_binary_file) + self.assertEqual(res[4].diff_line_no, 15) + + def test_parse_debdiff_binary_file_line_numbers(self): + # issue #122 / PR #123: a binary change without hunks should still + # expose the diff line number where its entry appears. + utf8_file = os.path.join(self.samples_dir, 'samples/debdiff.diff') + with open(utf8_file, 'r') as diff_file: + res = PatchSet(diff_file) + + self.assertEqual(len(res), 3) + + # first file has a hunk; entry starts at the +++ line (3) + self.assertEqual(res[0].path, 'new/added.txt') + self.assertEqual(res[0].diff_line_no, 3) + self.assertEqual(res[0][0][0].diff_line_no, 5) + + # the binary entries carry the line number of their "Binary files" line + self.assertEqual(res[1].path, '/t/p2/a.png') + self.assertTrue(res[1].is_binary_file) + self.assertEqual(res[1].diff_line_no, 6) + + self.assertEqual(res[2].path, '/t/p2/b.png') + self.assertTrue(res[2].is_binary_file) + self.assertEqual(res[2].diff_line_no, 7) def test_parse_round_trip_with_binary_files_in_diff(self): """Parse git diff with binary files though round trip""" diff --git a/unidiff/patch.py b/unidiff/patch.py index 6105906..c3aa171 100644 --- a/unidiff/patch.py +++ b/unidiff/patch.py @@ -209,7 +209,8 @@ def __init__(self, patch_info: Optional[PatchInfo] = None, target_timestamp: Optional[str] = None, is_binary_file: bool = False, source_mode: Optional[str] = None, - target_mode: Optional[str] = None) -> None: + target_mode: Optional[str] = None, + diff_line_no: Optional[int] = None) -> None: super(PatchedFile, self).__init__() self.patch_info = patch_info self.source_file = source @@ -220,6 +221,9 @@ def __init__(self, patch_info: Optional[PatchInfo] = None, # git file modes (e.g. '100644', '100755', '120000'); None if unknown self.source_mode = source_mode self.target_mode = target_mode + # 1-based line number in the diff where this file entry starts; useful + # to locate files that have no hunks (e.g. binary changes) + self.diff_line_no = diff_line_no def __repr__(self) -> str: return "" % self.path @@ -453,7 +457,7 @@ def _parse(self, diff: Iterable, encoding: Optional[str], patch_info = None diff_lines = enumerate(diff, 1) - for unused_diff_line_no, line in diff_lines: + for diff_line_no, line in diff_lines: if encoding is not None: line = line.decode(encoding) @@ -466,7 +470,8 @@ def _parse(self, diff: Iterable, encoding: Optional[str], source_file = is_diff_git_header.group('source') target_file = is_diff_git_header.group('target') current_file = PatchedFile( - patch_info, source_file, target_file, None, None) + patch_info, source_file, target_file, None, None, + diff_line_no=diff_line_no) self.append(current_file) patch_info.append(line) continue @@ -542,7 +547,8 @@ def _parse(self, diff: Iterable, encoding: Optional[str], # add current file to PatchSet current_file = PatchedFile( patch_info, source_file, target_file, - source_timestamp, target_timestamp) + source_timestamp, target_timestamp, + diff_line_no=diff_line_no) self.append(current_file) patch_info = None else: @@ -585,7 +591,8 @@ def _parse(self, diff: Iterable, encoding: Optional[str], current_file.is_binary_file = True else: current_file = PatchedFile( - patch_info, source_file, target_file, is_binary_file=True) + patch_info, source_file, target_file, is_binary_file=True, + diff_line_no=diff_line_no) self.append(current_file) patch_info = None current_file = None