Просмотр исходного кода

patch+LibDiff: Implement 'strip' of filenames when parsing patch

Implement the patch '-p' / '--strip' option, which strips the given
number of leading components from filenames parsed in the patch header.
If not given this option defaults to the basename of that path.
Shannon Booth 2 лет назад
Родитель
Сommit
81df0278b1

+ 42 - 0
Tests/Utilities/TestPatch.cpp

@@ -107,3 +107,45 @@ TEST_CASE(basic_addition_patch_from_empty_file)
 
     EXPECT_FILE_EQ(MUST(String::formatted("{}/a", s_test_dir)), "1\n2\n3\n");
 }
+
+TEST_CASE(strip_path_to_basename)
+{
+    PatchSetup setup;
+
+    auto patch = R"(
+--- /dev/null
++++ a/bunch/of/../folders/stripped/to/basename
+@@ -0,0 +1 @@
++Hello, friends!
+)"sv;
+
+    auto file = ""sv;
+    auto input = MUST(Core::File::open(MUST(String::formatted("{}/basename", s_test_dir)), Core::File::OpenMode::Write));
+    MUST(input->write_until_depleted(file.bytes()));
+
+    run_patch({}, patch, "patching file basename\n"sv);
+
+    EXPECT_FILE_EQ(MUST(String::formatted("{}/basename", s_test_dir)), "Hello, friends!\n");
+}
+
+TEST_CASE(strip_path_partially)
+{
+    PatchSetup setup;
+
+    auto patch = R"(
+--- /dev/null
++++ a/bunch/of/../folders/stripped/to/basename
+@@ -0,0 +1 @@
++Hello, friends!
+)"sv;
+
+    MUST(Core::System::mkdir(MUST(String::formatted("{}/to", s_test_dir)), 0755));
+
+    auto file = ""sv;
+    auto input = MUST(Core::File::open(MUST(String::formatted("{}/to/basename", s_test_dir)), Core::File::OpenMode::Write));
+    MUST(input->write_until_depleted(file.bytes()));
+
+    run_patch({ "-p6" }, patch, "patching file to/basename\n"sv);
+
+    EXPECT_FILE_EQ(MUST(String::formatted("{}/to/basename", s_test_dir)), "Hello, friends!\n");
+}

+ 33 - 3
Userland/Libraries/LibDiff/Hunks.cpp

@@ -7,6 +7,7 @@
 
 #include "Hunks.h"
 #include <AK/Debug.h>
+#include <AK/LexicalPath.h>
 
 namespace Diff {
 
@@ -57,19 +58,48 @@ bool Parser::consume_line_number(size_t& number)
     return true;
 }
 
-ErrorOr<Header> Parser::parse_header()
+ErrorOr<String> Parser::parse_file_line(Optional<size_t> const& strip_count)
+{
+    // FIXME: handle parsing timestamps as well.
+    auto path = consume_line();
+
+    // No strip count given. Default to basename of file.
+    if (!strip_count.has_value())
+        return String::from_deprecated_string(LexicalPath::basename(path));
+
+    // NOTE: We cannot use LexicalPath::parts as we want to strip the non-canonicalized path.
+    auto const& parts = path.split_view('/');
+
+    // More components to strip than the filename has. Just pretend it is missing.
+    if (strip_count.value() >= parts.size())
+        return String();
+
+    // Remove given number of leading components from the path.
+    size_t components = parts.size() - strip_count.value();
+
+    StringBuilder stripped_path;
+    for (size_t i = parts.size() - components; i < parts.size(); ++i) {
+        TRY(stripped_path.try_append(parts[i]));
+        if (i != parts.size() - 1)
+            TRY(stripped_path.try_append("/"sv));
+    }
+
+    return stripped_path.to_string();
+}
+
+ErrorOr<Header> Parser::parse_header(Optional<size_t> const& strip_count)
 {
     Header header;
 
     while (!is_eof()) {
 
         if (consume_specific("+++ ")) {
-            header.new_file_path = TRY(String::from_utf8(consume_line()));
+            header.new_file_path = TRY(parse_file_line(strip_count));
             continue;
         }
 
         if (consume_specific("--- ")) {
-            header.old_file_path = TRY(String::from_utf8(consume_line()));
+            header.old_file_path = TRY(parse_file_line(strip_count));
             continue;
         }
 

+ 2 - 1
Userland/Libraries/LibDiff/Hunks.h

@@ -82,9 +82,10 @@ public:
 
     ErrorOr<Vector<Hunk>> parse_hunks();
 
-    ErrorOr<Header> parse_header();
+    ErrorOr<Header> parse_header(Optional<size_t> const& strip_count);
 
 private:
+    ErrorOr<String> parse_file_line(Optional<size_t> const& strip_count);
     Optional<HunkLocation> consume_unified_location();
     bool consume_line_number(size_t& number);
 };

+ 3 - 1
Userland/Utilities/patch.cpp

@@ -30,9 +30,11 @@ static ErrorOr<void> do_patch(StringView path_of_file_to_patch, Diff::Patch cons
 ErrorOr<int> serenity_main(Main::Arguments arguments)
 {
     StringView directory;
+    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.parse(arguments);
 
     if (!directory.is_null())
@@ -45,7 +47,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     // FIXME: Support multiple patches in the patch file.
     Diff::Parser parser(patch_content);
     Diff::Patch patch;
-    patch.header = TRY(parser.parse_header());
+    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.