diff --git a/src/file.cpp b/src/file.cpp index 1127118..41616c5 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -164,7 +164,7 @@ char File::peek() int c = fgetc(m_file); ungetc(c, m_file); - return static_cast(c); + return c == EOF ? '\0' : static_cast(c); } bool File::get_line(std::string& line, NewLine* newline) diff --git a/src/parser.cpp b/src/parser.cpp index cc6291b..d606178 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -631,10 +631,16 @@ bool Parser::parse_patch_header(Patch& patch, PatchHeaderInfo& header_info, int } if (patch.operation == Operation::Change) { - if (hunk.new_file_range.start_line == 0) + if (patch.new_file_path == "/dev/null") { patch.operation = Operation::Delete; - else if (hunk.old_file_range.start_line == 0) + } else if (patch.old_file_path == "/dev/null") { patch.operation = Operation::Add; + } else if (patch.old_file_path.empty() && patch.new_file_path.empty()) { + if (hunk.new_file_range.start_line == 0) + patch.operation = Operation::Delete; + else if (hunk.old_file_range.start_line == 0) + patch.operation = Operation::Add; + } } return should_parse_body; diff --git a/tests/test_basic.cpp b/tests/test_basic.cpp index 4002c38..4191aa9 100644 --- a/tests/test_basic.cpp +++ b/tests/test_basic.cpp @@ -769,6 +769,33 @@ PATCH_TEST(remove_file_successfully_posix_and_remove_flag) remove_file(patch_path, true, { "--posix", "--remove-empty-files" }); } +PATCH_TEST(unified_patch_that_empties_file_keeps_empty_file) +{ + { + Patch::File file("diff.patch", std::ios_base::out); + file << R"(--- old.txt 2026-04-05 13:10:37 ++++ new.txt 2026-04-05 13:10:37 +@@ -1,2 +0,0 @@ +- +-n[@CuBYRF+w.Ul.jw]M)e=XYaAc +)"; + } + + { + Patch::File file("target.txt", std::ios_base::out); + file << "\n" + "n[@CuBYRF+w.Ul.jw]M)e=XYaAc\n"; + } + + Process process(patch_path, { patch_path, "-i", "diff.patch", "target.txt", nullptr }); + + EXPECT_EQ(process.stdout_data(), "patching file target.txt\n"); + EXPECT_EQ(process.stderr_data(), ""); + EXPECT_EQ(process.return_code(), 0); + EXPECT_TRUE(Patch::filesystem::exists("target.txt")); + EXPECT_FILE_EQ("target.txt", ""); +} + PATCH_TEST(git_patch_remove_file) { { diff --git a/tests/test_parser.cpp b/tests/test_parser.cpp index 839f7cf..383b21d 100644 --- a/tests/test_parser.cpp +++ b/tests/test_parser.cpp @@ -805,3 +805,24 @@ TEST(parser_malformed_range_line_fails) "+1\n"); EXPECT_THROW(Patch::parse_patch(patch_file), std::runtime_error); } + +TEST(parser_unified_patch_that_empties_file_is_not_delete) +{ + Patch::File patch_file = Patch::File::create_temporary_with_content(R"( +--- old.txt 2026-04-05 13:10:37 ++++ new.txt 2026-04-05 13:10:37 +@@ -1,2 +0,0 @@ +- +-n[@CuBYRF+w.Ul.jw]M)e=XYaAc +)"); + + auto patch = Patch::parse_patch(patch_file); + EXPECT_EQ(patch.operation, Patch::Operation::Change); + EXPECT_EQ(patch.old_file_path, "old.txt"); + EXPECT_EQ(patch.new_file_path, "new.txt"); + EXPECT_EQ(patch.hunks.size(), 1); + EXPECT_EQ(patch.hunks[0].old_file_range.start_line, 1); + EXPECT_EQ(patch.hunks[0].old_file_range.number_of_lines, 2); + EXPECT_EQ(patch.hunks[0].new_file_range.start_line, 0); + EXPECT_EQ(patch.hunks[0].new_file_range.number_of_lines, 0); +}