From 391f991c3a6cc5182208d6762b78287f8e4e9448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= Date: Tue, 2 Jul 2024 14:27:19 +0200 Subject: [PATCH 1/4] Add `MERGE` goal action and `MERGE_ERROR` goal problem This will be used in transaction merging. --- include/libdnf5/base/goal_elements.hpp | 6 ++++-- libdnf5/base/goal.cpp | 3 +++ libdnf5/base/goal_elements.cpp | 2 ++ libdnf5/base/log_event.cpp | 3 +++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/libdnf5/base/goal_elements.hpp b/include/libdnf5/base/goal_elements.hpp index cb02a4054..36c4e04fb 100644 --- a/include/libdnf5/base/goal_elements.hpp +++ b/include/libdnf5/base/goal_elements.hpp @@ -124,7 +124,8 @@ enum class GoalProblem : uint32_t { EXTRA = (1 << 22), MALFORMED = (1 << 23), NOT_FOUND_DEBUGINFO = (1 << 24), - NOT_FOUND_DEBUGSOURCE = (1 << 25) + NOT_FOUND_DEBUGSOURCE = (1 << 25), + MERGE_ERROR = (1 << 26) }; /// Types of Goal actions @@ -155,7 +156,8 @@ enum class GoalAction { REPLAY_REASON_CHANGE, REPLAY_REASON_OVERRIDE, REVERT_COMPS_UPGRADE, - INSTALL_DEBUG + INSTALL_DEBUG, + MERGE }; /// Convert GoalAction enum to user-readable string diff --git a/libdnf5/base/goal.cpp b/libdnf5/base/goal.cpp index 4ba91280b..f046c3cf3 100644 --- a/libdnf5/base/goal.cpp +++ b/libdnf5/base/goal.cpp @@ -670,6 +670,9 @@ GoalProblem Goal::Impl::add_specs_to_goal(base::Transaction & transaction) { case GoalAction::REVERT_COMPS_UPGRADE: { libdnf_throw_assertion("Unsupported action \"REVERT_COMPS_UPGRADE\""); } + case GoalAction::MERGE: { + libdnf_throw_assertion("Unsupported action \"MERGE\""); + } } } return ret; diff --git a/libdnf5/base/goal_elements.cpp b/libdnf5/base/goal_elements.cpp index 9d4461a84..d8e0be8b8 100644 --- a/libdnf5/base/goal_elements.cpp +++ b/libdnf5/base/goal_elements.cpp @@ -398,6 +398,8 @@ std::string goal_action_to_string(GoalAction action) { return _("Revert comps upgrade"); case GoalAction::INSTALL_DEBUG: return _("Install debug RPMs"); + case GoalAction::MERGE: + return _("Transaction merge"); } return ""; } diff --git a/libdnf5/base/log_event.cpp b/libdnf5/base/log_event.cpp index d271ae1ff..f9416fd3a 100644 --- a/libdnf5/base/log_event.cpp +++ b/libdnf5/base/log_event.cpp @@ -361,6 +361,9 @@ std::string LogEvent::to_string( utils::string::join( additional_data, C_("String for joining NEVRAs - e.g. `foo-4-4.noarch, bar-5-5.noarch`", ", ")))); } + case GoalProblem::MERGE_ERROR: { + return ret.append(utils::sformat(_("Transaction merge error: '{0}'"), *additional_data.begin())); + } } return ret; } From 2a4c70aae414d516d0541bf045dfbab677559bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= Date: Mon, 24 Jun 2024 11:37:32 +0200 Subject: [PATCH 2/4] Add private `merge_transactions(...)` API It will be used by goal in `resolve_reverted_transactions(...)`. --- libdnf5/transaction/transaction_merge.cpp | 334 ++++ libdnf5/transaction/transaction_merge.hpp | 42 + .../transaction/test_transaction_merge.cpp | 1521 +++++++++++++++++ .../transaction/test_transaction_merge.hpp | 202 +++ test/shared/utils.hpp | 58 + 5 files changed, 2157 insertions(+) create mode 100644 libdnf5/transaction/transaction_merge.cpp create mode 100644 libdnf5/transaction/transaction_merge.hpp create mode 100644 test/libdnf5/transaction/test_transaction_merge.cpp create mode 100644 test/libdnf5/transaction/test_transaction_merge.hpp diff --git a/libdnf5/transaction/transaction_merge.cpp b/libdnf5/transaction/transaction_merge.cpp new file mode 100644 index 000000000..e83992cb6 --- /dev/null +++ b/libdnf5/transaction/transaction_merge.cpp @@ -0,0 +1,334 @@ +/* +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 . +*/ + + +#include "transaction_merge.hpp" + +#include "transaction/transaction_sr.hpp" +#include "utils/string.hpp" + +#include "libdnf5/rpm/nevra.hpp" +#include "libdnf5/utils/bgettext/bgettext-lib.h" +#include "libdnf5/utils/bgettext/bgettext-mark-domain.h" +#include "libdnf5/utils/format.hpp" + +#include + +namespace libdnf5::transaction { + +class TransactionMergeError : public Error { +public: + using Error::Error; + const char * get_domain_name() const noexcept override { return "libdnf5::transaction"; } + const char * get_name() const noexcept override { return "TransactionMergeError"; } +}; + +static void insert_or_append( + std::unordered_map> & map, + const std::string name_arch, + const std::string & nevra) { + if (map.contains(name_arch)) { + map[name_arch].push_back(nevra); + } else { + map[name_arch] = {nevra}; + } +} + +static void remove( + std::unordered_map> & map, + const std::string name_arch, + const std::string & nevra) { + auto itr = std::find(map[name_arch].begin(), map[name_arch].end(), nevra); + if (itr != map[name_arch].end()) { + map[name_arch].erase(itr); + } +} + +static bool contains( + std::unordered_map> & map, + const std::string name_arch, + const std::string & nevra) { + if (!map.contains(name_arch)) { + return false; + } + auto itr = std::find(map[name_arch].begin(), map[name_arch].end(), nevra); + if (itr != map[name_arch].end()) { + return true; + } else { + return false; + } +} + +template +void merge_comps_actions( + const TypeReplay & replay, + const std::string & item_id, + std::unordered_map & id_to_replay, + std::vector & problems) { + auto previous_replay = id_to_replay.find(item_id); + if (previous_replay != id_to_replay.end()) { + // comps have only install/upgrade and remove + if (transaction_item_action_is_inbound(replay.action) && + transaction_item_action_is_inbound(previous_replay->second.action)) { + // Install action needs to propagate forward because if the group isn't + // installed at the beginning other inbound actions cannot be applied. + if (previous_replay->second.action == TransactionItemAction::INSTALL) { + id_to_replay[item_id] = replay; + id_to_replay[item_id].action = TransactionItemAction::INSTALL; + } else { + id_to_replay[item_id] = replay; + } + } else if ( + transaction_item_action_is_outbound(replay.action) && + transaction_item_action_is_outbound(previous_replay->second.action)) { + // When both actions for id are outbound its an error. + problems.push_back(utils::sformat( + _("Action '{0}' '{1}' cannot be merged after it was '{2}' in " + "preceding transactions -> setting '{0}'."), + transaction_item_action_to_string(replay.action), + item_id, + transaction_item_action_to_string(previous_replay->second.action))); + id_to_replay[item_id] = replay; + } else { + // One is inbound and the other is outbound + if (previous_replay->second.action == TransactionItemAction::UPGRADE) { + // keep the new action + id_to_replay[item_id] = replay; + } else if (replay.action == TransactionItemAction::UPGRADE) { + // cannot upgrade removed group + problems.push_back(utils::sformat( + _("Action 'Upgrade' '{0}' cannot be merged because it is not present " + "at that point -> setting 'Install'."), + item_id)); + id_to_replay[item_id] = replay; + id_to_replay[item_id].action = TransactionItemAction::INSTALL; + } else { + // actions cancel out + id_to_replay.erase(item_id); + } + } + } else { + id_to_replay[item_id] = replay; + } +} + +std::tuple> merge_transactions( + std::vector transactions, + std::unordered_map> & na_to_installed_nevras, + std::vector installonly_names) { + TransactionReplay merged; + std::vector problems; + + std::unordered_map nevra_to_package_replay; + std::unordered_set removed_this_transaction; + std::unordered_map id_to_group_replay; + std::unordered_map id_to_env_replay; + + for (auto & trans : transactions) { + // Sort outbound actions first to handle them first, this avoid problems when for example a-1 is installed and + // we have a transaction with a-2 UPGRADE and a-1 REPLACED. If we were to handle the upgrade action first we + // would have two inbound action for particular non-installonly name-arch. + std::ranges::sort(trans.packages, [](const PackageReplay & a, const PackageReplay & b) { + return transaction_item_action_is_outbound(a.action) && transaction_item_action_is_inbound(b.action); + }); + + for (const auto & package_replay : trans.packages) { + const auto nevras = libdnf5::rpm::Nevra::parse(package_replay.nevra, {rpm::Nevra::Form::NEVRA}); + libdnf_assert( + nevras.size() == 1, + "Cannot parse rpm nevra or ambiguous \"{}\" while merging transaction.", + package_replay.nevra); + + const auto package_replay_name = nevras[0].get_name(); + const auto package_replay_arch = nevras[0].get_arch(); + const auto name_arch = package_replay_name + "." + package_replay_arch; + + auto previous_replay = nevra_to_package_replay.find(package_replay.nevra); + + // When the previous action is REASON_CHANGE or REINSTALL override it (act as if its not there), + // new reason is already present in the current package_replay. + if (previous_replay != nevra_to_package_replay.end() && + previous_replay->second.action != TransactionItemAction::REASON_CHANGE && + previous_replay->second.action != TransactionItemAction::REINSTALL) { + if (package_replay.action == TransactionItemAction::REINSTALL) { + if (contains(na_to_installed_nevras, name_arch, package_replay.nevra)) { + continue; + } else { + problems.push_back(utils::sformat( + _("Action 'Reinstall' '{0}' cannot be merged because it is not " + "present at that point -> setting 'Install'."), + package_replay.nevra)); + insert_or_append(na_to_installed_nevras, name_arch, package_replay.nevra); + nevra_to_package_replay[package_replay.nevra] = package_replay; + nevra_to_package_replay[package_replay.nevra].action = TransactionItemAction::INSTALL; + continue; + } + } + if (package_replay.action == TransactionItemAction::REASON_CHANGE) { + previous_replay->second.reason = package_replay.reason; + continue; + } + + if ((transaction_item_action_is_inbound(package_replay.action) && + transaction_item_action_is_inbound(previous_replay->second.action)) || + (transaction_item_action_is_outbound(package_replay.action) && + transaction_item_action_is_outbound(previous_replay->second.action))) { + // When both actions for nevra are inbound (REINSTALL excluded) or outbound use newer one and log it + problems.push_back(utils::sformat( + _("Action '{0}' '{1}' cannot be merged after it was " + "'{2}' in preceding transaction -> setting '{0}'."), + transaction_item_action_to_string(package_replay.action), + package_replay.nevra, + transaction_item_action_to_string(previous_replay->second.action))); + nevra_to_package_replay[package_replay.nevra] = package_replay; + } else if ( + (transaction_item_action_is_inbound(package_replay.action) && + transaction_item_action_is_outbound(previous_replay->second.action)) || + (transaction_item_action_is_outbound(package_replay.action) && + transaction_item_action_is_inbound(previous_replay->second.action))) { + // Actions cancel out + nevra_to_package_replay.erase(package_replay.nevra); + if (transaction_item_action_is_inbound(package_replay.action)) { + insert_or_append(na_to_installed_nevras, name_arch, package_replay.nevra); + } else { + removed_this_transaction.insert(name_arch); + remove(na_to_installed_nevras, name_arch, package_replay.nevra); + } + } else { + throw TransactionMergeError( + M_("Unexpected action encountered: '{0}' during transaction merge."), + transaction_item_action_to_string(package_replay.action)); + } + } else { + if (!na_to_installed_nevras.contains(name_arch) || + na_to_installed_nevras[name_arch].empty()) { // This name.arch is not installed at all + if (transaction_item_action_is_outbound(package_replay.action) || + package_replay.action == TransactionItemAction::REASON_CHANGE) { + problems.push_back(utils::sformat( + _("Action '{0}' '{1}' cannot be merged because it is not present at that point -> " + "skipping it."), + transaction_item_action_to_string(package_replay.action), + package_replay.nevra)); + continue; + } else if (package_replay.action == TransactionItemAction::REINSTALL) { + problems.push_back(utils::sformat( + _("Action 'Reinstall' '{0}' cannot be merged because it is not " + "present at that point -> setting 'Install'."), + package_replay.nevra)); + nevra_to_package_replay[package_replay.nevra] = package_replay; + nevra_to_package_replay[package_replay.nevra].action = TransactionItemAction::INSTALL; + continue; + } + // For a given name.arch install action needs to propagate forward because if + // the package isn't installed at the beginning other actions cannot be applied. + nevra_to_package_replay[package_replay.nevra] = package_replay; + if (package_replay.action != TransactionItemAction::INSTALL) { + // If some other version of this name_arch isn't removed in this transaction it's an invalid transaction merge + if (!removed_this_transaction.contains(name_arch)) { + problems.push_back(utils::sformat( + _("Action '{0}' '{1}' cannot be merged because it is not present at that point -> " + "setting 'INSTALL'."), + transaction_item_action_to_string(package_replay.action), + package_replay.nevra)); + } + nevra_to_package_replay[package_replay.nevra].action = TransactionItemAction::INSTALL; + } + insert_or_append(na_to_installed_nevras, name_arch, package_replay.nevra); + } else { // This name.arch is already installed is some version + if (transaction_item_action_is_outbound(package_replay.action) || + package_replay.action == TransactionItemAction::REASON_CHANGE || + package_replay.action == TransactionItemAction::REINSTALL) { + if (contains(na_to_installed_nevras, name_arch, package_replay.nevra)) { + if (package_replay.action != TransactionItemAction::REASON_CHANGE && + package_replay.action != TransactionItemAction::REINSTALL) { + remove(na_to_installed_nevras, name_arch, package_replay.nevra); + removed_this_transaction.insert(name_arch); + } + nevra_to_package_replay[package_replay.nevra] = package_replay; + } else { + problems.push_back(utils::sformat( + _("Action '{0}' '{1}' cannot be merged because it is not present at that point " + "(present versions are: {2}) -> skipping it."), + transaction_item_action_to_string(package_replay.action), + package_replay.nevra, + libdnf5::utils::string::join(na_to_installed_nevras[name_arch], ","))); + continue; + } + } else if (transaction_item_action_is_inbound(package_replay.action)) { + // If this nevra is already installed skip this action + if (contains(na_to_installed_nevras, name_arch, package_replay.nevra)) { + problems.push_back(utils::sformat( + _("Action '{0}' '{1}' cannot be merged because it is already present at that point -> " + "skipping it."), + transaction_item_action_to_string(package_replay.action), + package_replay.nevra)); + continue; + } + if (std::find(installonly_names.begin(), installonly_names.end(), package_replay_name) != + installonly_names.end()) { + // This package is installonly, multiple versions can be installed + nevra_to_package_replay[package_replay.nevra] = package_replay; + insert_or_append(na_to_installed_nevras, name_arch, package_replay.nevra); + } else { + // Not installonly, keep only the latest action + nevra_to_package_replay.erase(na_to_installed_nevras[name_arch][0]); + nevra_to_package_replay[package_replay.nevra] = package_replay; + if (package_replay.action == TransactionItemAction::INSTALL) { + problems.push_back(utils::sformat( + _("Action '{0}' '{1}' cannot be merged because it is already installed in version " + "'{2}' -> keeping the action from older transaction with '{1}'."), + transaction_item_action_to_string(package_replay.action), + package_replay.nevra, + na_to_installed_nevras[name_arch][0])); + } + remove(na_to_installed_nevras, name_arch, package_replay.nevra); + } + } else { + throw TransactionMergeError( + M_("Invalid action encountered: '{0}' during transaction merge."), + transaction_item_action_to_string(package_replay.action)); + } + } + } + } + + for (const auto & group_replay : trans.groups) { + merge_comps_actions(group_replay, group_replay.group_id, id_to_group_replay, problems); + } + + for (const auto & env_replay : trans.environments) { + merge_comps_actions(env_replay, env_replay.environment_id, id_to_env_replay, problems); + } + removed_this_transaction.clear(); + } + + for (const auto & n : nevra_to_package_replay) { + merged.packages.push_back(n.second); + } + for (const auto & n : id_to_group_replay) { + merged.groups.push_back(n.second); + } + for (const auto & n : id_to_env_replay) { + merged.environments.push_back(n.second); + } + + return {merged, problems}; +} + +} // namespace libdnf5::transaction diff --git a/libdnf5/transaction/transaction_merge.hpp b/libdnf5/transaction/transaction_merge.hpp new file mode 100644 index 000000000..22f1ec4ed --- /dev/null +++ b/libdnf5/transaction/transaction_merge.hpp @@ -0,0 +1,42 @@ +/* +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 . +*/ + +#ifndef LIBDNF5_TRANSACTION_TRANSACTION_MERGE_HPP +#define LIBDNF5_TRANSACTION_TRANSACTION_MERGE_HPP + +#include "transaction_sr.hpp" + + +namespace libdnf5::transaction { + +// Merge a vector of transactions replays. +// Order matters when merging transactions, we prefer the latest transaction actions (actions from TransactionReplays later in the vector). +// The na_to_installed_nevras is a unordered_map of currently installed nevras, in format name.arch: {nevra1, nevra2..} the vector is +// necessary because of installonly packages. +// The last argument is a vector of names of installonly packages. +// +// It returns the merged transaction and a vector of encountered problems. +std::tuple> merge_transactions( + std::vector transactions, + std::unordered_map> & na_to_installed_nevras, + std::vector installonly_names = {}); + +} // namespace libdnf5::transaction + +#endif // LIBDNF5_TRANSACTION_TRANSACTION_MERGE_HPP diff --git a/test/libdnf5/transaction/test_transaction_merge.cpp b/test/libdnf5/transaction/test_transaction_merge.cpp new file mode 100644 index 000000000..e7f9d5e47 --- /dev/null +++ b/test/libdnf5/transaction/test_transaction_merge.cpp @@ -0,0 +1,1521 @@ +/* +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 General Public License as published by +the Free Software Foundation, either version 2 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + + +#include "test_transaction_merge.hpp" + +#include "../shared/utils.hpp" + +#include +#include + +using namespace libdnf5::transaction; + +CPPUNIT_TEST_SUITE_REGISTRATION(TransactionMergeTest); + +static void add_transaction_item_package( + TransactionReplay & trans, + TransactionItemAction action, + const std::string & nevra, + const TransactionItemReason reason = TransactionItemReason::USER) { + libdnf5::transaction::PackageReplay pkg_replay; + pkg_replay.nevra = nevra; + pkg_replay.action = action; + pkg_replay.reason = reason; + trans.packages.push_back(pkg_replay); +} + +static void add_transaction_item_group( + TransactionReplay & trans, TransactionItemAction action, const std::string & id) { + libdnf5::transaction::GroupReplay group_replay; + group_replay.action = action; + group_replay.reason = TransactionItemReason::USER; + group_replay.group_id = id; + trans.groups.push_back(group_replay); +} + +static void add_transaction_item_environment( + TransactionReplay & trans, TransactionItemAction action, const std::string & id) { + libdnf5::transaction::EnvironmentReplay env_replay; + env_replay.action = action; + env_replay.environment_id = id; + trans.environments.push_back(env_replay); +} + +static bool nevra_compare(PackageReplay pkg1, PackageReplay pkg2) { + return (pkg1.nevra > pkg2.nevra); +}; + +void TransactionMergeTest::empty_transaction() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans; + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans}, na_to_installed_nevras); + + CPPUNIT_ASSERT_EQUAL((size_t)0, replay.packages.size()); + CPPUNIT_ASSERT_EQUAL((size_t)0, replay.groups.size()); + CPPUNIT_ASSERT_EQUAL((size_t)0, replay.environments.size()); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::only_one_transaction() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans; + //bash-5.2.26-3.fc40.x86_64 + add_transaction_item_package(trans, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + add_transaction_item_package(trans, TransactionItemAction::INSTALL, "systemd-223-1.x86_64"); + add_transaction_item_package(trans, TransactionItemAction::INSTALL, "sysvinit-2-1.x86_64"); + add_transaction_item_group(trans, TransactionItemAction::INSTALL, "vlc"); + add_transaction_item_environment(trans, TransactionItemAction::INSTALL, "basic-desktop-environment"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans}, na_to_installed_nevras); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "sysvinit-2-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "systemd-223-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_groups = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "vlc", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected_groups, replay.groups); + std::vector expected_envs = { + {TransactionItemAction::INSTALL, "basic-desktop-environment", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected_envs, replay.environments); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::two_disjoint() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "systemd-223-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "sysvinit-2-1.x86_64"); + add_transaction_item_group(trans2, TransactionItemAction::INSTALL, "vlc"); + add_transaction_item_environment(trans2, TransactionItemAction::INSTALL, "basic-desktop-environment"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "sysvinit-2-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "systemd-223-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_groups = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "vlc", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected_groups, replay.groups); + std::vector expected_envs = { + {TransactionItemAction::INSTALL, "basic-desktop-environment", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected_envs, replay.environments); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_install() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Install' 'bash-5-1.x86_64' cannot be merged because it is already installed in version " + "'bash-4-1.x86_64' -> keeping the action from older transaction with 'bash-5-1.x86_64'."}; + + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); + + na_to_installed_nevras.clear(); + // Order matters when merging transacitons, we perfer the latest transaction + auto [replay2, problems2] = libdnf5::transaction::merge_transactions({trans2, trans1}, na_to_installed_nevras); + + std::vector expected2 = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected2, replay2.packages); + std::vector expected_problems2 = { + "Action 'Install' 'bash-4-1.x86_64' cannot be merged because it is already installed in version " + "'bash-5-1.x86_64' -> keeping the action from older transaction with 'bash-4-1.x86_64'."}; + + CPPUNIT_ASSERT_EQUAL(expected_problems2, problems2); +} + +void TransactionMergeTest::install_install2() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + {"Action 'Install' 'bash-4-1.x86_64' cannot be merged after it was 'Install' in " + "preceding transaction -> setting 'Install'."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::install_upgrade() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_downgrade() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::DOWNGRADE, "bash-3-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-3-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_reinstall() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_remove() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_replaced() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_reasonchange() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package( + trans2, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::DEPENDENCY, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::upgrade_install() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + {"Action 'Install' 'bash-5-1.x86_64' cannot be merged after it was 'Install' in " + "preceding transaction -> setting 'Install'."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + + +void TransactionMergeTest::upgrade_upgrade() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-5-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::UPGRADE, "bash-6-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-6-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::upgrade_upgrade2() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::UPGRADE, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + {"Action 'Replaced' 'bash-4-1.x86_64' cannot be merged after it was 'Replaced' in " + "preceding transaction -> setting 'Replaced'."}, + {"Action 'Upgrade' 'bash-5-1.x86_64' cannot be merged after it was 'Install' in " + "preceding transaction -> setting 'Upgrade'."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::upgrade_upgrade_installonly() { + std::unordered_map> na_to_installed_nevras{ + {"kernel.x86_64", {"kernel-4-1.x86_64", "kernel-8-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "kernel-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "kernel-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "kernel-8-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "kernel-9-1.x86_64"); + + auto [replay, problems] = + libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras, {"kernel"}); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-9-1.x86_64", "", ""}, + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "kernel-8-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-5-1.x86_64", "", ""}, + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "kernel-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::upgrade_downgrade() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-5-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::DOWNGRADE, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::upgrade_reinstall() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REINSTALL, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::upgrade_remove() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::upgrade_replaced() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::upgrade_reasonchange() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package( + trans2, TransactionItemAction::REASON_CHANGE, "bash-5-1.x86_64", TransactionItemReason::DEPENDENCY); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::DEPENDENCY, "", "bash-5-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::upgrade_missing() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1}, na_to_installed_nevras); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Replaced' 'bash-4-1.x86_64' cannot be merged because it is not present at that point -> skipping it.", + "Action 'Upgrade' 'bash-5-1.x86_64' cannot be merged because it is not present at that point -> setting " + "'INSTALL'."}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REINSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall_install() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REINSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + {"Action 'Install' 'bash-4-1.x86_64' cannot be merged because it is already present at " + "that point -> skipping it."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall_install2() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + {"Action 'Install' 'bash-5-1.x86_64' cannot be merged because it is already installed in version " + "'bash-4-1.x86_64' -> keeping the action from older transaction with 'bash-5-1.x86_64'."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall_upgrade() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall_downgrade() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-3-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-3-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall_reinstall() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REINSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall_remove() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall_replaced() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall_reasonchange() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package( + trans2, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REASON_CHANGE, TransactionItemReason::DEPENDENCY, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall_missing() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + {"Action 'Reinstall' 'bash-4-1.x86_64' cannot be merged because it is not present at " + "that point -> setting 'Install'."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reinstall_wrong_version() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-1-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1}, na_to_installed_nevras); + + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Reinstall' 'bash-4-1.x86_64' cannot be merged because it is not present at that point (present " + "versions are: bash-1-1.x86_64) -> skipping it."}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reasonchange() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package( + trans1, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REASON_CHANGE, TransactionItemReason::DEPENDENCY, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reasonchange_install() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package( + trans1, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Install' 'bash-5-1.x86_64' cannot be merged because it is already installed in version " + "'bash-4-1.x86_64' -> keeping the action from older transaction with 'bash-5-1.x86_64'."}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reasonchange_missing() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-1-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package( + trans1, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1}, na_to_installed_nevras); + + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Reason Change' 'bash-4-1.x86_64' cannot be merged because it is not present at that point (present " + "versions are: bash-1-1.x86_64) -> skipping it."}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reasonchange_upgrade() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package( + trans1, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + // reason change is overwritten by the reason in UPGRADE + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reasonchange_downgrade() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package( + trans1, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::DOWNGRADE, "bash-3-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + // reason change is overwritten by the reason in DOWNGRADE + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-3-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reasonchange_reinstall() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package( + trans1, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + // reason change is overwritten by the reason in REINSTALL + {TransactionItemAction::REINSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reasonchange_remove() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package( + trans1, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + // reason change is overwritten by the reason in remove + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reasonchange_replaced() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package( + trans1, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + // reason change is overwritten by the reason in REPLACED + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::reasonchange_reasonchange() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package( + trans1, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package( + trans2, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::WEAK_DEPENDENCY); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REASON_CHANGE, TransactionItemReason::WEAK_DEPENDENCY, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::remove_install() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::remove_upgrade() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Replaced' 'bash-4-1.x86_64' cannot be merged after it was 'Remove' in preceding transaction -> " + "setting 'Replaced'.", + "Action 'Upgrade' 'bash-5-1.x86_64' cannot be merged because it is not present at that point -> setting " + "'INSTALL'."}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::remove_downgrade() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::DOWNGRADE, "bash-3-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-3-1.x86_64", "", ""}, + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Replaced' 'bash-4-1.x86_64' cannot be merged after it was 'Remove' in preceding transaction -> " + "setting 'Replaced'.", + "Action 'Downgrade' 'bash-3-1.x86_64' cannot be merged because it is not present at that point -> setting " + "'INSTALL'."}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::remove_reinstall() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REINSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + {"Action 'Reinstall' 'bash-4-1.x86_64' cannot be merged because it is not present at " + "that point -> setting 'Install'."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::remove_remove() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + {"Action 'Remove' 'bash-4-1.x86_64' cannot be merged after it was 'Remove' in " + "preceding transaction -> setting 'Remove'."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::remove_remove_installonly() { + std::unordered_map> na_to_installed_nevras{ + {"kernel.x86_64", {"kernel-4-1.x86_64", "kernel-5-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "kernel-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "kernel-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "kernel-5-1.x86_64", "", ""}, + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "kernel-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::remove_replaced() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REPLACED, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + {"Action 'Replaced' 'bash-4-1.x86_64' cannot be merged after it was 'Remove' in " + "preceding transaction -> setting 'Replaced'."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::remove_reasonchange() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package( + trans2, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REMOVE, TransactionItemReason::DEPENDENCY, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = {}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::remove_missing() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-1-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1}, na_to_installed_nevras); + + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Remove' 'bash-4-1.x86_64' cannot be merged because it is not present at that point (present versions " + "are: bash-1-1.x86_64) -> skipping it."}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::install_upgrade_reinstall() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans3; + add_transaction_item_package(trans3, TransactionItemAction::REINSTALL, "bash-5-1.x86_64"); + + auto [replay, problems] = + libdnf5::transaction::merge_transactions({trans1, trans2, trans3}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::remove_install_remove() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans3; + add_transaction_item_package(trans3, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + + auto [replay, problems] = + libdnf5::transaction::merge_transactions({trans1, trans2, trans3}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_reasonchange_upgrade() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package( + trans2, TransactionItemAction::REASON_CHANGE, "bash-4-1.x86_64", TransactionItemReason::DEPENDENCY); + libdnf5::transaction::TransactionReplay trans3; + add_transaction_item_package(trans3, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + add_transaction_item_package(trans3, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + + auto [replay, problems] = + libdnf5::transaction::merge_transactions({trans1, trans2, trans3}, na_to_installed_nevras); + + std::vector expected = { + // the reason change is overwritten by the UPGRADE reason + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_remove_install() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans3; + add_transaction_item_package(trans3, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + + auto [replay, problems] = + libdnf5::transaction::merge_transactions({trans1, trans2, trans3}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_remove_upgrade() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans3; + add_transaction_item_package(trans3, TransactionItemAction::REMOVE, "bash-5-1.x86_64"); + + auto [replay, problems] = + libdnf5::transaction::merge_transactions({trans1, trans2, trans3}, na_to_installed_nevras); + + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Install' 'bash-5-1.x86_64' cannot be merged because it is already installed in version " + "'bash-4-1.x86_64' -> keeping the action from older transaction with 'bash-5-1.x86_64'."}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::install_install_remove() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans3; + add_transaction_item_package(trans3, TransactionItemAction::REPLACED, "bash-4-1.x86_64"); + add_transaction_item_package(trans3, TransactionItemAction::UPGRADE, "bash-5-1.x86_64"); + + auto [replay, problems] = + libdnf5::transaction::merge_transactions({trans1, trans2, trans3}, na_to_installed_nevras); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-5-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Replaced' 'bash-4-1.x86_64' cannot be merged because it is not present at that point -> skipping it.", + "Action 'Upgrade' 'bash-5-1.x86_64' cannot be merged because it is not present at that point -> setting " + "'INSTALL'."}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::remove_install_lower() { + std::unordered_map> na_to_installed_nevras{ + {"bash.x86_64", {"bash-4-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "bash-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "bash-3-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "bash-4-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "bash-3-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_install_installonly() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "kernel-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "kernel-5-1.x86_64"); + + auto [replay, problems] = + libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras, {"kernel"}); + + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-5-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::install_remove_install_installonly() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "kernel-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "kernel-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans3; + add_transaction_item_package(trans3, TransactionItemAction::REMOVE, "kernel-4-1.x86_64"); + add_transaction_item_package(trans3, TransactionItemAction::REMOVE, "kernel-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans4; + add_transaction_item_package(trans4, TransactionItemAction::INSTALL, "kernel-4-1.x86_64"); + add_transaction_item_package(trans4, TransactionItemAction::INSTALL, "kernel-5-1.x86_64"); + + auto [replay, problems] = + libdnf5::transaction::merge_transactions({trans1, trans2, trans3, trans4}, na_to_installed_nevras, {"kernel"}); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-5-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-4-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::remove_installonly_missing() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "kernel-4-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "kernel-5-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1}, na_to_installed_nevras, {"kernel"}); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + std::vector expected_problems = { + "Action 'Remove' 'kernel-4-1.x86_64' cannot be merged because it is not present at that point -> skipping it.", + "Action 'Remove' 'kernel-5-1.x86_64' cannot be merged because it is not present at that point -> skipping it."}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::install_remove_installonly() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "kernel-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "kernel-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans3; + add_transaction_item_package(trans3, TransactionItemAction::REMOVE, "kernel-4-1.x86_64"); + add_transaction_item_package(trans3, TransactionItemAction::REMOVE, "kernel-5-1.x86_64"); + + auto [replay, problems] = + libdnf5::transaction::merge_transactions({trans1, trans2, trans3}, na_to_installed_nevras, {"kernel"}); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::upgrade_installonly_forward() { + std::unordered_map> na_to_installed_nevras{ + {"kernel.x86_64", {"kernel-1-1.x86_64", "kernel-2-1.x86_64", "kernel-3-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "kernel-1-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "kernel-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "kernel-2-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "kernel-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans3; + add_transaction_item_package(trans3, TransactionItemAction::REMOVE, "kernel-3-1.x86_64"); + add_transaction_item_package(trans3, TransactionItemAction::INSTALL, "kernel-6-1.x86_64"); + libdnf5::transaction::TransactionReplay trans4; + add_transaction_item_package(trans4, TransactionItemAction::REMOVE, "kernel-4-1.x86_64"); + add_transaction_item_package(trans4, TransactionItemAction::INSTALL, "kernel-7-1.x86_64"); + libdnf5::transaction::TransactionReplay trans5; + add_transaction_item_package(trans5, TransactionItemAction::REMOVE, "kernel-5-1.x86_64"); + add_transaction_item_package(trans5, TransactionItemAction::INSTALL, "kernel-8-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions( + {trans1, trans2, trans3, trans4, trans5}, na_to_installed_nevras, {"kernel"}); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-8-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-7-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-6-1.x86_64", "", ""}, + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "kernel-3-1.x86_64", "", ""}, + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "kernel-2-1.x86_64", "", ""}, + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "kernel-1-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::upgrade_installonly_backward() { + std::unordered_map> na_to_installed_nevras{ + {"kernel.x86_64", {"kernel-7-1.x86_64", "kernel-8-1.x86_64", "kernel-9-1.x86_64"}}}; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_package(trans1, TransactionItemAction::REMOVE, "kernel-9-1.x86_64"); + add_transaction_item_package(trans1, TransactionItemAction::INSTALL, "kernel-6-1.x86_64"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_package(trans2, TransactionItemAction::REMOVE, "kernel-8-1.x86_64"); + add_transaction_item_package(trans2, TransactionItemAction::INSTALL, "kernel-5-1.x86_64"); + libdnf5::transaction::TransactionReplay trans3; + add_transaction_item_package(trans3, TransactionItemAction::REMOVE, "kernel-7-1.x86_64"); + add_transaction_item_package(trans3, TransactionItemAction::INSTALL, "kernel-4-1.x86_64"); + libdnf5::transaction::TransactionReplay trans4; + add_transaction_item_package(trans4, TransactionItemAction::REMOVE, "kernel-6-1.x86_64"); + add_transaction_item_package(trans4, TransactionItemAction::INSTALL, "kernel-3-1.x86_64"); + libdnf5::transaction::TransactionReplay trans5; + add_transaction_item_package(trans5, TransactionItemAction::REMOVE, "kernel-5-1.x86_64"); + add_transaction_item_package(trans5, TransactionItemAction::INSTALL, "kernel-2-1.x86_64"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions( + {trans1, trans2, trans3, trans4, trans5}, na_to_installed_nevras, {"kernel"}); + + std::sort(replay.packages.begin(), replay.packages.end(), nevra_compare); + std::vector expected = { + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "kernel-9-1.x86_64", "", ""}, + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "kernel-8-1.x86_64", "", ""}, + {TransactionItemAction::REMOVE, TransactionItemReason::USER, "", "kernel-7-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-4-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-3-1.x86_64", "", ""}, + {TransactionItemAction::INSTALL, TransactionItemReason::USER, "", "kernel-2-1.x86_64", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.packages); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::group_install_remove() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_group(trans1, TransactionItemAction::INSTALL, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_group(trans2, TransactionItemAction::REMOVE, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.groups); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + + +void TransactionMergeTest::group_install_install() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_group(trans1, TransactionItemAction::INSTALL, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_group(trans2, TransactionItemAction::INSTALL, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {{TransactionItemAction::INSTALL, TransactionItemReason::USER, "vlc", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.groups); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::group_install_upgrade() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_group(trans1, TransactionItemAction::INSTALL, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_group(trans2, TransactionItemAction::UPGRADE, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {{TransactionItemAction::INSTALL, TransactionItemReason::USER, "vlc", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.groups); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::group_remove_install() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_group(trans1, TransactionItemAction::REMOVE, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_group(trans2, TransactionItemAction::INSTALL, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.groups); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::group_remove_upgrade() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_group(trans1, TransactionItemAction::REMOVE, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_group(trans2, TransactionItemAction::UPGRADE, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {{TransactionItemAction::INSTALL, TransactionItemReason::USER, "vlc", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.groups); + std::vector expected_problems = { + {"Action 'Upgrade' 'vlc' cannot be merged because it is not present at that point -> " + "setting 'Install'."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::group_remove_remove() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_group(trans1, TransactionItemAction::REMOVE, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_group(trans2, TransactionItemAction::REMOVE, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {{TransactionItemAction::REMOVE, TransactionItemReason::USER, "vlc", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.groups); + std::vector expected_problems = { + {"Action 'Remove' 'vlc' cannot be merged after it was 'Remove' in preceding " + "transactions -> setting 'Remove'."}}; + CPPUNIT_ASSERT_EQUAL(expected_problems, problems); +} + +void TransactionMergeTest::group_upgrade_upgrade() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_group(trans1, TransactionItemAction::UPGRADE, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_group(trans2, TransactionItemAction::UPGRADE, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {{TransactionItemAction::UPGRADE, TransactionItemReason::USER, "vlc", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.groups); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::group_upgrade_remove() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_group(trans1, TransactionItemAction::UPGRADE, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_group(trans2, TransactionItemAction::REMOVE, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {{TransactionItemAction::REMOVE, TransactionItemReason::USER, "vlc", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.groups); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::group_upgrade_install() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_group(trans1, TransactionItemAction::UPGRADE, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_group(trans2, TransactionItemAction::INSTALL, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {{TransactionItemAction::INSTALL, TransactionItemReason::USER, "vlc", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.groups); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::env_install_remove() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_environment(trans1, TransactionItemAction::INSTALL, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_environment(trans2, TransactionItemAction::REMOVE, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {}; + CPPUNIT_ASSERT_EQUAL(expected, replay.environments); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} + +void TransactionMergeTest::env_upgrade_remove() { + std::unordered_map> na_to_installed_nevras; + + libdnf5::transaction::TransactionReplay trans1; + add_transaction_item_environment(trans1, TransactionItemAction::UPGRADE, "vlc"); + libdnf5::transaction::TransactionReplay trans2; + add_transaction_item_environment(trans2, TransactionItemAction::REMOVE, "vlc"); + + auto [replay, problems] = libdnf5::transaction::merge_transactions({trans1, trans2}, na_to_installed_nevras); + + std::vector expected = {{TransactionItemAction::REMOVE, "vlc", "", ""}}; + CPPUNIT_ASSERT_EQUAL(expected, replay.environments); + CPPUNIT_ASSERT_EQUAL(std::vector(), problems); +} diff --git a/test/libdnf5/transaction/test_transaction_merge.hpp b/test/libdnf5/transaction/test_transaction_merge.hpp new file mode 100644 index 000000000..8d7a9d869 --- /dev/null +++ b/test/libdnf5/transaction/test_transaction_merge.hpp @@ -0,0 +1,202 @@ +/* +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 General Public License as published by +the Free Software Foundation, either version 2 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + + +#ifndef TEST_LIBDNF5_TRANSACTION_TEST_TRANSACTION_MERGE_HPP +#define TEST_LIBDNF5_TRANSACTION_TEST_TRANSACTION_MERGE_HPP + + +#include "transaction_test_base.hpp" + +#include +#include + + +class TransactionMergeTest : public TransactionTestBase { + CPPUNIT_TEST_SUITE(TransactionMergeTest); + CPPUNIT_TEST(empty_transaction); + CPPUNIT_TEST(only_one_transaction); + CPPUNIT_TEST(two_disjoint); + + CPPUNIT_TEST(install_install); + CPPUNIT_TEST(install_install2); + CPPUNIT_TEST(install_upgrade); + CPPUNIT_TEST(install_downgrade); + CPPUNIT_TEST(install_reinstall); + CPPUNIT_TEST(install_remove); + CPPUNIT_TEST(install_replaced); + CPPUNIT_TEST(install_reasonchange); + + CPPUNIT_TEST(upgrade_install); + CPPUNIT_TEST(upgrade_upgrade); + CPPUNIT_TEST(upgrade_upgrade2); + CPPUNIT_TEST(upgrade_upgrade_installonly); + CPPUNIT_TEST(upgrade_downgrade); + CPPUNIT_TEST(upgrade_reinstall); + CPPUNIT_TEST(upgrade_remove); + CPPUNIT_TEST(upgrade_replaced); + CPPUNIT_TEST(upgrade_reasonchange); + CPPUNIT_TEST(upgrade_missing); + + CPPUNIT_TEST(remove_install); + CPPUNIT_TEST(remove_upgrade); + CPPUNIT_TEST(remove_downgrade); + CPPUNIT_TEST(remove_reinstall); + CPPUNIT_TEST(remove_remove); + CPPUNIT_TEST(remove_remove_installonly); + CPPUNIT_TEST(remove_replaced); + CPPUNIT_TEST(remove_reasonchange); + CPPUNIT_TEST(remove_missing); + + CPPUNIT_TEST(reinstall); + CPPUNIT_TEST(reinstall_install); + CPPUNIT_TEST(reinstall_install2); + CPPUNIT_TEST(reinstall_upgrade); + CPPUNIT_TEST(reinstall_downgrade); + CPPUNIT_TEST(reinstall_reinstall); + CPPUNIT_TEST(reinstall_remove); + CPPUNIT_TEST(reinstall_replaced); + CPPUNIT_TEST(reinstall_reasonchange); + CPPUNIT_TEST(reinstall_missing); + CPPUNIT_TEST(reinstall_wrong_version); + + CPPUNIT_TEST(reasonchange); + CPPUNIT_TEST(reasonchange_install); + CPPUNIT_TEST(reasonchange_missing); + CPPUNIT_TEST(reasonchange_upgrade); + CPPUNIT_TEST(reasonchange_downgrade); + CPPUNIT_TEST(reasonchange_reinstall); + CPPUNIT_TEST(reasonchange_remove); + CPPUNIT_TEST(reasonchange_replaced); + CPPUNIT_TEST(reasonchange_reasonchange); + + CPPUNIT_TEST(install_upgrade_reinstall); + CPPUNIT_TEST(install_remove_install); + CPPUNIT_TEST(install_remove_upgrade); + CPPUNIT_TEST(install_install_remove); + CPPUNIT_TEST(install_install_installonly); + CPPUNIT_TEST(install_remove_installonly); + CPPUNIT_TEST(remove_installonly_missing); + CPPUNIT_TEST(install_remove_install_installonly); + CPPUNIT_TEST(remove_install_remove); + CPPUNIT_TEST(install_reasonchange_upgrade); + CPPUNIT_TEST(remove_install_lower); + CPPUNIT_TEST(upgrade_installonly_forward); + CPPUNIT_TEST(upgrade_installonly_backward); + + CPPUNIT_TEST(group_install_remove); + CPPUNIT_TEST(group_install_install); + CPPUNIT_TEST(group_install_upgrade); + CPPUNIT_TEST(group_remove_install); + CPPUNIT_TEST(group_remove_upgrade); + CPPUNIT_TEST(group_remove_remove); + CPPUNIT_TEST(group_upgrade_upgrade); + CPPUNIT_TEST(group_upgrade_remove); + CPPUNIT_TEST(group_upgrade_install); + + CPPUNIT_TEST_SUITE_END(); + +public: + void empty_transaction(); + void only_one_transaction(); + void two_disjoint(); + + void install_install(); + void install_install2(); + void install_upgrade(); + void install_downgrade(); + void install_reinstall(); + void install_remove(); + void install_replaced(); + void install_reasonchange(); + + void upgrade_install(); + void upgrade_upgrade(); + void upgrade_upgrade2(); + void upgrade_upgrade_installonly(); + void upgrade_downgrade(); + void upgrade_reinstall(); + void upgrade_remove(); + void upgrade_replaced(); + void upgrade_reasonchange(); + void upgrade_missing(); + + void reinstall(); + void reinstall_install(); + void reinstall_install2(); + void reinstall_upgrade(); + void reinstall_downgrade(); + void reinstall_reinstall(); + void reinstall_remove(); + void reinstall_replaced(); + void reinstall_reasonchange(); + void reinstall_missing(); + void reinstall_wrong_version(); + + void remove_install(); + void remove_upgrade(); + void remove_downgrade(); + void remove_reinstall(); + void remove_remove(); + void remove_remove_installonly(); + void remove_replaced(); + void remove_reasonchange(); + void remove_missing(); + + void reasonchange(); + void reasonchange_install(); + void reasonchange_missing(); + void reasonchange_upgrade(); + void reasonchange_downgrade(); + void reasonchange_reinstall(); + void reasonchange_remove(); + void reasonchange_replaced(); + void reasonchange_reasonchange(); + + void install_upgrade_reinstall(); + void install_remove_install(); + void install_remove_upgrade(); + void install_install_remove(); + void install_install_installonly(); + void install_remove_installonly(); + void remove_installonly_missing(); + void install_remove_install_installonly(); + void remove_install_lower(); + void remove_install_remove(); + void install_reasonchange_upgrade(); + void upgrade_installonly_forward(); + void upgrade_installonly_backward(); + + void group_install_remove(); + void group_install_install(); + void group_install_upgrade(); + void group_remove_install(); + void group_remove_upgrade(); + void group_remove_remove(); + void group_upgrade_upgrade(); + void group_upgrade_remove(); + void group_upgrade_install(); + + // Only a couple tests for environments, they share code with groups + void env_install_remove(); + void env_upgrade_remove(); +}; + + +#endif // TEST_LIBDNF5_TRANSACTION_TEST_TRANSACTION_MERGE_HPP diff --git a/test/shared/utils.hpp b/test/shared/utils.hpp index f473d1b24..860e6b163 100644 --- a/test/shared/utils.hpp +++ b/test/shared/utils.hpp @@ -33,6 +33,7 @@ along with libdnf. If not, see . #include #include #include +#include #include #include @@ -277,6 +278,63 @@ struct assertion_traits { }; #endif +template <> +struct assertion_traits { + inline static bool equal( + const libdnf5::transaction::PackageReplay & left, const libdnf5::transaction::PackageReplay & right) { + return left.action == right.action && left.reason == right.reason && left.group_id == right.group_id && + left.nevra == right.nevra && left.package_path == right.package_path && left.repo_id == right.repo_id; + } + + inline static std::string toString(const libdnf5::transaction::PackageReplay & replay) { + return fmt::format( + "PackageReplay: nevra: {}, action: {}, reason: {}, repo_id: {}, group_id: {}, package_path: {}", + replay.nevra, + libdnf5::transaction::transaction_item_action_to_string(replay.action), + libdnf5::transaction::transaction_item_reason_to_string(replay.reason), + replay.repo_id, + replay.group_id, + std::string(replay.package_path)); + } +}; + +template <> +struct assertion_traits { + inline static bool equal( + const libdnf5::transaction::GroupReplay & left, const libdnf5::transaction::GroupReplay & right) { + return left.action == right.action && left.reason == right.reason && left.group_id == right.group_id && + left.group_path == right.group_path && left.repo_id == right.repo_id; + } + + inline static std::string toString(const libdnf5::transaction::GroupReplay & replay) { + return fmt::format( + "GroupReplay: group_id: {}, action: {}, reason: {}, repo_id: {}, group_path: {}", + replay.group_id, + libdnf5::transaction::transaction_item_action_to_string(replay.action), + libdnf5::transaction::transaction_item_reason_to_string(replay.reason), + replay.repo_id, + std::string(replay.group_path)); + } +}; + +template <> +struct assertion_traits { + inline static bool equal( + const libdnf5::transaction::EnvironmentReplay & left, const libdnf5::transaction::EnvironmentReplay & right) { + return left.action == right.action && left.environment_id == right.environment_id && + left.environment_path == right.environment_path && left.repo_id == right.repo_id; + } + + inline static std::string toString(const libdnf5::transaction::EnvironmentReplay & replay) { + return fmt::format( + "EnvironmentReplay: environment_id: {}, action: {}, repo_id: {}, environment_path: {}", + replay.environment_id, + libdnf5::transaction::transaction_item_action_to_string(replay.action), + replay.repo_id, + std::string(replay.environment_path)); + } +}; + } // namespace CPPUNIT_NS From 745aca76af366a4a26bfa7720f854e6361fad725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= Date: Mon, 24 Jun 2024 12:29:48 +0200 Subject: [PATCH 3/4] Use transaction merging when reverting transactions The order is: first revert all specified transactions and then merge them together. --- libdnf5/base/goal.cpp | 205 ++++++++++++++++++++++++------------------ 1 file changed, 118 insertions(+), 87 deletions(-) diff --git a/libdnf5/base/goal.cpp b/libdnf5/base/goal.cpp index f046c3cf3..010322dd1 100644 --- a/libdnf5/base/goal.cpp +++ b/libdnf5/base/goal.cpp @@ -32,6 +32,7 @@ along with libdnf. If not, see . #include "solv/id_queue.hpp" #include "solv/pool.hpp" #include "solver_problems_internal.hpp" +#include "transaction/transaction_merge.hpp" #include "transaction/transaction_sr.hpp" #include "transaction_impl.hpp" #include "utils/string.hpp" @@ -2810,111 +2811,141 @@ GoalProblem Goal::Impl::resolve_reverted_transactions(base::Transaction & transa {Action::REPLACED, Action::INSTALL}, {Action::REASON_CHANGE, Action::REASON_CHANGE}, }; - transaction::TransactionReplay replay; auto history = base->get_transaction_history(); auto & [reverting_transactions, settings] = *revert_transactions; - //TODO(amatej): Implement merging of transactions and merge the vector - // instead of taking the first one. - auto & reverting_transaction = reverting_transactions[0]; - - for (const auto & pkg : reverting_transaction.get_packages()) { - transaction::PackageReplay package_replay; - package_replay.nevra = libdnf5::rpm::to_nevra_string(pkg); - auto reverted_action = REVERT_ACTION.find(pkg.get_action()); - libdnf_assert( - reverted_action != REVERT_ACTION.end(), - "Cannot revert action: \"{}\"", - transaction_item_action_to_string(pkg.get_action())); - package_replay.action = reverted_action->second; - - // We cannot tell the previous reason if the action is REASON_CHANGE it could have been anything. - // For reverted action INSTALL and reason CLEAN the previous reason could have been either DEPENDENCY or WEAK DEPENDENCY - // to pick the right one we have to look into history. - if ((package_replay.action == Action::REASON_CHANGE) || - (package_replay.action == Action::INSTALL && pkg.get_reason() == Reason::CLEAN)) { - // We look up the reason based on only name and arch, this means we could find a different - // version of installonly package however we store only one reason for ALL versions of - // installonly packages so it doesn't matter. - package_replay.reason = - history->transaction_item_reason_at(pkg.get_name(), pkg.get_arch(), reverting_transaction.get_id() - 1); - } else if ( - package_replay.action == Action::REMOVE && - (pkg.get_reason() == Reason::DEPENDENCY || pkg.get_reason() == Reason::WEAK_DEPENDENCY)) { - package_replay.reason = Reason::CLEAN; - } else { - package_replay.reason = pkg.get_reason(); + std::vector reverted_transactions; + + for (auto & reverting_transaction : reverting_transactions) { + transaction::TransactionReplay replay; + for (const auto & pkg : reverting_transaction.get_packages()) { + transaction::PackageReplay package_replay; + package_replay.nevra = libdnf5::rpm::to_nevra_string(pkg); + auto reverted_action = REVERT_ACTION.find(pkg.get_action()); + libdnf_assert( + reverted_action != REVERT_ACTION.end(), + "Cannot revert action: \"{}\"", + transaction_item_action_to_string(pkg.get_action())); + package_replay.action = reverted_action->second; + + // We cannot tell the previous reason if the action is REASON_CHANGE it could have been anything. + // For reverted action INSTALL and reason CLEAN the previous reason could have been either DEPENDENCY or WEAK DEPENDENCY + // to pick the right one we have to look into history. + if ((package_replay.action == Action::REASON_CHANGE) || + (package_replay.action == Action::INSTALL && pkg.get_reason() == Reason::CLEAN)) { + // We look up the reason based on only name and arch, this means we could find a different + // version of installonly package however we store only one reason for ALL versions of + // installonly packages so it doesn't matter. + package_replay.reason = history->transaction_item_reason_at( + pkg.get_name(), pkg.get_arch(), reverting_transaction.get_id() - 1); + } else if ( + package_replay.action == Action::REMOVE && + (pkg.get_reason() == Reason::DEPENDENCY || pkg.get_reason() == Reason::WEAK_DEPENDENCY)) { + package_replay.reason = Reason::CLEAN; + } else { + package_replay.reason = pkg.get_reason(); + } + + replay.packages.push_back(package_replay); } - replay.packages.push_back(package_replay); - } + for (const auto & group : reverting_transaction.get_comps_groups()) { + transaction::GroupReplay group_replay; + group_replay.group_id = group.to_string(); + // Do not revert UPGRADE for groups. Groups don't have an upgrade path so they cannot be + // upgraded or downgraded. The UPGRADE action is basically a synchronization with + // current group definition. Revert happens automatically by reverting the rpm actions. + if (group.get_action() != transaction::TransactionItemAction::UPGRADE) { + auto reverted_action = REVERT_ACTION.find(group.get_action()); + if (reverted_action == REVERT_ACTION.end()) { + libdnf_throw_assertion( + "Cannot revert action: \"{}\"", transaction_item_action_to_string(group.get_action())); + } + group_replay.action = reverted_action->second; + } else { + transaction.p_impl->add_resolve_log( + GoalAction::REVERT_COMPS_UPGRADE, + libdnf5::GoalProblem::UNSUPPORTED_ACTION, + settings, + libdnf5::transaction::TransactionItemType::GROUP, + group_replay.group_id, + {}, + libdnf5::Logger::Level::WARNING); + continue; + } - for (const auto & group : reverting_transaction.get_comps_groups()) { - transaction::GroupReplay group_replay; - group_replay.group_id = group.to_string(); - // Do not revert UPGRADE for groups. Groups don't have an upgrade path so they cannot be - // upgraded or downgraded. The UPGRADE action is basically a synchronization with - // current group definition. Revert happens automatically by reverting the rpm actions. - if (group.get_action() != transaction::TransactionItemAction::UPGRADE) { - auto reverted_action = REVERT_ACTION.find(group.get_action()); - if (reverted_action == REVERT_ACTION.end()) { - libdnf_throw_assertion( - "Cannot revert action: \"{}\"", transaction_item_action_to_string(group.get_action())); + if (group_replay.action == Action::INSTALL && group.get_reason() == Reason::CLEAN) { + group_replay.reason = Reason::DEPENDENCY; + } else if (group_replay.action == Action::REMOVE && group.get_reason() == Reason::DEPENDENCY) { + group_replay.reason = Reason::CLEAN; + } else { + group_replay.reason = group.get_reason(); } - group_replay.action = reverted_action->second; - } else { - transaction.p_impl->add_resolve_log( - GoalAction::REVERT_COMPS_UPGRADE, - libdnf5::GoalProblem::UNSUPPORTED_ACTION, - settings, - libdnf5::transaction::TransactionItemType::GROUP, - group_replay.group_id, - {}, - libdnf5::Logger::Level::WARNING); - continue; + + replay.groups.push_back(group_replay); } - if (group_replay.action == Action::INSTALL && group.get_reason() == Reason::CLEAN) { - group_replay.reason = Reason::DEPENDENCY; - } else if (group_replay.action == Action::REMOVE && group.get_reason() == Reason::DEPENDENCY) { - group_replay.reason = Reason::CLEAN; - } else { - group_replay.reason = group.get_reason(); + for (const auto & env : reverting_transaction.get_comps_environments()) { + transaction::EnvironmentReplay env_replay; + env_replay.environment_id = env.to_string(); + // Do not revert UPGRADE for environments. Environments don't have an upgrade path so they cannot be + // upgraded or downgraded. The UPGRADE action is basically a synchronization with + // current environment definition. Revert happens automatically by reverting the rpm + // actions. + if (env.get_action() != transaction::TransactionItemAction::UPGRADE) { + auto reverted_action = REVERT_ACTION.find(env.get_action()); + if (reverted_action == REVERT_ACTION.end()) { + libdnf_throw_assertion( + "Cannot revert action: \"{}\"", transaction_item_action_to_string(env.get_action())); + } + env_replay.action = reverted_action->second; + } else { + transaction.p_impl->add_resolve_log( + GoalAction::REVERT_COMPS_UPGRADE, + libdnf5::GoalProblem::UNSUPPORTED_ACTION, + settings, + libdnf5::transaction::TransactionItemType::ENVIRONMENT, + env_replay.environment_id, + {}, + libdnf5::Logger::Level::WARNING); + continue; + } + + replay.environments.push_back(env_replay); } - replay.groups.push_back(group_replay); + reverted_transactions.push_back(replay); } - for (const auto & env : reverting_transaction.get_comps_environments()) { - transaction::EnvironmentReplay env_replay; - env_replay.environment_id = env.to_string(); - // Do not revert UPGRADE for environments. Environments don't have an upgrade path so they cannot be - // upgraded or downgraded. The UPGRADE action is basically a synchronization with - // current environment definition. Revert happens automatically by reverting the rpm - // actions. - if (env.get_action() != transaction::TransactionItemAction::UPGRADE) { - auto reverted_action = REVERT_ACTION.find(env.get_action()); - if (reverted_action == REVERT_ACTION.end()) { - libdnf_throw_assertion( - "Cannot revert action: \"{}\"", transaction_item_action_to_string(env.get_action())); - } - env_replay.action = reverted_action->second; + std::reverse(reverted_transactions.begin(), reverted_transactions.end()); + + // Prepare installed map which is needed for merging + libdnf5::rpm::PackageQuery installed_query(base, libdnf5::rpm::PackageQuery::ExcludeFlags::IGNORE_EXCLUDES); + installed_query.filter_installed(); + std::unordered_map> installed; + for (const auto & pkg : installed_query) { + const auto name_arch = pkg.get_name() + "." + pkg.get_arch(); + if (installed.contains(name_arch)) { + installed[name_arch].push_back(pkg.get_nevra()); } else { - transaction.p_impl->add_resolve_log( - GoalAction::REVERT_COMPS_UPGRADE, - libdnf5::GoalProblem::UNSUPPORTED_ACTION, - settings, - libdnf5::transaction::TransactionItemType::ENVIRONMENT, - env_replay.environment_id, - {}, - libdnf5::Logger::Level::WARNING); - continue; + installed[name_arch] = {pkg.get_nevra()}; } + } + auto [merged_transactions, problems] = merge_transactions( + reverted_transactions, installed, base->get_config().get_installonlypkgs_option().get_value()); - replay.environments.push_back(env_replay); + for (const auto & problem : problems) { + transaction.p_impl->add_resolve_log( + GoalAction::MERGE, + libdnf5::GoalProblem::MERGE_ERROR, + settings, + libdnf5::transaction::TransactionItemType::PACKAGE, + {}, + {problem}, + libdnf5::Logger::Level::WARNING); } - ret |= add_replay_to_goal(transaction, replay, settings); + ret |= add_replay_to_goal(transaction, merged_transactions, settings); return ret; } From 427a9f484d2d88e532ae8dc7021721a1101d9fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= Date: Fri, 17 May 2024 11:39:21 +0200 Subject: [PATCH 4/4] Add `history rollback` command --- dnf5/commands/history/history.cpp | 2 +- dnf5/commands/history/history_rollback.cpp | 53 +++++++++++++++++++++- dnf5/commands/history/history_rollback.hpp | 8 ++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/dnf5/commands/history/history.cpp b/dnf5/commands/history/history.cpp index 693485366..791c516ce 100644 --- a/dnf5/commands/history/history.cpp +++ b/dnf5/commands/history/history.cpp @@ -58,7 +58,7 @@ void HistoryCommand::register_subcommands() { cmd.register_group(software_management_commands_group); register_subcommand(std::make_unique(get_context()), software_management_commands_group); // register_subcommand(std::make_unique(get_context()), software_management_commands_group); - // register_subcommand(std::make_unique(get_context()), software_management_commands_group); + register_subcommand(std::make_unique(get_context()), software_management_commands_group); register_subcommand(std::make_unique(get_context()), software_management_commands_group); } diff --git a/dnf5/commands/history/history_rollback.cpp b/dnf5/commands/history/history_rollback.cpp index 51479f484..46783331f 100644 --- a/dnf5/commands/history/history_rollback.cpp +++ b/dnf5/commands/history/history_rollback.cpp @@ -19,14 +19,65 @@ along with libdnf. If not, see . #include "history_rollback.hpp" +#include "arguments.hpp" +#include "dnf5/shared_options.hpp" +#include "transaction_id.hpp" + +#include + namespace dnf5 { using namespace libdnf5::cli; void HistoryRollbackCommand::set_argument_parser() { get_argument_parser_command()->set_description("Undo all transactions performed after the specified transaction"); + transaction_specs = std::make_unique(*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(*this); + ignore_extras = std::make_unique(*this); + ignore_installed = std::make_unique(*this); +} + +void HistoryRollbackCommand::configure() { + auto & context = get_context(); + context.set_load_system_repo(true); + context.set_load_available_repos(Context::LoadAvailableRepos::ENABLED); } -void HistoryRollbackCommand::run() {} +void HistoryRollbackCommand::run() { + auto ts_specs = transaction_specs->get_value(); + libdnf5::transaction::TransactionHistory history(get_context().get_base()); + std::vector transactions; + std::vector 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 max_id = history.list_transaction_ids().back(); + + int64_t target_id = target_trans[0].get_id() + 1; + if (target_id <= max_id) { + transactions = history.list_transactions(target_id, max_id); + } + + auto goal = get_context().get_goal(); + // To enable removal of dependency packages not present in the selected transactions + // 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(transactions, settings); +} } // namespace dnf5 diff --git a/dnf5/commands/history/history_rollback.hpp b/dnf5/commands/history/history_rollback.hpp index 33559edda..c60fe1d41 100644 --- a/dnf5/commands/history/history_rollback.hpp +++ b/dnf5/commands/history/history_rollback.hpp @@ -21,6 +21,8 @@ along with libdnf. If not, see . #ifndef DNF5_COMMANDS_HISTORY_HISTORY_ROLLBACK_HPP #define DNF5_COMMANDS_HISTORY_HISTORY_ROLLBACK_HPP +#include "commands/history/arguments.hpp" + #include @@ -31,7 +33,13 @@ class HistoryRollbackCommand : public Command { public: explicit HistoryRollbackCommand(Context & context) : Command(context, "rollback") {} void set_argument_parser() override; + void configure() override; void run() override; + +private: + std::unique_ptr transaction_specs{nullptr}; + std::unique_ptr ignore_extras{nullptr}; + std::unique_ptr ignore_installed{nullptr}; };