diff --git a/SOURCES/0034-fix-compare-RPMItem-in-transaction-with-rpmvercmp.patch b/SOURCES/0034-fix-compare-RPMItem-in-transaction-with-rpmvercmp.patch new file mode 100644 index 0000000..7c5e6c5 --- /dev/null +++ b/SOURCES/0034-fix-compare-RPMItem-in-transaction-with-rpmvercmp.patch @@ -0,0 +1,206 @@ +From 26f929cdbf033a48abbc8d6fe4cf741a366d1028 Mon Sep 17 00:00:00 2001 +From: Matej Focko +Date: Fri, 30 Jan 2026 12:28:09 +0100 +Subject: [PATCH] fix: compare RPMItem in transaction with rpmvercmp +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Previous implementation resorts to manual comparison of the EVR fields +including the following issues: + +• There appears to be a typo when comparing epochs: + + } else if (epoch < 0) { + + instead of + + } else if (epochDif < 0) { + + N.B. ‹epoch < 0› is unreachable + • RPM packaging guidelines **do not** define any requirements on the + value (and is internally represented as ‹int32_t›) + • On the contrary Fedora packaging guidelines denote: + + If present, it MUST consist of a positive integer. + +• There is a manual parsing of the version fields by “chopping off” the + parts ofversion and parsing them via ‹std::stoi›. + +• More importantly, reported by bugs below, the release fields are + ignored altogether. + +Instead of manually processing the EVR, with a potential room to error, +switch to using the ‹::rpmvercmp› from ‹rpm/rpmver.h› that should be the +“source of truth” in this case. + +Also cover the discovered problems with regression tests in unit tests. + +Fixes RHEL-81778 +Fixes RHEL-81779 +Fixes RHEL-128443 (RHEL10 clone of RHEL-81779) + +Signed-off-by: Matej Focko +(cherry picked from commit 1bcd660168302c120b73b99238babf39f3131faa) +--- + libdnf/transaction/RPMItem.cpp | 39 +++++++-------- + tests/libdnf/transaction/RpmItemTest.cpp | 61 ++++++++++++++++++++++++ + tests/libdnf/transaction/RpmItemTest.hpp | 2 + + 3 files changed, 80 insertions(+), 22 deletions(-) + +diff --git a/libdnf/transaction/RPMItem.cpp b/libdnf/transaction/RPMItem.cpp +index ecce789d..92c26d09 100644 +--- a/libdnf/transaction/RPMItem.cpp ++++ b/libdnf/transaction/RPMItem.cpp +@@ -20,6 +20,7 @@ + + #include + #include ++#include + #include + + #include "../hy-subject.h" +@@ -343,35 +344,29 @@ RPMItem::resolveTransactionItemReason(SQLite3Ptr conn, + * Compare RPM packages + * This method doesn't care about compare package names + * \param other RPMItem to compare with +- * \return true if other package is newer (has higher version and/or epoch) ++ * \return true if other package is newer (has higher epoch, version, or release) + */ + bool + RPMItem::operator<(const RPMItem &other) const + { +- // compare epochs +- int32_t epochDif = other.getEpoch() - getEpoch(); +- if (epochDif > 0) { +- return true; +- } else if (epoch < 0) { +- return false; ++ // Compare epochs ++ if (getEpoch() != other.getEpoch()) { ++ return getEpoch() < other.getEpoch(); + } + +- // compare versions +- std::stringstream versionThis(getVersion()); +- std::stringstream versionOther(other.getVersion()); +- +- std::string bufferThis; +- std::string bufferOther; +- while (std::getline(versionThis, bufferThis, '.') && +- std::getline(versionOther, bufferOther, '.')) { +- int subVersionThis = std::stoi(bufferThis); +- int subVersionOther = std::stoi(bufferOther); +- if (subVersionThis == subVersionOther) { +- continue; +- } +- return subVersionOther > subVersionThis; ++ // Compare versions ++ auto version = getVersion(); ++ auto otherVersion = other.getVersion(); ++ auto cmpResult = ::rpmvercmp(version.c_str(), otherVersion.c_str()); ++ if (cmpResult != 0) { ++ return cmpResult < 0; + } +- return false; ++ ++ // Compare releases ++ auto release = getRelease(); ++ auto otherRelease = other.getRelease(); ++ cmpResult = ::rpmvercmp(release.c_str(), otherRelease.c_str()); ++ return cmpResult < 0; + } + + std::vector< int64_t > +diff --git a/tests/libdnf/transaction/RpmItemTest.cpp b/tests/libdnf/transaction/RpmItemTest.cpp +index 774c716a..503e4ec3 100644 +--- a/tests/libdnf/transaction/RpmItemTest.cpp ++++ b/tests/libdnf/transaction/RpmItemTest.cpp +@@ -119,3 +119,64 @@ RpmItemTest::testGetTransactionItems() + //CPPUNIT_ASSERT(createMs.count() == 0); + //CPPUNIT_ASSERT(readMs.count() == 0); + } ++ ++/** ++ * Regression test for RHEL-81778 ++ * Regression test for RHEL-81779 ++ */ ++void ++RpmItemTest::testComparison() ++{ ++ auto a = std::make_shared< RPMItem >(conn); ++ a->setName("rsyslog"); ++ a->setEpoch(0); ++ a->setVersion("8.2102.0"); ++ a->setRelease("1.el9"); ++ a->setArch("aarch64"); ++ ++ auto b = std::make_shared< RPMItem >(conn); ++ b->setName("rsyslog"); ++ b->setEpoch(0); ++ b->setVersion("8.2102.0"); ++ b->setRelease("5.el9"); ++ b->setArch("aarch64"); ++ ++ // rsyslog-8.2102.0-1.el9.aarch64 < rsyslog-8.2102.0-5.el9.aarch64 ++ CPPUNIT_ASSERT(*a < *b); ++ CPPUNIT_ASSERT(!(*b < *a)); ++ ++ a->setRelease("2.el9"); ++ b->setRelease("1.el9"); ++ ++ // rsyslog-8.2102.0-1.el9.aarch64 < rsyslog-8.2102.0-2.el9.aarch64 ++ CPPUNIT_ASSERT(*b < *a); ++ CPPUNIT_ASSERT(!(*a < *b)); ++ ++ b->setRelease("10.el9"); ++ ++ // rsyslog-8.2102.0-2.el9.aarch64 < rsyslog-8.2102.0-10.el9.aarch64 ++ CPPUNIT_ASSERT(*a < *b); ++ CPPUNIT_ASSERT(!(*b < *a)); ++ ++ // Cover fixed comparison of epochs (previously falling through to version ++ // comparison when left side being newer) ++ { ++ auto a = std::make_shared< RPMItem >(conn); ++ a->setName("rsyslog"); ++ a->setEpoch(0); ++ a->setVersion("8.2102.0"); ++ a->setRelease("1.el9"); ++ a->setArch("aarch64"); ++ ++ auto b = std::make_shared< RPMItem >(conn); ++ b->setName("rsyslog"); ++ b->setEpoch(3); ++ b->setVersion("8.2101.0"); ++ b->setRelease("1.el9"); ++ b->setArch("aarch64"); ++ ++ // rsyslog-0:8.2102.0-1.el9.aarch64 < rsyslog-3:8.2101.0-1.el9.aarch64 ++ CPPUNIT_ASSERT(*a < *b); ++ CPPUNIT_ASSERT(!(*b < *a)); ++ } ++} +diff --git a/tests/libdnf/transaction/RpmItemTest.hpp b/tests/libdnf/transaction/RpmItemTest.hpp +index 59cc6c59..bb4cd930 100644 +--- a/tests/libdnf/transaction/RpmItemTest.hpp ++++ b/tests/libdnf/transaction/RpmItemTest.hpp +@@ -10,6 +10,7 @@ class RpmItemTest : public CppUnit::TestCase { + CPPUNIT_TEST(testCreate); + CPPUNIT_TEST(testCreateDuplicates); + CPPUNIT_TEST(testGetTransactionItems); ++ CPPUNIT_TEST(testComparison); + CPPUNIT_TEST_SUITE_END(); + + public: +@@ -19,6 +20,7 @@ public: + void testCreate(); + void testCreateDuplicates(); + void testGetTransactionItems(); ++ void testComparison(); + + private: + std::shared_ptr< SQLite3 > conn; +-- +2.52.0 + diff --git a/SOURCES/0035-Fix-dnf_keyring_add_public_keys-reset-GError-to-NULL.patch b/SOURCES/0035-Fix-dnf_keyring_add_public_keys-reset-GError-to-NULL.patch new file mode 100644 index 0000000..d96b68b --- /dev/null +++ b/SOURCES/0035-Fix-dnf_keyring_add_public_keys-reset-GError-to-NULL.patch @@ -0,0 +1,36 @@ +From 96df64bf5f4e374adac4b1ea423cb29ae73b9f49 Mon Sep 17 00:00:00 2001 +From: Jaroslav Rohel +Date: Mon, 7 Nov 2022 12:15:19 +0100 +Subject: [PATCH] Fix: "dnf_keyring_add_public_keys": reset GError to NULL + (RhBug:2121222) + +Fixes problem "PackageKit crashes if parsing multiple key files fails" +packagekitd[1397]: GError set over the top of a previous GError or + uninitialized memory. This indicates a bug in someone's code. You must + ensure an error is NULL before it's set. The overwriting error message + was: failed to parse public key for + /etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-14-secondary + += changelog = +msg: "dnf_keyring_add_public_keys": reset localError to NULL after free +type: bugfix +resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2121222 +--- + libdnf/dnf-keyring.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libdnf/dnf-keyring.cpp b/libdnf/dnf-keyring.cpp +index 62a6248c..550d5ce2 100644 +--- a/libdnf/dnf-keyring.cpp ++++ b/libdnf/dnf-keyring.cpp +@@ -213,6 +213,7 @@ dnf_keyring_add_public_keys(rpmKeyring keyring, GError **error) try + if (!ret) { + g_warning("%s", localError->message); + g_error_free(localError); ++ localError = NULL; + } + } while (true); + return TRUE; +-- +2.52.0 + diff --git a/SOURCES/0036-Mark-all-protected-packages-as-user-installed-for-al.patch b/SOURCES/0036-Mark-all-protected-packages-as-user-installed-for-al.patch new file mode 100644 index 0000000..351fe76 --- /dev/null +++ b/SOURCES/0036-Mark-all-protected-packages-as-user-installed-for-al.patch @@ -0,0 +1,34 @@ +From 5298228fc2ac50f2172f17ff3f821f7a1a35393d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Fri, 9 Jan 2026 07:26:34 +0100 +Subject: [PATCH 1/5] Mark all protected packages as user installed for all + transactions + +Closes: https://github.com/rpm-software-management/dnf/issues/2192 +--- + libdnf/goal/Goal.cpp | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/libdnf/goal/Goal.cpp b/libdnf/goal/Goal.cpp +index ba938e10..3543b3bd 100644 +--- a/libdnf/goal/Goal.cpp ++++ b/libdnf/goal/Goal.cpp +@@ -987,6 +987,15 @@ Goal::jobLength() + bool + Goal::run(DnfGoalActions flags) + { ++ // Automatically mark all protected packages as user installed. ++ // When a protected package is installed as a dependency it can block ++ // removal of the last package that depends on it (because the protected ++ // package cannot be removed, not even as an unused dependency). ++ // To prevent this and still correctly resolve dependencies of the protected ++ // packages mark them all as user installed. ++ if (pImpl->protectedPkgs) { ++ userInstalled(*pImpl->protectedPkgs); ++ } + auto job = pImpl->constructJob(flags); + pImpl->actions = static_cast(pImpl->actions | flags); + int ret = pImpl->solve(job->getQueue(), flags); +-- +2.53.0 + diff --git a/SOURCES/0037-Add-filterUnneededExtraUserinstalled-and-Python-vers.patch b/SOURCES/0037-Add-filterUnneededExtraUserinstalled-and-Python-vers.patch new file mode 100644 index 0000000..8040a47 --- /dev/null +++ b/SOURCES/0037-Add-filterUnneededExtraUserinstalled-and-Python-vers.patch @@ -0,0 +1,149 @@ +From 71c3d69bf56c1fa0726b7b5a2fad20acbf8f1ba9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Mon, 26 Jan 2026 14:07:37 +0100 +Subject: [PATCH 2/5] Add `filterUnneededExtraUserinstalled` and Python version + to the API + +This can be used to get unneeded packages with for example protected +packages extra marked as userinstalled. +--- + libdnf/sack/query.cpp | 18 +++++++++++---- + libdnf/sack/query.hpp | 1 + + python/hawkey/query-py.cpp | 47 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 62 insertions(+), 4 deletions(-) + +diff --git a/libdnf/sack/query.cpp b/libdnf/sack/query.cpp +index 6eecfa50..8dce7082 100644 +--- a/libdnf/sack/query.cpp ++++ b/libdnf/sack/query.cpp +@@ -830,7 +830,7 @@ private: + void filterUpdownByPriority(const Filter & f, Map *m); + void filterUpdownAble(const Filter &f, Map *m); + void filterDataiterator(const Filter & f, Map *m); +- int filterUnneededOrSafeToRemove(const Swdb &swdb, bool debug_solver, bool safeToRemove); ++ int filterUnneededOrSafeToRemove(const Swdb &swdb, bool debug_solver, bool safeToRemove, PackageSet *extra_userinstalled); + void obsoletesByPriority(Pool * pool, Solvable * candidate, Map * m, const Map * target, int obsprovides); + + bool isGlob(const std::vector &matches) const; +@@ -2245,7 +2245,7 @@ Query::Impl::filterDataiterator(const Filter & f, Map *m) + } + + int +-Query::Impl::filterUnneededOrSafeToRemove(const Swdb &swdb, bool debug_solver, bool safeToRemove) ++Query::Impl::filterUnneededOrSafeToRemove(const Swdb &swdb, bool debug_solver, bool safeToRemove, PackageSet *extra_userinstalled) + { + apply(); + Goal goal(sack); +@@ -2260,6 +2260,10 @@ Query::Impl::filterUnneededOrSafeToRemove(const Swdb &swdb, bool debug_solver, b + } + goal.userInstalled(*userInstalled); + ++ if (extra_userinstalled != NULL) { ++ goal.userInstalled(*extra_userinstalled); ++ } ++ + int ret1 = goal.run(DNF_NONE); + if (ret1) + return -1; +@@ -2575,13 +2579,19 @@ Query::filterDuplicated() + int + Query::filterUnneeded(const Swdb &swdb, bool debug_solver) + { +- return pImpl->filterUnneededOrSafeToRemove(swdb, debug_solver, false); ++ return pImpl->filterUnneededOrSafeToRemove(swdb, debug_solver, false, NULL); + } + + int + Query::filterSafeToRemove(const Swdb &swdb, bool debug_solver) + { +- return pImpl->filterUnneededOrSafeToRemove(swdb, debug_solver, true); ++ return pImpl->filterUnneededOrSafeToRemove(swdb, debug_solver, true, NULL); ++} ++ ++int ++Query::filterUnneededExtraUserinstalled(const Swdb &swdb, PackageSet &extra_userinstalled, bool debug_solver) ++{ ++ return pImpl->filterUnneededOrSafeToRemove(swdb, debug_solver, false, &extra_userinstalled); + } + + void +diff --git a/libdnf/sack/query.hpp b/libdnf/sack/query.hpp +index 306b24e3..a5ed7745 100644 +--- a/libdnf/sack/query.hpp ++++ b/libdnf/sack/query.hpp +@@ -177,6 +177,7 @@ public: + void filterRecent(const long unsigned int recent_limit); + void filterDuplicated(); + int filterUnneeded(const Swdb &swdb, bool debug_solver); ++ int filterUnneededExtraUserinstalled(const Swdb &swdb, PackageSet &extra_userinstalled, bool debug_solver); + int filterSafeToRemove(const Swdb &swdb, bool debug_solver); + void getAdvisoryPkgs(int cmpType, std::vector & advisoryPkgs); + void filterUserInstalled(const Swdb &swdb); +diff --git a/python/hawkey/query-py.cpp b/python/hawkey/query-py.cpp +index 99f71bd5..1290a07a 100644 +--- a/python/hawkey/query-py.cpp ++++ b/python/hawkey/query-py.cpp +@@ -809,6 +809,52 @@ filter_unneeded(PyObject *self, PyObject *args, PyObject *kwds) try + return filter_unneeded_or_safe_to_remove(self, args, kwds, false); + } CATCH_TO_PYTHON + ++static PyObject * ++filter_unneeded_extra_userinstalled(PyObject *self, PyObject *args, PyObject *kwds) try ++{ ++ const char *kwlist[] = {"swdb", "extra_userinstalled", "debug_solver", NULL}; ++ PyObject *pySwdb; ++ PyObject *extra_userinstalled; ++ PyObject *debug_solver = NULL; ++ ++ if (!PyArg_ParseTupleAndKeywords( ++ args, kwds, "OO|O!", (char **)kwlist, &pySwdb, &extra_userinstalled, &PyBool_Type, &debug_solver)) { ++ return NULL; ++ } ++ ++ UniquePtrPyObject thisPySwdb(PyObject_GetAttrString(pySwdb, "this")); ++ auto swigSwdb = reinterpret_cast< SwdbSwigPyObject * >(thisPySwdb.get()); ++ if (swigSwdb == nullptr) { ++ PyErr_SetString(PyExc_SystemError, "Unable to parse SwigPyObject"); ++ return NULL; ++ } ++ libdnf::Swdb *swdb = swigSwdb->ptr; ++ if (swdb == NULL) { ++ PyErr_SetString(PyExc_SystemError, "Unable to parse swig object"); ++ return NULL; ++ } ++ ++ HyQuery query = ((_QueryObject *) self)->query; ++ auto extra_userinstalled_pset = pyseq_to_packageset(extra_userinstalled, query->getSack()); ++ if (!extra_userinstalled_pset) { ++ PyErr_SetString(PyExc_SystemError, "Unable to parse SwigPyObject: extra_userinstalled PackageSet"); ++ return NULL; ++ } ++ ++ std::unique_ptr self_query_copy(new libdnf::Query(*query)); ++ gboolean c_debug_solver = debug_solver != NULL && PyObject_IsTrue(debug_solver); ++ ++ int ret = self_query_copy->filterUnneededExtraUserinstalled(*swdb, *extra_userinstalled_pset, c_debug_solver); ++ if (ret == -1) { ++ PyErr_SetString(PyExc_SystemError, "Unable to provide query with unneded filter"); ++ return NULL; ++ } ++ ++ PyObject *final_query = queryToPyObject(self_query_copy.release(), ((_QueryObject *) self)->sack, ++ Py_TYPE(self)); ++ return final_query; ++} CATCH_TO_PYTHON ++ + static PyObject * + q_add(_QueryObject *self, PyObject *list) try + { +@@ -1071,6 +1117,7 @@ static struct PyMethodDef query_methods[] = { + {"_nevra", (PyCFunction)add_nevra_or_other_filter, METH_VARARGS, NULL}, + {"_recent", (PyCFunction)add_filter_recent, METH_VARARGS, NULL}, + {"_unneeded", (PyCFunction)filter_unneeded, METH_KEYWORDS|METH_VARARGS, NULL}, ++ {"_unneeded_extra_userinstalled", (PyCFunction)filter_unneeded_extra_userinstalled, METH_KEYWORDS|METH_VARARGS, NULL}, + {"_safe_to_remove", (PyCFunction)filter_safe_to_remove, METH_KEYWORDS|METH_VARARGS, NULL}, + {NULL} /* sentinel */ + }; +-- +2.53.0 + diff --git a/SOURCES/0038-Describe-all-problems-even-when-there-are-protected-.patch b/SOURCES/0038-Describe-all-problems-even-when-there-are-protected-.patch new file mode 100644 index 0000000..c084282 --- /dev/null +++ b/SOURCES/0038-Describe-all-problems-even-when-there-are-protected-.patch @@ -0,0 +1,31 @@ +From 61066041c2d4e816bc5f4443876af7d9b15d03ec Mon Sep 17 00:00:00 2001 +From: Evan Goode +Date: Tue, 27 Jan 2026 13:00:50 -0500 +Subject: [PATCH 4/5] Describe all problems even when there are protected + removals + +Previously, if a transaction involved removal (or dependency break) of a +protected package, only the protected package problem would be described +by `describeProblemRules`. Information about excluded or versionlocked +packages would be missing. + +For: RHEL-115194 +--- + libdnf/goal/Goal.cpp | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/libdnf/goal/Goal.cpp b/libdnf/goal/Goal.cpp +index 3543b3bd..fb7bd343 100644 +--- a/libdnf/goal/Goal.cpp ++++ b/libdnf/goal/Goal.cpp +@@ -1108,7 +1108,6 @@ Goal::describeProblemRules(unsigned i, bool pkgs) + auto problem = pImpl->describeProtectedRemoval(); + if (!problem.empty()) { + output.push_back(std::move(problem)); +- return output; + } + auto solv = pImpl->solv; + +-- +2.53.0 + diff --git a/SOURCES/0039-Clearer-error-for-protected-package-broken-dependenc.patch b/SOURCES/0039-Clearer-error-for-protected-package-broken-dependenc.patch new file mode 100644 index 0000000..6695a7a --- /dev/null +++ b/SOURCES/0039-Clearer-error-for-protected-package-broken-dependenc.patch @@ -0,0 +1,60 @@ +From 5f5dbd01e93d1ec830d5d071051d1a98d2357227 Mon Sep 17 00:00:00 2001 +From: Evan Goode +Date: Tue, 27 Jan 2026 13:19:14 -0500 +Subject: [PATCH 5/5] Clearer error for protected package broken dependencies + +The error message now distinguishes removal of a protected package from +breaking dependencies of protected packages. +--- + libdnf/goal/Goal.cpp | 17 +++++++++++------ + 1 file changed, 11 insertions(+), 6 deletions(-) + +diff --git a/libdnf/goal/Goal.cpp b/libdnf/goal/Goal.cpp +index fb7bd343..5ae86db5 100644 +--- a/libdnf/goal/Goal.cpp ++++ b/libdnf/goal/Goal.cpp +@@ -1697,11 +1697,11 @@ Goal::Impl::protectedInRemovals() + std::string + Goal::Impl::describeProtectedRemoval() + { +- std::string message(_("The operation would result in removing" +- " the following protected packages: ")); + Pool * pool = solv->pool; + + if (removalOfProtected && removalOfProtected->size()) { ++ const std::string removal_message(_("The operation would result in removing " ++ "the following protected packages: ")); + Id id = -1; + std::vector names; + while((id = removalOfProtected->next(id)) != -1) { +@@ -1711,9 +1711,13 @@ Goal::Impl::describeProtectedRemoval() + if (names.empty()) { + return {}; + } +- return message + std::accumulate(std::next(names.begin()), names.end(), +- std::string(names[0]), [](std::string a, std::string b) { return a + ", " + b; }); ++ return removal_message + ++ std::accumulate(std::next(names.begin()), names.end(), std::string(names[0]), ++ [](std::string a, std::string b) { return a + ", " + b; }); + } ++ ++ const std::string broken_dependency_message(_("The operation would result in broken " ++ "dependencies for the following protected packages: ")); + auto pset = brokenDependencyAllPkgs(DNF_PACKAGE_STATE_INSTALLED); + Id id = -1; + Id protected_kernel = protectedRunningKernel(); +@@ -1726,8 +1730,9 @@ Goal::Impl::describeProtectedRemoval() + } + if (names.empty()) + return {}; +- return message + std::accumulate(std::next(names.begin()), names.end(), std::string(names[0]), +- [](std::string a, std::string b) { return a + ", " + b; }); ++ return broken_dependency_message + ++ std::accumulate(std::next(names.begin()), names.end(), std::string(names[0]), ++ [](std::string a, std::string b) { return a + ", " + b; }); + } + + } +-- +2.53.0 + diff --git a/SOURCES/0040-Goal-set-protected-as-userinstalled-only-for-the-tem.patch b/SOURCES/0040-Goal-set-protected-as-userinstalled-only-for-the-tem.patch new file mode 100644 index 0000000..7754302 --- /dev/null +++ b/SOURCES/0040-Goal-set-protected-as-userinstalled-only-for-the-tem.patch @@ -0,0 +1,57 @@ +From ac1121bc58f779838713c0b0a39b6cc929d141c3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Tue, 10 Feb 2026 13:04:42 +0100 +Subject: [PATCH] Goal: set protected as userinstalled only for the temporary + job + +This way the goal's `staging` is not affected. +--- + libdnf/goal/Goal.cpp | 25 ++++++++++++++++--------- + 1 file changed, 16 insertions(+), 9 deletions(-) + +diff --git a/libdnf/goal/Goal.cpp b/libdnf/goal/Goal.cpp +index 5ae86db5..7766b976 100644 +--- a/libdnf/goal/Goal.cpp ++++ b/libdnf/goal/Goal.cpp +@@ -987,15 +987,6 @@ Goal::jobLength() + bool + Goal::run(DnfGoalActions flags) + { +- // Automatically mark all protected packages as user installed. +- // When a protected package is installed as a dependency it can block +- // removal of the last package that depends on it (because the protected +- // package cannot be removed, not even as an unused dependency). +- // To prevent this and still correctly resolve dependencies of the protected +- // packages mark them all as user installed. +- if (pImpl->protectedPkgs) { +- userInstalled(*pImpl->protectedPkgs); +- } + auto job = pImpl->constructJob(flags); + pImpl->actions = static_cast(pImpl->actions | flags); + int ret = pImpl->solve(job->getQueue(), flags); +@@ -1401,6 +1392,22 @@ Goal::Impl::constructJob(DnfGoalActions flags) + + allowUninstallAllButProtected(job->getQueue(), flags); + ++ // Automatically mark all protected packages as user installed. ++ // When a protected package is installed as a dependency it can block ++ // removal of the last package that depends on it (because the protected ++ // package cannot be removed, not even as an unused dependency). ++ // To prevent this and still correctly resolve dependencies of the protected ++ // packages mark them all as user installed. ++ if (protectedPkgs) { ++ Id id = -1; ++ while (true) { ++ id = protectedPkgs->next(id); ++ if (id == -1) ++ break; ++ queue_push2(job->getQueue(), SOLVER_SOLVABLE|SOLVER_USERINSTALLED, id); ++ } ++ } ++ + if (flags & DNF_VERIFY) + job->pushBack(SOLVER_VERIFY|SOLVER_SOLVABLE_ALL, 0); + +-- +2.53.0 + diff --git a/SPECS/libdnf.spec b/SPECS/libdnf.spec index 946d5e6..107ff8a 100644 --- a/SPECS/libdnf.spec +++ b/SPECS/libdnf.spec @@ -58,7 +58,7 @@ Name: libdnf Version: %{libdnf_major_version}.%{libdnf_minor_version}.%{libdnf_micro_version} -Release: 16%{?dist} +Release: 18%{?dist} Summary: Library providing simplified C and Python API to libsolv License: LGPLv2+ URL: https://github.com/rpm-software-management/libdnf @@ -96,6 +96,13 @@ Patch30: 0030-C-API-Detect-releasever_major-releasever_minor-from-.patch Patch31: 0031-C-API-Use-releasever_-major-minor-from-context-inste.patch Patch32: 0032-C-API-support-shell-style-variable-substitution.patch Patch33: 0033-C-API-test-shell-style-variable-expressions.patch +Patch34: 0034-fix-compare-RPMItem-in-transaction-with-rpmvercmp.patch +Patch35: 0035-Fix-dnf_keyring_add_public_keys-reset-GError-to-NULL.patch +Patch36: 0036-Mark-all-protected-packages-as-user-installed-for-al.patch +Patch37: 0037-Add-filterUnneededExtraUserinstalled-and-Python-vers.patch +Patch38: 0038-Describe-all-problems-even-when-there-are-protected-.patch +Patch39: 0039-Clearer-error-for-protected-package-broken-dependenc.patch +Patch40: 0040-Goal-set-protected-as-userinstalled-only-for-the-tem.patch BuildRequires: cmake @@ -345,6 +352,16 @@ popd %endif %changelog +* Mon Feb 09 2026 Ales Matej - 0.69.0-18 +- Fix a crash when parsing multiple key files fails (RHEL-145618) +- Mark all protected packages as user installed for all transactions (RHEL-76112) +- Add `filterUnneededExtraUserinstalled` and Python version to the API +- Describe all problems even when there are protected removals (RHEL-115194) +- Clearer error for protected package broken dependencies + +* Tue Feb 03 2026 Matej Focko - 0.69.0-17 +- Fix comparison of RPM items in the transaction (RHEL-81779) + * Mon Jun 30 2025 Evan Goode - 0.69.0-16 - Introduce $releasever_major, $releasever_minor variables, shell-style variable substitution (RHEL-95006)