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 redo command #1595

Merged
merged 6 commits into from
Jul 31, 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
3 changes: 1 addition & 2 deletions dnf5.spec
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,7 @@ It supports RPM packages, modulemd modules, and comps groups & environments.
%{_mandir}/man8/dnf*-download.8.*
%{_mandir}/man8/dnf*-environment.8.*
%{_mandir}/man8/dnf*-group.8.*
# TODO(jkolarik): history is not ready yet
# %%{_mandir}/man8/dnf*-history.8.*
%{_mandir}/man8/dnf*-history.8.*
%{_mandir}/man8/dnf*-info.8.*
%{_mandir}/man8/dnf*-install.8.*
%{_mandir}/man8/dnf*-leaves.8.*
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() {
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<HistoryRedoCommand>(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
50 changes: 48 additions & 2 deletions dnf5/commands/history/history_redo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,60 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.

#include "history_redo.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 HistoryRedoCommand::set_argument_parser() {
get_argument_parser_command()->set_description("Repeat transactions");
get_argument_parser_command()->set_description("Repeat 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);
}

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

void HistoryRedoCommand::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(true);
settings.set_ignore_installed(true);
// If a package is already installed in the requested version but with a different reason override the reason.
// This is useful when redoing an interrupted transaction.
// The longest part of a transaction is running rpm changes, saving reasons is done after rpm finishes. If a transaction
// is interrupted in the middle of the rpm part some rpms are changed but reasons are not updated at all. To handle this
// enforce the reasons in the selected transaction.
settings.set_override_reasons(true);
goal->add_redo_transaction(target_trans[0], settings);
}

} // namespace dnf5
5 changes: 5 additions & 0 deletions dnf5/commands/history/history_redo.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_REDO_HPP
#define DNF5_COMMANDS_HISTORY_HISTORY_REDO_HPP

#include "commands/history/arguments.hpp"

#include <dnf5/context.hpp>


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

std::unique_ptr<TransactionSpecArguments> transaction_specs{nullptr};
};


Expand Down
3 changes: 1 addition & 2 deletions doc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ if(WITH_MAN)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/dnf5-download.8 DESTINATION share/man/man8)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/dnf5-environment.8 DESTINATION share/man/man8)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/dnf5-group.8 DESTINATION share/man/man8)
# TODO(jkolarik): history is not ready yet
# install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/dnf5-history.8 DESTINATION share/man/man8)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/dnf5-history.8 DESTINATION share/man/man8)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/dnf5-info.8 DESTINATION share/man/man8)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/dnf5-install.8 DESTINATION share/man/man8)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/dnf5-leaves.8 DESTINATION share/man/man8)
Expand Down
13 changes: 9 additions & 4 deletions doc/commands/history.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
You should have received a copy of the GNU General Public License
along with libdnf. If not, see <https://www.gnu.org/licenses/>.

..
# TODO(jkolarik): Command not ready yet in upstream ...

.. _history_command_ref-label:

Expand Down Expand Up @@ -54,6 +52,9 @@ Subcommands

``redo``
| Repeat the specified transaction.
| Automatically uses ``--ignore-extras`` and ``--ignore-installed``.
| Unlike the rest of history commands it overrides reasons for transaction packages that are already installed.
| This command is useful to finish interrupted transactons.

``rollback``
| Undo all transactions performed after the specified transaction.
Expand All @@ -69,12 +70,16 @@ Options for ``list`` and ``info``
| Reverse the order of transactions in the output.


Options for ``undo``
====================
Options for ``undo``, ``rollback`` and ``redo``
===============================================

``--skip-unavailable``
| Allow skipping packages actions that are not possible perform.


Options for ``undo`` and ``rollback``
=====================================

``--ignore-extras``
| Don't consider extra packages pulled into the transaction as errors.
| They will still be reported as warnings.
Expand Down
3 changes: 1 addition & 2 deletions doc/commands/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ DNF5 Commands
download.8
environment.8
group.8
history.8
info.8
install.8
leaves.8
Expand All @@ -37,5 +38,3 @@ DNF5 Commands
versionlock.8

