Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add history undo command #1452

Merged
merged 17 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions dnf5/commands/history/arguments.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Copyright Contributors to the libdnf project.

This file is part of libdnf: https://github.com/rpm-software-management/libdnf/

Libdnf is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 of the License, or
(at your option) any later version.

Libdnf is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with libdnf. If not, see <https://www.gnu.org/licenses/>.
*/


#include "arguments.hpp"


namespace dnf5 {


std::function<std::vector<std::string>(const char * arg)> create_history_id_autocomplete(Context & ctx) {
return [&ctx](const char * arg) {
const std::string_view to_complete{arg};
libdnf5::transaction::TransactionHistory history(ctx.get_base());
std::vector<int64_t> ids = history.list_transaction_ids();
std::vector<std::string> all_string_ids;
std::transform(
ids.begin(), ids.end(), std::back_inserter(all_string_ids), [](int num) { return std::to_string(num); });
std::string last;
std::vector<std::string> possible_ids;
for (const auto & id : all_string_ids) {
if (id.compare(0, to_complete.length(), to_complete) == 0) {
possible_ids.emplace_back(id);
last = id;
}
}
if (possible_ids.size() == 1) {
possible_ids[0] = last + " ";
}
return possible_ids;
};
}


} // namespace dnf5
33 changes: 31 additions & 2 deletions dnf5/commands/history/arguments.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#ifndef DNF5_COMMANDS_HISTORY_ARGUMENTS_HPP
#define DNF5_COMMANDS_HISTORY_ARGUMENTS_HPP

#include "dnf5/context.hpp"

#include <libdnf5-cli/session.hpp>
#include <libdnf5/utils/bgettext/bgettext-lib.h>

Expand All @@ -30,8 +32,9 @@ namespace dnf5 {

class TransactionSpecArguments : public libdnf5::cli::session::StringArgumentList {
public:
explicit TransactionSpecArguments(libdnf5::cli::session::Command & command)
: StringArgumentList(command, "transaction-id", _("Transaction ID")) {}
explicit TransactionSpecArguments(
libdnf5::cli::session::Command & command, int nrepeats = libdnf5::cli::ArgumentParser::PositionalArg::OPTIONAL)
: StringArgumentList(command, "transaction-id", _("Transaction ID"), nrepeats) {}
};


Expand All @@ -41,6 +44,32 @@ class ReverseOption : public libdnf5::cli::session::BoolOption {
: BoolOption(command, "reverse", '\0', _("Reverse the order of transactions."), false) {}
};

class IgnoreInstalledOption : public libdnf5::cli::session::BoolOption {
public:
explicit IgnoreInstalledOption(libdnf5::cli::session::Command & command)
: BoolOption(
command,
"ignore-installed",
'\0',
_("Don't consider mismatches between installed and stored transaction packages as errors. This can "
"result in an empty transaction because among other things the option can ignore failing Remove "
"actions."),
false) {}
};

class IgnoreExtrasOption : public libdnf5::cli::session::BoolOption {
public:
explicit IgnoreExtrasOption(libdnf5::cli::session::Command & command)
: BoolOption(
command,
"ignore-extras",
'\0',
_("Don't consider extra packages pulled into the transaction as errors."),
false) {}
};

std::function<std::vector<std::string>(const char * arg)> create_history_id_autocomplete(Context & ctx);


} // namespace dnf5

