diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt index 6b0447f889..6c2c30f898 100644 --- a/Userland/Utilities/CMakeLists.txt +++ b/Userland/Utilities/CMakeLists.txt @@ -3,7 +3,7 @@ list(APPEND SPECIAL_TARGETS test install) list(APPEND REQUIRED_TARGETS arp base64 basename cat chmod chown clear comm cp cut date dd df diff dirname dmesg du echo env expr false file find grep groups head host hostname id ifconfig kill killall ln logout ls mkdir mount mv network-settings nproc - pgrep pidof ping pkill pmap ps readlink realpath reboot rm rmdir sed route seq shutdown sleep sort stat stty su tail test + patch pgrep pidof ping pkill pmap ps readlink realpath reboot rm rmdir sed route seq shutdown sleep sort stat stty su tail test touch tr true umount uname uniq uptime w wc which whoami xargs yes ) list(APPEND RECOMMENDED_TARGETS @@ -123,6 +123,7 @@ target_link_libraries(notify PRIVATE LibGfx LibGUI) target_link_libraries(open PRIVATE LibDesktop LibFileSystem) target_link_libraries(passwd PRIVATE LibCrypt) target_link_libraries(paste PRIVATE LibGUI) +target_link_libraries(patch PRIVATE LibDiff LibFileSystem) target_link_libraries(pdf PRIVATE LibGfx LibPDF) target_link_libraries(pgrep PRIVATE LibRegex) target_link_libraries(pixelflut PRIVATE LibImageDecoderClient LibIPC LibGfx) diff --git a/Userland/Utilities/patch.cpp b/Userland/Utilities/patch.cpp new file mode 100644 index 0000000000..a3261b1ca2 --- /dev/null +++ b/Userland/Utilities/patch.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +static ErrorOr do_patch(StringView path_of_file_to_patch, Diff::Patch const& patch) +{ + auto file_to_patch = TRY(Core::File::open(path_of_file_to_patch, Core::File::OpenMode::Read)); + auto content = TRY(file_to_patch->read_until_eof()); + auto lines = StringView(content).lines(); + + // Apply patch to a temporary file in case one or more of the hunks fails. + char tmp_output[] = "/tmp/patch.XXXXXX"; + auto tmp_file = TRY(Core::File::adopt_fd(TRY(Core::System::mkstemp(tmp_output)), Core::File::OpenMode::ReadWrite)); + + TRY(Diff::apply_patch(*tmp_file, lines, patch)); + + return FileSystem::move_file(path_of_file_to_patch, StringView { tmp_output, sizeof(tmp_output) }); +} + +ErrorOr serenity_main(Main::Arguments arguments) +{ + Core::ArgsParser args_parser; + args_parser.parse(arguments); + + auto input = TRY(Core::File::standard_input()); + + 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()); + 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 (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)); + + return 0; +}