..
# TODO(jkolarik): history not ready yet
history.8
2 changes: 1 addition & 1 deletion doc/conf.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ man_pages = [
('commands/download.8', 'dnf5-download', 'Download Command', AUTHORS, 8),
('commands/environment.8', 'dnf5-environment', 'Environment Command', AUTHORS, 8),
('commands/group.8', 'dnf5-group', 'Group Command', AUTHORS, 8),
# TODO(jkolarik): ('commands/history.8', 'dnf5-history', 'History Command', AUTHORS, 8),
('commands/history.8', 'dnf5-history', 'History Command', AUTHORS, 8),
('commands/info.8', 'dnf5-info', 'Info Command', AUTHORS, 8),
('commands/install.8', 'dnf5-install', 'Install Command', AUTHORS, 8),
('commands/leaves.8', 'dnf5-leaves', 'Leaves Command', AUTHORS, 8),
Expand Down
12 changes: 4 additions & 8 deletions doc/dnf5.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ For more details see the separate man page for the specific command, f.e. ``man
:ref:`group <group_command_ref-label>`
| Manage comps groups.

:ref:`history <history_command_ref-label>`
| Manage transaction history.

:ref:`info <info_command_ref-label>`
| Provide detailed information about installed or available packages.

Expand Down Expand Up @@ -136,11 +139,6 @@ For more details see the separate man page for the specific command, f.e. ``man
:ref:`versionlock <versionlock_command_ref-label>`
| Protect packages from updates to newer versions.

..
# TODO(jkolarik): History command is not ready yet
:ref:`history <history_command_ref-label>`
| Manage transaction history.


Plugin commands
---------------
Expand Down Expand Up @@ -417,6 +415,7 @@ Commands:
| :manpage:`dnf5-download(8)`, :ref:`Download command <download_command_ref-label>`
| :manpage:`dnf5-environment(8)`, :ref:`Environment command <environment_command_ref-label>`
| :manpage:`dnf5-group(8)`, :ref:`Group command <group_command_ref-label>`
| :manpage:`dnf5-history(8)`, :ref:`History command, <history_command_ref-label>`
| :manpage:`dnf5-info(8)`, :ref:`Info command <info_command_ref-label>`
| :manpage:`dnf5-install(8)`, :ref:`Install command <install_command_ref-label>`
| :manpage:`dnf5-leaves(8)`, :ref:`Leaves command <leaves_command_ref-label>`
Expand All @@ -436,9 +435,6 @@ Commands:
| :manpage:`dnf5-upgrade(8)`, :ref:`Upgrade command <upgrade_command_ref-label>`
| :manpage:`dnf5-versionlock(8)`, :ref:`Versionlock command <versionlock_command_ref-label>`

..
# TODO(jkolarik): History command is not ready yet
| :manpage:`dnf5-history(8)`, :ref:`History command, <history_command_ref-label>`

Application Plugins:
| :manpage:`dnf5-automatic(8)`, :ref:`Automatic command <automatic_plugin_ref-label>`
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 @@ -384,6 +384,16 @@ class LIBDNF_API Goal {
const std::vector<libdnf5::transaction::Transaction> & transactions,
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 redo request of history transaction to the goal.
/// Can be called only once per Goal.
///
/// @param transaction A history transaction to be repeated.
/// @param settings A structure to override default goal settings.
void add_redo_transaction(
const libdnf5::transaction::Transaction & transaction,
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
13 changes: 11 additions & 2 deletions include/libdnf5/base/goal_elements.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,20 +349,29 @@ struct LIBDNF_API 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
/// If set to true, after resolving serialized, reverted or redo 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
/// If set to true, after resolving serialized, reverted or redo 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;

/// If set to true, after resolving serialized, reverted or redo transactions override reasons
/// of already installed packages.
/// This option only has an effect if ignore_installed is set otherwise the transaction fails
/// when it contains already installed packages.
///
/// Default: false
void set_override_reasons(bool override_reasons);
bool get_override_reasons() const;

private:
friend class Goal;

Expand Down
10 changes: 5 additions & 5 deletions include/libdnf5/transaction/comps_group.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ class LIBDNF_API CompsGroup : public TransactionItem {
// @replaces libdnf:transaction/CompsGroupItem.hpp:method:CompsGroupItem.toStr()
std::string to_string() const;

/// Get types of the packages to be installed with the group (related xml elements: `<comps><group><packagelist><packagereq type="VALUE" ...>`)
///
// @replaces libdnf:transaction/CompsGroupItem.hpp:method:CompsGroupItem.getPackageTypes()
libdnf5::comps::PackageType get_package_types() const noexcept;

CompsGroup(const CompsGroup & src);
CompsGroup & operator=(const CompsGroup & src);
CompsGroup(CompsGroup && src) noexcept;
Expand Down Expand Up @@ -92,11 +97,6 @@ class LIBDNF_API CompsGroup : public TransactionItem {
// @replaces libdnf:transaction/CompsGroupItem.hpp:method:CompsGroupItem.setTranslatedName(const std::string & value)
LIBDNF_LOCAL void set_translated_name(const std::string & value);

/// Get types of the packages to be installed with the group (related xml elements: `<comps><group><packagelist><packagereq type="VALUE" ...>`)
///
// @replaces libdnf:transaction/CompsGroupItem.hpp:method:CompsGroupItem.getPackageTypes()
LIBDNF_LOCAL libdnf5::comps::PackageType get_package_types() const noexcept;

/// Set types of the packages to be installed with the group (related xml elements: `<comps><group><packagelist><packagereq type="VALUE" ...>`)
///
// @replaces libdnf:transaction/CompsGroupItem.hpp:method:CompsGroupItem.setPackageTypes(libdnf::CompsPackageType value)
Expand Down
37 changes: 34 additions & 3 deletions libdnf5/base/goal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class Goal::Impl {
GoalProblem add_reason_change_specs_to_goal(base::Transaction & transaction);

GoalProblem resolve_reverted_transactions(base::Transaction & transaction);
GoalProblem resolve_redo_transaction(base::Transaction & transaction);

std::pair<GoalProblem, libdnf5::solv::IdQueue> add_install_to_goal(
base::Transaction & transaction, GoalAction action, const std::string & spec, GoalJobSettings & settings);
Expand Down Expand Up @@ -271,6 +272,7 @@ class Goal::Impl {
std::unique_ptr<std::tuple<std::filesystem::path, GoalJobSettings>> serialized_transaction;

std::unique_ptr<std::tuple<std::vector<transaction::Transaction>, GoalJobSettings>> revert_transactions;
std::unique_ptr<std::tuple<transaction::Transaction, GoalJobSettings>> redo_transaction;
};

Goal::Goal(const BaseWeakPtr & base) : p_impl(new Impl(base)) {}
Expand Down Expand Up @@ -857,8 +859,21 @@ GoalProblem Goal::Impl::add_replay_to_goal(

if (!query_na.empty() && is_installonly.empty()) {
query_nevra.filter_installed();
auto problem = query_nevra.empty() ? GoalProblem::INSTALLED_IN_DIFFERENT_VERSION
: GoalProblem::ALREADY_INSTALLED;
auto problem = GoalProblem::INSTALLED_IN_DIFFERENT_VERSION;

if (!query_nevra.empty()) {
problem = GoalProblem::ALREADY_INSTALLED;
if (settings.get_override_reasons()) {
if ((*query_nevra.begin()).get_reason() != package_replay.reason) {
rpm_reason_change_specs.push_back(std::make_tuple(
package_replay.reason,
package_replay.nevra,
package_replay.group_id,
settings_per_package));
}
}
}

auto log_level = libdnf5::Logger::Level::WARNING;
if (!settings.get_ignore_installed()) {
log_level = libdnf5::Logger::Level::ERROR;
Expand All @@ -873,7 +888,9 @@ GoalProblem Goal::Impl::add_replay_to_goal(
package_replay.nevra,
query_to_vec_of_nevra_str(query_na),
log_level);
continue;
if (problem == GoalProblem::ALREADY_INSTALLED) {
continue;
}
}
}
}
Expand Down Expand Up @@ -2950,6 +2967,13 @@ GoalProblem Goal::Impl::resolve_reverted_transactions(base::Transaction & transa
return ret;
}

GoalProblem Goal::Impl::resolve_redo_transaction(base::Transaction & transaction) {
if (!redo_transaction) {
return GoalProblem::NO_PROBLEM;
}
auto & [trans, settings] = *redo_transaction;
return add_replay_to_goal(transaction, transaction::to_replay(trans), settings);
}

void Goal::Impl::add_paths_to_goal() {
if (rpm_filepaths.empty()) {
Expand Down Expand Up @@ -3093,6 +3117,7 @@ base::Transaction Goal::resolve() {
// Both serialized and reverted transactions use TransactionReplay.
ret |= p_impl->add_serialized_transaction_to_goal(transaction);
ret |= p_impl->resolve_reverted_transactions(transaction);
ret |= p_impl->resolve_redo_transaction(transaction);

p_impl->add_paths_to_goal();

Expand Down Expand Up @@ -3261,6 +3286,12 @@ void Goal::add_revert_transactions(
std::make_unique<std::tuple<std::vector<transaction::Transaction>, GoalJobSettings>>(transactions, settings);
}

void Goal::add_redo_transaction(
const libdnf5::transaction::Transaction & transaction, const libdnf5::GoalJobSettings & settings) {
libdnf_user_assert(!p_impl->redo_transaction, "Redo transactions cannot be set multiple times.");
p_impl->redo_transaction =
std::make_unique<std::tuple<transaction::Transaction, GoalJobSettings>>(transaction, settings);
}

void Goal::reset() {
p_impl->module_specs.clear();
Expand Down
11 changes: 11 additions & 0 deletions libdnf5/base/goal_elements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ class GoalJobSettings::Impl {
/// Used by history undo, system upgrade, ...
bool ignore_installed{false};

/// For replaying transactions override reasons for already installed packages.
/// Used by history redo.
bool override_reasons{false};

GoalUsedSetting used_skip_broken{GoalUsedSetting::UNUSED};
GoalUsedSetting used_skip_unavailable{GoalUsedSetting::UNUSED};
GoalUsedSetting used_best{GoalUsedSetting::UNUSED};
Expand Down Expand Up @@ -518,4 +522,11 @@ bool GoalJobSettings::get_ignore_installed() const {
return p_impl->ignore_installed;
}

void GoalJobSettings::set_override_reasons(bool override_reasons) {
p_impl->override_reasons = override_reasons;
}
bool GoalJobSettings::get_override_reasons() const {
return p_impl->override_reasons;
}

} // namespace libdnf5
Loading
Loading