diff --git a/Tests/Utilities/TestPatch.cpp b/Tests/Utilities/TestPatch.cpp index 123533feeb3..f95a928a4a9 100644 --- a/Tests/Utilities/TestPatch.cpp +++ b/Tests/Utilities/TestPatch.cpp @@ -276,3 +276,40 @@ TEST_CASE(patch_with_timestamp_separated_by_tab) run_patch(ExpectSuccess::Yes, {}, patch, "patching file 1\n"sv); EXPECT_FILE_EQ(ByteString::formatted("{}/1", s_test_dir), "a\n"sv); } + +TEST_CASE(patch_defines_add_remove) +{ + PatchSetup setup; + + StringView patch = R"( +--- file.cpp ++++ file.cpp +@@ -1,4 +1,4 @@ + int main() + { +- return 0; ++ return 1; + } +)"sv; + + auto file = R"(int main() +{ + return 0; +} +)"sv; + + auto input = MUST(Core::File::open(ByteString::formatted("{}/file.cpp", s_test_dir), Core::File::OpenMode::Write)); + MUST(input->write_until_depleted(file.bytes())); + + run_patch(ExpectSuccess::Yes, { "--ifdef", "TEST_PATCH" }, patch); + + EXPECT_FILE_EQ(ByteString::formatted("{}/file.cpp", s_test_dir), R"(int main() +{ +#ifndef TEST_PATCH + return 0; +#else + return 1; +#endif +} +)"); +} diff --git a/Userland/Libraries/LibDiff/Applier.cpp b/Userland/Libraries/LibDiff/Applier.cpp index 8b8fd49608f..c1a7e90860f 100644 --- a/Userland/Libraries/LibDiff/Applier.cpp +++ b/Userland/Libraries/LibDiff/Applier.cpp @@ -130,7 +130,59 @@ static ErrorOr write_hunk(Stream& out, Hunk const& hunk, Location const& return line_number; } -ErrorOr apply_patch(Stream& out, Vector const& lines, Patch const& patch) +static ErrorOr write_define_hunk(Stream& out, Hunk const& hunk, Location const& location, Vector const& lines, StringView define) +{ + enum class State { + Outside, + InsideIFNDEF, + InsideIFDEF, + InsideELSE, + }; + + auto state = State::Outside; + + auto line_number = location.line_number; + + for (auto const& patch_line : hunk.lines) { + if (patch_line.operation == Diff::Line::Operation::Context) { + auto const& line = lines.at(line_number); + ++line_number; + if (state != State::Outside) { + TRY(out.write_formatted("#endif\n")); + state = State::Outside; + } + TRY(out.write_formatted("{}\n", line)); + } else if (patch_line.operation == Diff::Line::Operation::Addition) { + if (state == State::Outside) { + state = State::InsideIFDEF; + TRY(out.write_formatted("#ifdef {}\n", define)); + } else if (state == State::InsideIFNDEF) { + state = State::InsideELSE; + TRY(out.write_formatted("#else\n")); + } + TRY(out.write_formatted("{}\n", patch_line.content)); + } else if (patch_line.operation == Diff::Line::Operation::Removal) { + auto const& line = lines.at(line_number); + ++line_number; + + if (state == State::Outside) { + state = State::InsideIFNDEF; + TRY(out.write_formatted("#ifndef {}\n", define)); + } else if (state == State::InsideIFDEF) { + state = State::InsideELSE; + TRY(out.write_formatted("#else\n")); + } + TRY(out.write_formatted("{}\n", line)); + } + } + + if (state != State::Outside) + TRY(out.write_formatted("#endif\n")); + + return line_number; +} + +ErrorOr apply_patch(Stream& out, Vector const& lines, Patch const& patch, Optional const& define) { size_t line_number = 0; // NOTE: relative to 'old' file. ssize_t offset_error = 0; @@ -150,7 +202,10 @@ ErrorOr apply_patch(Stream& out, Vector const& lines, Patch co TRY(out.write_formatted("{}\n", lines.at(line_number))); // Then output the hunk to what we hope is the correct location in the file. - line_number = TRY(write_hunk(out, hunk, location, lines)); + if (define.has_value()) + line_number = TRY(write_define_hunk(out, hunk, location, lines, define.value())); + else + line_number = TRY(write_hunk(out, hunk, location, lines)); } // We've finished applying all hunks, write out anything from the old file we haven't already. diff --git a/Userland/Libraries/LibDiff/Applier.h b/Userland/Libraries/LibDiff/Applier.h index 011281d0f8e..f210aecc366 100644 --- a/Userland/Libraries/LibDiff/Applier.h +++ b/Userland/Libraries/LibDiff/Applier.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Shannon Booth + * Copyright (c) 2023-2024, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -11,6 +11,6 @@ namespace Diff { -ErrorOr apply_patch(Stream& out, Vector const& lines, Patch const& patch); +ErrorOr apply_patch(Stream& out, Vector const& lines, Patch const& patch, Optional const& define = {}); } diff --git a/Userland/Utilities/patch.cpp b/Userland/Utilities/patch.cpp index 03e8b8e7144..e5cc7beb1a4 100644 --- a/Userland/Utilities/patch.cpp +++ b/Userland/Utilities/patch.cpp @@ -39,7 +39,7 @@ static ErrorOr read_content(StringView path_of_file_to_patch, Diff:: return ByteBuffer {}; } -static ErrorOr do_patch(StringView path_of_file_to_patch, Diff::Patch const& patch) +static ErrorOr do_patch(StringView path_of_file_to_patch, Diff::Patch const& patch, Optional const& define = {}) { ByteBuffer content = TRY(read_content(path_of_file_to_patch, patch)); auto lines = StringView(content).lines(); @@ -49,7 +49,7 @@ static ErrorOr do_patch(StringView path_of_file_to_patch, Diff::Patch cons auto tmp_file = TRY(Core::File::adopt_fd(TRY(Core::System::mkstemp(tmp_output)), Core::File::OpenMode::ReadWrite)); StringView tmp_path { tmp_output, sizeof(tmp_output) }; - TRY(Diff::apply_patch(*tmp_file, lines, patch)); + TRY(Diff::apply_patch(*tmp_file, lines, patch, define)); // If the patched file ends up being empty, remove it, as the patch was a removal. // Note that we cannot simply rely on the patch successfully applying and the patch claiming it is removing the file @@ -67,11 +67,13 @@ static ErrorOr do_patch(StringView path_of_file_to_patch, Diff::Patch cons ErrorOr serenity_main(Main::Arguments arguments) { StringView directory; + Optional define; Optional strip_count; Core::ArgsParser args_parser; args_parser.add_option(directory, "Change the working directory to before applying the patch file", "directory", 'd', "directory"); args_parser.add_option(strip_count, "Strip given number of leading path components from file names (defaults as basename)", "strip", 'p', "count"); + args_parser.add_option(define, "Apply merged patch content separated by C preprocessor macros", "ifdef", 'D', "define"); args_parser.parse(arguments); if (!directory.is_null()) @@ -100,7 +102,7 @@ ErrorOr serenity_main(Main::Arguments arguments) } outln("patching file {}", to_patch); - TRY(do_patch(to_patch, patch)); + TRY(do_patch(to_patch, patch, define)); } return 0;