Expand Down
2 changes: 1 addition & 1 deletion dnf5/commands/history/history.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ void HistoryCommand::register_subcommands() {
auto * software_management_commands_group = parser.add_new_group("history_software_management_commands");
software_management_commands_group->set_header("Software Management Commands:");
cmd.register_group(software_management_commands_group);
// register_subcommand(std::make_unique<HistoryUndoCommand>(get_context()), software_management_commands_group);
register_subcommand(std::make_unique<HistoryUndoCommand>(get_context()), software_management_commands_group);
// register_subcommand(std::make_unique<HistoryRedoCommand>(get_context()), software_management_commands_group);
// register_subcommand(std::make_unique<HistoryRollbackCommand>(get_context()), software_management_commands_group);
register_subcommand(std::make_unique<HistoryStoreCommand>(get_context()), software_management_commands_group);
Expand Down
2 changes: 2 additions & 0 deletions dnf5/commands/history/history_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ void HistoryInfoCommand::set_argument_parser() {
get_argument_parser_command()->set_description("Print details about transactions");

transaction_specs = std::make_unique<TransactionSpecArguments>(*this);
auto & ctx = get_context();
transaction_specs->get_arg()->set_complete_hook_func(create_history_id_autocomplete(ctx));
reverse = std::make_unique<ReverseOption>(*this);
}

Expand Down
47 changes: 45 additions & 2 deletions dnf5/commands/history/history_undo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,57 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.

#include "history_undo.hpp"

#include "arguments.hpp"
#include "commands/history/transaction_id.hpp"
#include "dnf5/shared_options.hpp"

#include <libdnf5/utils/bgettext/bgettext-mark-domain.h>

namespace dnf5 {

using namespace libdnf5::cli;

void HistoryUndoCommand::set_argument_parser() {
get_argument_parser_command()->set_description("Revert all actions from the specified transactions");
get_argument_parser_command()->set_description("Revert all actions from the specified transaction");
transaction_specs = std::make_unique<TransactionSpecArguments>(*this, 1);
auto & ctx = get_context();
transaction_specs->get_arg()->set_complete_hook_func(create_history_id_autocomplete(ctx));
auto skip_unavailable = std::make_unique<SkipUnavailableOption>(*this);
ignore_extras = std::make_unique<IgnoreExtrasOption>(*this);
ignore_installed = std::make_unique<IgnoreInstalledOption>(*this);
}

void HistoryUndoCommand::run() {}
void HistoryUndoCommand::configure() {
auto & context = get_context();
context.set_load_system_repo(true);
context.set_load_available_repos(Context::LoadAvailableRepos::ENABLED);
}

void HistoryUndoCommand::run() {
auto ts_specs = transaction_specs->get_value();
libdnf5::transaction::TransactionHistory history(get_context().get_base());
std::vector<libdnf5::transaction::Transaction> target_trans;

target_trans = list_transactions_from_specs(history, ts_specs);

if (target_trans.size() < 1) {
throw libdnf5::cli::CommandExitError(1, M_("No matching transaction ID found, exactly one required."));
}

if (target_trans.size() > 1) {
throw libdnf5::cli::CommandExitError(1, M_("Matched more than one transaction ID, exactly one required."));
}

auto goal = get_context().get_goal();
// To enable removal of dependency packages not present in the selected transaction
// it requires allow_erasing. This will inform the user that additional removes
// are required and the transaction won't proceed without --ignore-extras.
goal->set_allow_erasing(true);

auto settings = libdnf5::GoalJobSettings();
settings.set_ignore_extras(ignore_extras->get_value());
settings.set_ignore_installed(ignore_installed->get_value());
goal->add_revert_transactions({target_trans}, settings);
}

} // namespace dnf5
7 changes: 7 additions & 0 deletions dnf5/commands/history/history_undo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#ifndef DNF5_COMMANDS_HISTORY_HISTORY_UNDO_HPP
#define DNF5_COMMANDS_HISTORY_HISTORY_UNDO_HPP

#include "commands/history/arguments.hpp"

#include <dnf5/context.hpp>


Expand All @@ -31,7 +33,12 @@ class HistoryUndoCommand : public Command {
public:
explicit HistoryUndoCommand(Context & context) : Command(context, "undo") {}
void set_argument_parser() override;
void configure() override;
void run() override;

std::unique_ptr<TransactionSpecArguments> transaction_specs{nullptr};
std::unique_ptr<libdnf5::cli::session::BoolOption> ignore_extras{nullptr};
std::unique_ptr<libdnf5::cli::session::BoolOption> ignore_installed{nullptr};
};


Expand Down
27 changes: 27 additions & 0 deletions dnf5/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,33 @@ static void print_resolve_hints(dnf5::Context & context) {
}
}

// hint --ignore-extras if there are unexpected extra packages in the transaction
if ((transaction_problems & libdnf5::GoalProblem::EXTRA) == libdnf5::GoalProblem::EXTRA) {
const std::string_view arg{"--ignore-extras"};
if (has_named_arg(command, arg.substr(2))) {
hints.emplace_back(libdnf5::utils::sformat(_("{} to allow extra packages in the transaction"), arg));
}
}

// hint --ignore-installed if there are mismatches between installed and stored transaction packages in replay
if ((transaction_problems & libdnf5::GoalProblem::NOT_INSTALLED) == libdnf5::GoalProblem::NOT_INSTALLED ||
(transaction_problems & libdnf5::GoalProblem::INSTALLED_IN_DIFFERENT_VERSION) ==
libdnf5::GoalProblem::INSTALLED_IN_DIFFERENT_VERSION) {
for (const auto & resolve_log : context.get_transaction()->get_resolve_logs()) {
if (goal_action_is_replay(resolve_log.get_action())) {
const std::string_view arg{"--ignore-installed"};
if (has_named_arg(command, arg.substr(2))) {
hints.emplace_back(libdnf5::utils::sformat(
_("{} to allow mismatches between installed and stored transaction packages. This can result "
"in an empty transaction because among other things the option can ignore failing Remove "
"actions."),
arg));
break;
}
}
}
}

