From dd373eacbcaa22d8752ba2afa936b716a4541bad Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Wed, 12 Jul 2023 10:37:26 +1200 Subject: [PATCH] LibDiff+patch: Support multiple patches in a single patch file Multiple patches may be concatenated in the same patch file, such as git commits which are changing multiple files at the same time. To handle this, parse each patch in order in the patch file, and apply each patch sequentially. To determine whether we are at the end of a patch (and not just parsing another hunk) the parser will look for a leading '@@ ' after every hunk. If that is found, there is another hunk. Otherwise, we must be at the end of this patch. --- Tests/Utilities/TestPatch.cpp | 22 +++++++++++++++++ Userland/Libraries/LibDiff/Hunks.cpp | 16 ++++++++++--- Userland/Libraries/LibDiff/Hunks.h | 4 +++- Userland/Utilities/patch.cpp | 35 +++++++++++++++------------- 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/Tests/Utilities/TestPatch.cpp b/Tests/Utilities/TestPatch.cpp index fd1a0511543..e39c02f28fc 100644 --- a/Tests/Utilities/TestPatch.cpp +++ b/Tests/Utilities/TestPatch.cpp @@ -165,3 +165,25 @@ TEST_CASE(add_file_from_scratch) EXPECT_FILE_EQ(MUST(String::formatted("{}/file_to_add", s_test_dir)), "Hello, friends!\n"); } + +TEST_CASE(two_patches_in_single_patch_file) +{ + PatchSetup setup; + + auto patch = R"( +--- /dev/null ++++ a/first_file_to_add +@@ -0,0 +1 @@ ++Hello, friends! +--- /dev/null ++++ a/second_file_to_add +@@ -0,0 +1 @@ ++Hello, friends! +)"sv; + + run_patch({}, patch, "patching file first_file_to_add\n" + "patching file second_file_to_add\n"sv); + + EXPECT_FILE_EQ(MUST(String::formatted("{}/first_file_to_add", s_test_dir)), "Hello, friends!\n"); + EXPECT_FILE_EQ(MUST(String::formatted("{}/second_file_to_add", s_test_dir)), "Hello, friends!\n"); +} diff --git a/Userland/Libraries/LibDiff/Hunks.cpp b/Userland/Libraries/LibDiff/Hunks.cpp index af88fa6e4b6..205b3d67783 100644 --- a/Userland/Libraries/LibDiff/Hunks.cpp +++ b/Userland/Libraries/LibDiff/Hunks.cpp @@ -87,6 +87,14 @@ ErrorOr Parser::parse_file_line(Optional const& strip_count) return stripped_path.to_string(); } +ErrorOr Parser::parse_patch(Optional const& strip_count) +{ + Patch patch; + patch.header = TRY(parse_header(strip_count)); + patch.hunks = TRY(parse_hunks()); + return patch; +} + ErrorOr
Parser::parse_header(Optional const& strip_count) { Header header; @@ -111,20 +119,20 @@ ErrorOr
Parser::parse_header(Optional const& strip_count) consume_line(); } - return Error::from_string_literal("Unable to find any patch"); + return header; } ErrorOr> Parser::parse_hunks() { Vector hunks; - while (!is_eof()) { + while (next_is("@@ ")) { // Try an locate a hunk location in this hunk. It may be prefixed with information. auto maybe_location = consume_unified_location(); consume_line(); if (!maybe_location.has_value()) - continue; + break; Hunk hunk { *maybe_location, {} }; @@ -178,6 +186,8 @@ ErrorOr> Parser::parse_hunks() ErrorOr> parse_hunks(StringView diff) { Parser lexer(diff); + while (!lexer.next_is("@@ ") && !lexer.is_eof()) + lexer.consume_line(); return lexer.parse_hunks(); } } diff --git a/Userland/Libraries/LibDiff/Hunks.h b/Userland/Libraries/LibDiff/Hunks.h index 51c0d7d46b7..fa919abeda7 100644 --- a/Userland/Libraries/LibDiff/Hunks.h +++ b/Userland/Libraries/LibDiff/Hunks.h @@ -80,11 +80,13 @@ class Parser : public GenericLexer { public: using GenericLexer::GenericLexer; + ErrorOr parse_patch(Optional const& strip_count = {}); + ErrorOr> parse_hunks(); +private: ErrorOr
parse_header(Optional const& strip_count); -private: ErrorOr parse_file_line(Optional const& strip_count); Optional consume_unified_location(); bool consume_line_number(size_t& number); diff --git a/Userland/Utilities/patch.cpp b/Userland/Utilities/patch.cpp index 0ce7c37557e..cebe38942b1 100644 --- a/Userland/Utilities/patch.cpp +++ b/Userland/Utilities/patch.cpp @@ -65,25 +65,28 @@ ErrorOr serenity_main(Main::Arguments arguments) auto patch_content = TRY(input->read_until_eof()); - // FIXME: Support multiple patches in the patch file. Diff::Parser parser(patch_content); - Diff::Patch patch; - patch.header = TRY(parser.parse_header(strip_count)); - patch.hunks = TRY(parser.parse_hunks()); - // FIXME: Support adding/removing a file, and asking for file to patch as fallback otherwise. - StringView to_patch; - if (FileSystem::is_regular_file(patch.header.old_file_path)) { - to_patch = patch.header.old_file_path; - } else if (is_adding_file(patch) || FileSystem::is_regular_file(patch.header.new_file_path)) { - to_patch = patch.header.new_file_path; - } else { - warnln("Unable to determine file to patch"); - return 1; + while (!parser.is_eof()) { + Diff::Patch patch = TRY(parser.parse_patch(strip_count)); + + if (patch.header.format == Diff::Format::Unknown) + break; + + // FIXME: Support adding/removing a file, and asking for file to patch as fallback otherwise. + StringView to_patch; + if (FileSystem::is_regular_file(patch.header.old_file_path)) { + to_patch = patch.header.old_file_path; + } else if (is_adding_file(patch) || FileSystem::is_regular_file(patch.header.new_file_path)) { + to_patch = patch.header.new_file_path; + } else { + warnln("Unable to determine file to patch"); + return 1; + } + + outln("patching file {}", to_patch); + TRY(do_patch(to_patch, patch)); } - outln("patching file {}", to_patch); - TRY(do_patch(to_patch, patch)); - return 0; }