diff --git a/tests/samples/quilt.diff b/tests/samples/quilt.diff new file mode 100644 index 0000000..2a4be3d --- /dev/null +++ b/tests/samples/quilt.diff @@ -0,0 +1,25 @@ +Description: Add fts64 support + Adds a new fts64 header and its implementation, and wires the new + module into the io Makefile. +Author: Some One +Forwarded: https://example.com/thread +Last-Update: 2026-07-31 + +--- /dev/null ++++ b/include/fts64.h +@@ -0,0 +1,3 @@ ++#ifndef _FTS64_H ++#define _FTS64_H ++#endif +--- /dev/null ++++ b/io/fts64.c +@@ -0,0 +1,2 @@ ++#include ++/* implementation */ +--- a/io/Makefile ++++ b/io/Makefile +@@ -1,3 +1,4 @@ + routines := \ + fts \ ++ fts64 \ + open diff --git a/tests/test_parser.py b/tests/test_parser.py index 9571ed0..770dc71 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -445,6 +445,51 @@ def test_parse_diff_with_empty_filenames(self): # the parsed patch should round-trip back to the original input self.assertEqual(str(res), ''.join(diff)) + def test_parse_multiple_added_files(self): + # regression test for issue #143: quilt-style patches that add several + # files in a row all share the "/dev/null" source, which must not make + # consecutive files be merged into one. + diff = ( + '--- /dev/null\n' + '+++ b/1.txt\n' + '@@ -0,0 +1 @@\n' + '+a\n' + '--- /dev/null\n' + '+++ b/2.txt\n' + '@@ -0,0 +1 @@\n' + '+b\n' + ) + + res = PatchSet(diff) + + self.assertEqual(len(res), 2) + self.assertEqual([f.path for f in res], ['1.txt', '2.txt']) + self.assertTrue(all(f.is_added_file for f in res)) + self.assertFalse(any(f.is_rename for f in res)) + self.assertEqual(res.added, 2) + self.assertEqual(res[0].target_file, 'b/1.txt') + self.assertEqual(res[1].target_file, 'b/2.txt') + + def test_parse_quilt_diff(self): + # issue #143: a realistic quilt patch (DEP-3 header preamble, two added + # files sharing the /dev/null source, then a modified file). + filename = os.path.join(self.samples_dir, 'samples/quilt.diff') + with open(filename) as f: + res = PatchSet(f) + + self.assertEqual(len(res), 3) + self.assertEqual( + [f.path for f in res], + ['include/fts64.h', 'io/fts64.c', 'io/Makefile']) + self.assertEqual( + [f.path for f in res.added_files], ['include/fts64.h', 'io/fts64.c']) + self.assertEqual([f.path for f in res.modified_files], ['io/Makefile']) + self.assertFalse(any(f.is_rename for f in res)) + self.assertEqual((res.added, res.removed), (6, 0)) + # the DEP-3 header preamble is kept as the first file's patch info + self.assertTrue( + str(res[0].patch_info).startswith('Description: Add fts64 support')) + def test_parse_filename_with_spaces(self): filename = os.path.join(self.samples_dir, 'samples/git_filenames_with_spaces.diff') with open(filename) as f: diff --git a/unidiff/patch.py b/unidiff/patch.py index 32f0835..dea6821 100644 --- a/unidiff/patch.py +++ b/unidiff/patch.py @@ -530,10 +530,11 @@ def _parse(self, diff: Iterable, encoding: Optional[str], if is_source_filename: source_file = is_source_filename.group('filename') source_timestamp = is_source_filename.group('timestamp') - # reset current file, unless we are processing a rename - # (in that case, source files should match) - if current_file is not None and not ( - current_file.source_file == source_file): + # a "---" line starts a new file, unless we are still inside a + # git header block (patch_info is only set while a "diff --git" + # header is being processed, in which case this line just + # restates the source of the file already being built) + if current_file is not None and patch_info is None: current_file = None elif current_file is not None: current_file.source_timestamp = source_timestamp