diff --git a/src/patch.cpp b/src/patch.cpp index 5aa84b1..929bac4 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -687,13 +687,19 @@ static int process_patches(const Options& options, DeferredWriter& deferred_writ } else { bool write_to_file = !options.dry_run; + const bool make_backup = options.save_backup + || (!result.all_hunks_applied_perfectly && !result.was_skipped && options.backup_if_mismatch == Options::OptionalBool::Yes); + // Clean up the file if it looks like it was removed. // NOTE: we check for file size for the degenerate case that the file is a removal, but has nothing left. if (options.remove_empty_files == Options::OptionalBool::Yes && (patch.operation == Operation::Delete || (patch.format == Format::Ed && tmp_out_file.size() == 0))) { if (tmp_out_file.size() == 0) { - if (!options.dry_run) + if (!options.dry_run) { + if (make_backup) + backup.make_backup_for(output_file); remove_file_and_empty_parent_folders(output_file); + } write_to_file = false; } else { out << "Not deleting file " << output_file << " as content differs from patch\n"; @@ -701,15 +707,15 @@ static int process_patches(const Options& options, DeferredWriter& deferred_writ } } - if (write_to_file) { - const bool make_backup = options.save_backup - || (!result.all_hunks_applied_perfectly && !result.was_skipped && options.backup_if_mismatch == Options::OptionalBool::Yes); + if (write_to_file) write_patched_result_to_file(patch, output_file, input_permissions, mode, deferred_writer, tmp_out_file, backup, make_backup); - } if (result.failed_hunks == 0) { - if (write_to_file && patch.operation == Operation::Rename) + if (write_to_file && patch.operation == Operation::Rename) { + if (make_backup) + backup.make_backup_for(file_to_patch); remove_file_and_empty_parent_folders(file_to_patch); + } } } } diff --git a/tests/test_basic.cpp b/tests/test_basic.cpp index 4af6bba..307811e 100644 --- a/tests/test_basic.cpp +++ b/tests/test_basic.cpp @@ -1538,6 +1538,127 @@ rename to b EXPECT_FILE_EQ("b", to_patch); EXPECT_FILE_EQ("b.orig", ""); EXPECT_FALSE(Patch::filesystem::exists("a")); + EXPECT_FILE_EQ("a.orig", to_patch); +} + +COMPAT_TEST(backup_of_removed_file) +{ + { + Patch::File file("diff.patch", std::ios_base::out); + + file << R"(--- gone.txt ++++ /dev/null +@@ -1 +0,0 @@ +-content to lose +)"; + file.close(); + } + + const std::string to_patch = "content to lose\n"; + { + Patch::File file("gone.txt", std::ios_base::out); + file << to_patch; + file.close(); + } + + Process process(patch_path, { patch_path, "--batch", "-p0", "-b", "-i", "diff.patch", nullptr }); + + EXPECT_EQ(process.stdout_data(), "patching file gone.txt\n"); + EXPECT_EQ(process.stderr_data(), ""); + EXPECT_EQ(process.return_code(), 0); + + // The file is removed, so the backup is the only copy left of its content. + EXPECT_FALSE(Patch::filesystem::exists("gone.txt")); + EXPECT_FILE_EQ("gone.txt.orig", to_patch); +} + +COMPAT_TEST(backup_of_removed_file_in_subdirectory) +{ + { + Patch::File file("diff.patch", std::ios_base::out); + + file << R"(--- sub/nested.txt ++++ /dev/null +@@ -1 +0,0 @@ +-content to lose +)"; + file.close(); + } + + const std::string to_patch = "content to lose\n"; + { + Patch::filesystem::create_directory("sub"); + Patch::File file("sub/nested.txt", std::ios_base::out); + file << to_patch; + file.close(); + } + + Process process(patch_path, { patch_path, "--batch", "-p0", "-b", "-i", "diff.patch", nullptr }); + + EXPECT_EQ(process.stdout_data(), "patching file sub/nested.txt\n"); + EXPECT_EQ(process.stderr_data(), ""); + EXPECT_EQ(process.return_code(), 0); + + EXPECT_FALSE(Patch::filesystem::exists("sub/nested.txt")); + EXPECT_FILE_EQ("sub/nested.txt.orig", to_patch); +} + +COMPAT_TEST(no_backup_of_removed_file_without_flag) +{ + { + Patch::File file("diff.patch", std::ios_base::out); + + file << R"(--- gone.txt ++++ /dev/null +@@ -1 +0,0 @@ +-content to lose +)"; + file.close(); + } + + { + Patch::File file("gone.txt", std::ios_base::out); + file << "content to lose\n"; + file.close(); + } + + Process process(patch_path, { patch_path, "--batch", "-p0", "-i", "diff.patch", nullptr }); + + EXPECT_EQ(process.stdout_data(), "patching file gone.txt\n"); + EXPECT_EQ(process.stderr_data(), ""); + EXPECT_EQ(process.return_code(), 0); + + EXPECT_FALSE(Patch::filesystem::exists("gone.txt")); + EXPECT_FALSE(Patch::filesystem::exists("gone.txt.orig")); +} + +COMPAT_TEST(no_backup_of_removed_file_for_dry_run) +{ + { + Patch::File file("diff.patch", std::ios_base::out); + + file << R"(--- gone.txt ++++ /dev/null +@@ -1 +0,0 @@ +-content to lose +)"; + file.close(); + } + + const std::string to_patch = "content to lose\n"; + { + Patch::File file("gone.txt", std::ios_base::out); + file << to_patch; + file.close(); + } + + Process process(patch_path, { patch_path, "--batch", "-p0", "-b", "--dry-run", "-i", "diff.patch", nullptr }); + + EXPECT_EQ(process.stderr_data(), ""); + EXPECT_EQ(process.return_code(), 0); + + EXPECT_FILE_EQ("gone.txt", to_patch); + EXPECT_FALSE(Patch::filesystem::exists("gone.txt.orig")); } COMPAT_TEST(backup_on_top_of_existing_file)