patch+LibDiff: Add support for applying patches with preprocessor macro

This commit is contained in:
Shannon Booth 2024-03-02 23:21:51 +13:00 committed by Andreas Kling
parent ee643b6417
commit 4bce61e508
Notes: sideshowbarker 2024-07-17 00:59:43 +09:00
4 changed files with 101 additions and 7 deletions

View File

@ -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
}
)");
}

View File

@ -130,7 +130,59 @@ static ErrorOr<size_t> write_hunk(Stream& out, Hunk const& hunk, Location const&
return line_number;
}
ErrorOr<void> apply_patch(Stream& out, Vector<StringView> const& lines, Patch const& patch)
static ErrorOr<size_t> write_define_hunk(Stream& out, Hunk const& hunk, Location const& location, Vector<StringView> 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<void> apply_patch(Stream& out, Vector<StringView> const& lines, Patch const& patch, Optional<StringView> const& define)
{
size_t line_number = 0; // NOTE: relative to 'old' file.
ssize_t offset_error = 0;
@ -150,7 +202,10 @@ ErrorOr<void> apply_patch(Stream& out, Vector<StringView> 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.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -11,6 +11,6 @@
namespace Diff {
ErrorOr<void> apply_patch(Stream& out, Vector<StringView> const& lines, Patch const& patch);
ErrorOr<void> apply_patch(Stream& out, Vector<StringView> const& lines, Patch const& patch, Optional<StringView> const& define = {});
}

View File

@ -39,7 +39,7 @@ static ErrorOr<ByteBuffer> read_content(StringView path_of_file_to_patch, Diff::
return ByteBuffer {};
}
static ErrorOr<void> do_patch(StringView path_of_file_to_patch, Diff::Patch const& patch)
static ErrorOr<void> do_patch(StringView path_of_file_to_patch, Diff::Patch const& patch, Optional<StringView> const& define = {})
{
ByteBuffer content = TRY(read_content(path_of_file_to_patch, patch));
auto lines = StringView(content).lines();
@ -49,7 +49,7 @@ static ErrorOr<void> 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<void> do_patch(StringView path_of_file_to_patch, Diff::Patch cons
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
StringView directory;
Optional<StringView> define;
Optional<size_t> strip_count;
Core::ArgsParser args_parser;
args_parser.add_option(directory, "Change the working directory to <directory> 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<int> 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;