if ((transaction_problems & libdnf5::GoalProblem::SOLVER_ERROR) == libdnf5::GoalProblem::SOLVER_ERROR) {
bool conflict = false;
bool broken_file_dep = false;
Expand Down
10 changes: 10 additions & 0 deletions include/libdnf5/base/goal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,16 @@ class Goal {
const std::filesystem::path & transaction_path,
const libdnf5::GoalJobSettings & settings = libdnf5::GoalJobSettings());

/// @warning This method is experimental/unstable and should not be relied on. It may be removed without warning
/// Add revert request of history transactions to the goal.
/// Can be called only once per Goal.
///
/// @param transactions A vector of history transactions to be reverted.
/// @param settings A structure to override default goal settings.
void add_revert_transactions(
const std::vector<libdnf5::transaction::Transaction> & transactions,
const libdnf5::GoalJobSettings & settings = libdnf5::GoalJobSettings());

/// When true it allows to remove installed packages to resolve dependency problems
void set_allow_erasing(bool value);

Expand Down
31 changes: 29 additions & 2 deletions include/libdnf5/base/goal_elements.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ enum class GoalProblem : uint32_t {
MODULE_SOLVER_ERROR_LATEST = (1 << 19),
/// Error detected during resolvement of module dependencies
MODULE_SOLVER_ERROR = (1 << 20),
MODULE_CANNOT_SWITH_STREAMS = (1 << 21)
MODULE_CANNOT_SWITH_STREAMS = (1 << 21),
/// Error when transaction contains additional unexpected elements.
/// Used when replaying transactions.
EXTRA = (1 << 22)
};

/// Types of Goal actions
Expand All @@ -137,12 +140,22 @@ enum class GoalAction {
REASON_CHANGE,
ENABLE,
DISABLE,
RESET
RESET,
REPLAY_INSTALL,
REPLAY_REMOVE,
REPLAY_UPGRADE,
REPLAY_REINSTALL,
REPLAY_REASON_CHANGE,
REPLAY_REASON_OVERRIDE,
REVERT_COMPS_UPGRADE
j-mracek marked this conversation as resolved.
Show resolved Hide resolved
};

/// Convert GoalAction enum to user-readable string
std::string goal_action_to_string(GoalAction action);

/// Check whether the action is a replay action
bool goal_action_is_replay(GoalAction action);
j-mracek marked this conversation as resolved.
Show resolved Hide resolved

/// Settings for GoalJobSettings
enum class GoalSetting { AUTO, SET_TRUE, SET_FALSE };

Expand Down Expand Up @@ -326,6 +339,20 @@ struct GoalJobSettings : public ResolveSpecSettings {
void set_to_repo_ids(std::vector<std::string> to_repo_ids);
std::vector<std::string> get_to_repo_ids() const;

/// If set to true, after resolving serialized or reverted transactions don't check for
/// extra packages pulled into the transaction.
///
/// Default: false
void set_ignore_extras(bool ignore_extras);
bool get_ignore_extras() const;

/// If set to true, after resolving serialized or reverted transactions don't check for
/// installed packages matching those in the transactions.
///
/// Default: false
void set_ignore_installed(bool ignore_installed);
bool get_ignore_installed() const;

private:
friend class Goal;

Expand Down
12 changes: 12 additions & 0 deletions include/libdnf5/transaction/transaction_history.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ class TransactionHistory {
/// @since 5.0
libdnf5::BaseWeakPtr get_base() const;

/// Get reason for package specified by name and arch at a point in history
/// specified by transaction id.
///
/// @param name Name of rpm package
/// @param arch Arch of rpm package
/// @param transaction_id_point Id of a history transaction (can be obtained from
/// libdnf5::transaction::TransactionHistory)
/// @return Reason of the last transaction item before transaction_id_point that
j-mracek marked this conversation as resolved.
Show resolved Hide resolved
/// has an rpm with matching name and arch.
TransactionItemReason transaction_item_reason_at(
const std::string & name, const std::string & arch, int64_t transaction_id_point);

private:
/// Create a new Transaction object.
libdnf5::transaction::Transaction new_transaction();
Expand Down
Loading
Loading