Hello community,

here is the log from the commit of package libdnf for openSUSE:Factory checked 
in at 2020-12-02 13:57:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libdnf (Old)
 and      /work/SRC/openSUSE:Factory/.libdnf.new.5913 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "libdnf"

Wed Dec  2 13:57:18 2020 rev:19 rq:851721 version:0.55.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/libdnf/libdnf.changes    2020-10-28 
09:58:42.719158598 +0100
+++ /work/SRC/openSUSE:Factory/.libdnf.new.5913/libdnf.changes  2020-12-02 
13:57:20.897745938 +0100
@@ -1,0 +2,17 @@
+Sun Nov 29 22:14:35 UTC 2020 - Neal Gompa <ngomp...@gmail.com>
+
+- Update to version 0.55.0
+  + Add vendor to dnf API (rh#1876561)
+  + Add formatting function for solver error
+  + Add error types in ModulePackageContainer
+  + Implement module enable for context part
+  + Improve string formatting for translation
+  + Remove redundant printf and change logging info to notice (rh#1827424)
+  + Add allow_vendor_change option (rh#1788371) (rh#1788371)
+- Backport patches from upstream
+  + Patch: 0001-Support-allow_vendor_change-setting-in-dnf-context-A.patch
+  + Patch: 0001-context-dnf_keyring_add_public_keys-not-generate-err.patch
+- Add patch to turn off changing vendors by default
+  + Patch: libdnf-0.55.0-Switch-allow_vendor_change-off.patch
+
+-------------------------------------------------------------------

Old:
----
  libdnf-0.54.2.tar.gz

New:
----
  0001-Support-allow_vendor_change-setting-in-dnf-context-A.patch
  0001-context-dnf_keyring_add_public_keys-not-generate-err.patch
  libdnf-0.55.0-Switch-allow_vendor_change-off.patch
  libdnf-0.55.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ libdnf.spec ++++++
--- /var/tmp/diff_new_pack.VdjYrO/_old  2020-12-02 13:57:21.541746616 +0100
+++ /var/tmp/diff_new_pack.VdjYrO/_new  2020-12-02 13:57:21.545746620 +0100
@@ -33,7 +33,7 @@
 %define devname %{name}-devel
 
 Name:           libdnf
-Version:        0.54.2
+Version:        0.55.0
 Release:        0
 Summary:        Library providing C and Python APIs atop libsolv
 License:        LGPL-2.1-or-later
@@ -42,12 +42,16 @@
 Source0:        %{url}/archive/%{version}/%{name}-%{version}.tar.gz
 
 # Backports from upstream
+Patch0001:      0001-Support-allow_vendor_change-setting-in-dnf-context-A.patch
+Patch0002:      0001-context-dnf_keyring_add_public_keys-not-generate-err.patch
 
 # openSUSE specific fixes
 ## Fix libdnf build with static libsolvext
 Patch1000:      libdnf-0.48.0-with-static-libsolvext.patch
 ## Switch default reposdir to /etc/dnf/repos.d
 Patch1001:      libdnf-0.54.2-Switch-default-reposdir-to-etc-dnf-repos.d.patch
+## Switch allow_vendor_change off by default
+Patch1002:      libdnf-0.55.0-Switch-allow_vendor_change-off.patch
 
 BuildRequires:  cmake
 BuildRequires:  gcc

++++++ 0001-Support-allow_vendor_change-setting-in-dnf-context-A.patch ++++++
From 690243af4f5113368ce905e750577926bfe94977 Mon Sep 17 00:00:00 2001
From: Neal Gompa <ngomp...@gmail.com>
Date: Wed, 18 Nov 2020 08:03:28 -0500
Subject: [PATCH] Support allow_vendor_change setting in dnf context API

= changelog =
msg: Support allow_vendor_change setting in dnf context API
type: enhancement
---
 libdnf/dnf-context.cpp | 33 +++++++++++++++++++++++++++++++++
 libdnf/dnf-context.h   |  2 ++
 2 files changed, 35 insertions(+)

diff --git a/libdnf/dnf-context.cpp b/libdnf/dnf-context.cpp
index 33e7eb6d..06926711 100644
--- a/libdnf/dnf-context.cpp
+++ b/libdnf/dnf-context.cpp
@@ -932,6 +932,22 @@ dnf_context_get_install_weak_deps()
     return mainConf.install_weak_deps().getValue();
 }
 
+/**
+ * dnf_context_get_allow_vendor_change:
+ *
+ * Gets allow_vendor_change global configuration value.
+ *
+ * Returns: %TRUE if changing vendors in a transaction is allowed
+ *
+ * Since: 0.55.2
+ */
+gboolean
+dnf_context_get_allow_vendor_change()
+{
+    auto & mainConf = libdnf::getGlobalMainConfig();
+    return mainConf.allow_vendor_change().getValue();
+}
+
 /**
  * dnf_context_get_check_disk_space:
  * @context: a #DnfContext instance.
@@ -1409,6 +1425,20 @@ dnf_context_set_install_weak_deps(gboolean enabled)
     mainConf.install_weak_deps().set(libdnf::Option::Priority::RUNTIME, 
enabled);
 }
 
+/**
+ * dnf_context_set_allow_vendor_change:
+ *
+ * Sets allow_vendor_change global configuration value.
+ *
+ * Since: 0.55.2
+ */
+void
+dnf_context_set_allow_vendor_change(gboolean vendorchange)
+{
+    auto & mainConf = libdnf::getGlobalMainConfig();
+    mainConf.allow_vendor_change().set(libdnf::Option::Priority::RUNTIME, 
vendorchange);
+}
+
 /**
  * dnf_context_set_cache_only:
  * @context: a #DnfContext instance.
@@ -1725,12 +1755,15 @@ dnf_context_setup_sack_with_flags(DnfContext            
   *context,
     DnfContextPrivate *priv = GET_PRIVATE(context);
     gboolean ret;
     g_autofree gchar *solv_dir_real = nullptr;
+    gboolean vendorchange;
 
     /* create empty sack */
     solv_dir_real = dnf_realpath(priv->solv_dir);
+    vendorchange = dnf_context_get_allow_vendor_change();
     priv->sack = dnf_sack_new();
     dnf_sack_set_cachedir(priv->sack, solv_dir_real);
     dnf_sack_set_rootdir(priv->sack, priv->install_root);
+    dnf_sack_set_allow_vendor_change(priv->sack, vendorchange);
     if (priv->arch) {
         if(!dnf_sack_set_arch(priv->sack, priv->arch, error)) {
             return FALSE;
diff --git a/libdnf/dnf-context.h b/libdnf/dnf-context.h
index f5dfb0b3..71e12ebd 100644
--- a/libdnf/dnf-context.h
+++ b/libdnf/dnf-context.h
@@ -132,6 +132,7 @@ const gchar     **dnf_context_get_native_arches         
(DnfContext     *context
 const gchar     **dnf_context_get_installonly_pkgs      (DnfContext     
*context);
 gboolean         dnf_context_get_best                   (void);
 gboolean         dnf_context_get_install_weak_deps      (void);
+gboolean         dnf_context_get_allow_vendor_change    (void);
 gboolean         dnf_context_get_cache_only             (DnfContext     
*context);
 gboolean         dnf_context_get_check_disk_space       (DnfContext     
*context);
 gboolean         dnf_context_get_check_transaction      (DnfContext     
*context);
@@ -185,6 +186,7 @@ void             dnf_context_set_source_root            
(DnfContext     *context
                                                          const gchar    
*source_root);
 void             dnf_context_set_best                   (gboolean        best);
 void             dnf_context_set_install_weak_deps      (gboolean        
enabled);
+void             dnf_context_set_allow_vendor_change    (gboolean        
vendorchange);
 void             dnf_context_set_cache_only             (DnfContext     
*context,
                                                          gboolean        
cache_only);
 void             dnf_context_set_check_disk_space       (DnfContext     
*context,
-- 
2.28.0

++++++ 0001-context-dnf_keyring_add_public_keys-not-generate-err.patch ++++++
From 35f2062c8508326221a6d807ece3feec766349fb Mon Sep 17 00:00:00 2001
From: Jaroslav Rohel <jro...@redhat.com>
Date: Wed, 25 Nov 2020 15:41:59 +0100
Subject: [PATCH] [context] dnf_keyring_add_public_keys(): not generate error
 if dir problem

The function adds all public keys from "/etc/pki/rpm-gpg" (installed public
keys) to the RPM keyring.

Before patch:
Empty directory was ignored. But if the directory could not be opened
(does not exist, it is a file, another error ...) an error has occurred.

Changes:
A non-existent directory is ignored too. If the path exists but
cannot be opened by the process as a directory, warning is reported.

Closes: #1097
Approved by: Conan-Kudo
---
 libdnf/dnf-keyring.cpp | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/libdnf/dnf-keyring.cpp b/libdnf/dnf-keyring.cpp
index 6797b119..eec58c69 100644
--- a/libdnf/dnf-keyring.cpp
+++ b/libdnf/dnf-keyring.cpp
@@ -189,11 +189,17 @@ dnf_keyring_add_public_keys(rpmKeyring keyring, GError 
**error) try
     const gchar *gpg_dir = "/etc/pki/rpm-gpg";
     gboolean ret = TRUE;
     g_autoptr(GDir) dir = NULL;
+    GError *localError = NULL;
 
     /* search all the public key files */
-    dir = g_dir_open(gpg_dir, 0, error);
-    if (dir == NULL)
-        return FALSE;
+    dir = g_dir_open(gpg_dir, 0, &localError);
+    if (dir == NULL) {
+        if (localError->domain != G_FILE_ERROR || localError->code != 
G_FILE_ERROR_NOENT) {
+            g_warning("%s", localError->message);
+        }
+        g_error_free(localError);
+        return TRUE;
+    }
     do {
         const gchar *filename;
         g_autofree gchar *path_tmp = NULL;
@@ -201,7 +207,6 @@ dnf_keyring_add_public_keys(rpmKeyring keyring, GError 
**error) try
         if (filename == NULL)
             break;
         path_tmp = g_build_filename(gpg_dir, filename, NULL);
-        GError *localError = NULL;
         ret = dnf_keyring_add_public_key(keyring, path_tmp, &localError);
         if (!ret) {
             g_warning("%s", localError->message);
-- 
2.28.0

++++++ libdnf-0.55.0-Switch-allow_vendor_change-off.patch ++++++
From 6a67b1a671f5778b014d8652321a193715cf952c Mon Sep 17 00:00:00 2001
From: Neal Gompa <ngomp...@gmail.com>
Date: Sun, 29 Nov 2020 18:46:15 -0500
Subject: [PATCH] Switch allow_vendor_change off by default

This is consistent with how SUSE distributions expect package
management to work.
---
 libdnf/conf/ConfigMain.cpp | 2 +-
 libdnf/dnf-sack.cpp        | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/libdnf/conf/ConfigMain.cpp b/libdnf/conf/ConfigMain.cpp
index 1ffd3b33..c451015d 100644
--- a/libdnf/conf/ConfigMain.cpp
+++ b/libdnf/conf/ConfigMain.cpp
@@ -216,7 +216,7 @@ class ConfigMain::Impl {
     OptionBool obsoletes{true};
     OptionBool showdupesfromrepos{false};
     OptionBool exit_on_lock{false};
-    OptionBool allow_vendor_change{true};
+    OptionBool allow_vendor_change{false};
     OptionSeconds metadata_timer_sync{60 * 60 * 3}; // 3 hours
     OptionStringList disable_excludes{std::vector<std::string>{}};
     OptionEnum<std::string> multilib_policy{"best", {"best", "all"}}; // :api
diff --git a/libdnf/dnf-sack.cpp b/libdnf/dnf-sack.cpp
index 9fd2c72d..e0b53b60 100644
--- a/libdnf/dnf-sack.cpp
+++ b/libdnf/dnf-sack.cpp
@@ -190,7 +190,7 @@ dnf_sack_init(DnfSack *sack)
     priv->running_kernel_fn = running_kernel;
     priv->considered_uptodate = TRUE;
     priv->cmdline_repo = NULL;
-    priv->allow_vendor_change = TRUE;
+    priv->allow_vendor_change = FALSE;
     queue_init(&priv->installonly);
 
     /* logging up after this*/
-- 
2.28.0

++++++ libdnf-0.54.2.tar.gz -> libdnf-0.55.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/VERSION.cmake 
new/libdnf-0.55.0/VERSION.cmake
--- old/libdnf-0.54.2/VERSION.cmake     2020-10-06 14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/VERSION.cmake     2020-11-09 15:42:13.000000000 +0100
@@ -1,6 +1,6 @@
 set (DEFAULT_LIBDNF_MAJOR_VERSION 0)
-set (DEFAULT_LIBDNF_MINOR_VERSION 54)
-set (DEFAULT_LIBDNF_MICRO_VERSION 2)
+set (DEFAULT_LIBDNF_MINOR_VERSION 55)
+set (DEFAULT_LIBDNF_MICRO_VERSION 0)
 
 if(DEFINED LIBDNF_MAJOR_VERSION)
   if(NOT ${DEFAULT_LIBDNF_MAJOR_VERSION} STREQUAL ${LIBDNF_MAJOR_VERSION})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/docs/release_notes.rst 
new/libdnf-0.55.0/docs/release_notes.rst
--- old/libdnf-0.54.2/docs/release_notes.rst    2020-10-06 14:52:22.000000000 
+0200
+++ new/libdnf-0.55.0/docs/release_notes.rst    2020-11-09 15:42:13.000000000 
+0100
@@ -20,6 +20,19 @@
 ######################
 
 ====================
+0.55.0 Release Notes
+====================
+
+- Add vendor to dnf API (RhBug:1876561)
+- Add formatting function for solver error
+- Add error types in ModulePackageContainer
+- Implement module enable for context part
+- Improve string formatting for translation
+- Remove redundant printf and change logging info to notice (RhBug:1827424)
+- Add allow_vendor_change option (RhBug:1788371) (RhBug:1788371)
+
+
+====================
 0.54.2 Release Notes
 ====================
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/conf/ConfigMain.cpp 
new/libdnf-0.55.0/libdnf/conf/ConfigMain.cpp
--- old/libdnf-0.54.2/libdnf/conf/ConfigMain.cpp        2020-10-06 
14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/libdnf/conf/ConfigMain.cpp        2020-11-09 
15:42:13.000000000 +0100
@@ -216,6 +216,7 @@
     OptionBool obsoletes{true};
     OptionBool showdupesfromrepos{false};
     OptionBool exit_on_lock{false};
+    OptionBool allow_vendor_change{true};
     OptionSeconds metadata_timer_sync{60 * 60 * 3}; // 3 hours
     OptionStringList disable_excludes{std::vector<std::string>{}};
     OptionEnum<std::string> multilib_policy{"best", {"best", "all"}}; // :api
@@ -399,6 +400,7 @@
     owner.optBinds().add("obsoletes", obsoletes);
     owner.optBinds().add("showdupesfromrepos", showdupesfromrepos);
     owner.optBinds().add("exit_on_lock", exit_on_lock);
+    owner.optBinds().add("allow_vendor_change", allow_vendor_change);
     owner.optBinds().add("metadata_timer_sync", metadata_timer_sync);
     owner.optBinds().add("disable_excludes", disable_excludes);
     owner.optBinds().add("multilib_policy", multilib_policy);
@@ -531,6 +533,7 @@
 OptionBool & ConfigMain::obsoletes() { return pImpl->obsoletes; }
 OptionBool & ConfigMain::showdupesfromrepos() { return 
pImpl->showdupesfromrepos; }
 OptionBool & ConfigMain::exit_on_lock() { return pImpl->exit_on_lock; }
+OptionBool & ConfigMain::allow_vendor_change() { return 
pImpl->allow_vendor_change; }
 OptionSeconds & ConfigMain::metadata_timer_sync() { return 
pImpl->metadata_timer_sync; }
 OptionStringList & ConfigMain::disable_excludes() { return 
pImpl->disable_excludes; }
 OptionEnum<std::string> & ConfigMain::multilib_policy() { return 
pImpl->multilib_policy; }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/conf/ConfigMain.hpp 
new/libdnf-0.55.0/libdnf/conf/ConfigMain.hpp
--- old/libdnf-0.54.2/libdnf/conf/ConfigMain.hpp        2020-10-06 
14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/libdnf/conf/ConfigMain.hpp        2020-11-09 
15:42:13.000000000 +0100
@@ -89,6 +89,7 @@
     OptionBool & obsoletes();
     OptionBool & showdupesfromrepos();
     OptionBool & exit_on_lock();
+    OptionBool & allow_vendor_change();
     OptionSeconds & metadata_timer_sync();
     OptionStringList & disable_excludes();
     OptionEnum<std::string> & multilib_policy(); // :api
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/dnf-context.cpp 
new/libdnf-0.55.0/libdnf/dnf-context.cpp
--- old/libdnf-0.54.2/libdnf/dnf-context.cpp    2020-10-06 14:52:22.000000000 
+0200
+++ new/libdnf-0.55.0/libdnf/dnf-context.cpp    2020-11-09 15:42:13.000000000 
+0100
@@ -35,6 +35,9 @@
 #include "dnf-context.hpp"
 #include "libdnf/conf/ConfigParser.hpp"
 #include "conf/Option.hpp"
+#include "bgettext/bgettext-lib.h"
+#include "tinyformat/tinyformat.hpp"
+#include "goal/Goal.hpp"
 
 #include <memory>
 #include <set>
@@ -74,6 +77,7 @@
 #include "dnf-repo.hpp"
 #include "goal/Goal.hpp"
 #include "plugin/plugin-private.hpp"
+#include "utils/GLibLogger.hpp"
 #include "utils/os-release.hpp"
 
 
@@ -185,6 +189,7 @@
     SIGNAL_LAST
 };
 
+static libdnf::GLibLogger glibLogger(G_LOG_DOMAIN);
 static std::string pluginsDir = DEFAULT_PLUGINS_DIRECTORY;
 static std::unique_ptr<std::string> configFilePath;
 static std::set<std::string> pluginsEnabled;
@@ -332,6 +337,8 @@
 {
     DnfContextPrivate *priv = GET_PRIVATE(context);
 
+    libdnf::Log::setLogger(&glibLogger);
+
     priv->install_root = g_strdup("/");
     priv->check_disk_space = TRUE;
     priv->check_transaction = TRUE;
@@ -2880,6 +2887,26 @@
     return priv->plugins->hook(id, hookData, error);
 }
 
+gchar *
+dnf_context_get_module_report(DnfContext * context)
+{
+    DnfContextPrivate *priv = GET_PRIVATE (context);
+
+    /* create sack and add sources */
+    if (priv->sack == nullptr) {
+        return nullptr;
+    }
+    auto container = dnf_sack_get_module_container(priv->sack);
+    if (container == nullptr) {
+        return nullptr;
+    }
+    auto report = container->getReport();
+    if (report.empty()) {
+        return nullptr;
+    }
+    return g_strdup(report.c_str());
+}
+
 DnfContext *
 pluginGetContext(DnfPluginInitData * data)
 {
@@ -2896,6 +2923,20 @@
     return (static_cast<PluginHookContextInitData *>(data)->context);
 }
 
+static std::vector<std::tuple<libdnf::ModulePackageContainer::ModuleErrorType, 
std::string, std::string>>
+recompute_modular_filtering(libdnf::ModulePackageContainer * moduleContainer, 
DnfSack * sack, std::vector<const char *> & hotfixRepos)
+{
+    auto solver_errors = dnf_sack_filter_modules_v2(
+        sack, moduleContainer, hotfixRepos.data(), nullptr, nullptr, true, 
false);
+    if (solver_errors.second == 
libdnf::ModulePackageContainer::ModuleErrorType::NO_ERROR) {
+        return {};
+    }
+    auto formated_problem = 
libdnf::Goal::formatAllProblemRules(solver_errors.first);
+    std::vector<std::tuple<libdnf::ModulePackageContainer::ModuleErrorType, 
std::string, std::string>> ret;
+    ret.emplace_back(std::make_tuple(solver_errors.second, 
std::move(formated_problem), std::string()));
+    return ret;
+}
+
 static gboolean
 recompute_modular_filtering(DnfContext * context, DnfSack * sack, GError ** 
error)
 {
@@ -2959,11 +3000,391 @@
     for (auto & name: names) {
         container->reset(name);
     }
-    container->save();
-    container->updateFailSafeData();
+    //container->save();
+    //container->updateFailSafeData();
     return recompute_modular_filtering(context, sack, error);
 } CATCH_TO_GERROR(FALSE)
 
+/// module dict { name : {stream : [modules] }
+static std::map<std::string, std::map<std::string, 
std::vector<libdnf::ModulePackage *>>> create_module_dict(
+    const std::vector<libdnf::ModulePackage *> & modules)
+{
+    std::map<std::string, std::map<std::string, 
std::vector<libdnf::ModulePackage *>>> data;
+    for (auto * module : modules) {
+        data[module->getName()][module->getStream()].push_back(module);
+    }
+    return data;
+}
+
+/// Modify module_dict => Keep only single relevant stream
+/// If more streams it keeps enabled or default stream
+static std::vector<std::tuple<libdnf::ModulePackageContainer::ModuleErrorType, 
std::string, std::string>> 
modify_module_dict_and_enable_stream(std::map<std::string, 
std::map<std::string, std::vector<libdnf::ModulePackage *>>> & module_dict, 
libdnf::ModulePackageContainer & container, bool enable)
+{
+    std::vector<std::tuple<libdnf::ModulePackageContainer::ModuleErrorType, 
std::string, std::string>> messages;
+    for (auto module_dict_iter : module_dict) {
+        auto & name = module_dict_iter.first;
+        auto & stream_dict = module_dict_iter.second;
+        auto moduleState = container.getModuleState(name);
+        if (stream_dict.size() > 1) {
+            if (moduleState != 
libdnf::ModulePackageContainer::ModuleState::ENABLED 
+                && moduleState != 
libdnf::ModulePackageContainer::ModuleState::DEFAULT) {
+                messages.emplace_back(std::make_tuple(
+                    
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_ENABLE_MULTIPLE_STREAMS,
+                    tfm::format(_("Cannot enable more streams from module '%s' 
at the same time"), name), name));
+                return messages;
+            }
+            const auto enabledOrDefaultStream = moduleState == 
libdnf::ModulePackageContainer::ModuleState::ENABLED ?
+            container.getEnabledStream(name) : 
container.getDefaultStream(name);
+            auto modules_iter = stream_dict.find(enabledOrDefaultStream);
+            if (modules_iter == stream_dict.end()) {
+                messages.emplace_back(std::make_tuple(
+                    
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_ENABLE_MULTIPLE_STREAMS,
+                    tfm::format(_("Cannot enable more streams from module '%s' 
at the same time"), name), name));
+                return messages;;
+            }
+            if (enable) {
+                try {
+                    container.enable(name, modules_iter->first);
+                } catch 
(libdnf::ModulePackageContainer::EnableMultipleStreamsException &) {
+                    messages.emplace_back(std::make_tuple(
+                        
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_MODIFY_MULTIPLE_TIMES_MODULE_STATE,
+                        tfm::format(_("Cannot enable module '%1$s' stream 
'%2$s': State of module already modified"),
+                                    name, modules_iter->first), name));
+                }
+            }
+            for (auto iter = stream_dict.begin(); iter != stream_dict.end(); ) 
{
+                if (iter->first != enabledOrDefaultStream) {
+                    stream_dict.erase(iter);
+                } else {
+                    ++iter;
+                }
+            }
+        } else if (enable) {
+            for (auto iter : stream_dict) {
+                try {
+                    container.enable(name, iter.first);
+                } catch 
(libdnf::ModulePackageContainer::EnableMultipleStreamsException &) {
+                    messages.emplace_back(std::make_tuple(
+                        
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_MODIFY_MULTIPLE_TIMES_MODULE_STATE,
+                        tfm::format(_("Cannot enable module '%1$s' stream 
'%2$s': State of module already modified"),
+                                     name, iter.first), name));
+                }
+            }
+        }
+        if (stream_dict.size() != 1) {
+            throw std::runtime_error("modify_module_dict_and_enable_stream() 
did not modify output correctly!!!");
+        }
+    }
+    return messages;
+}
+
+static std::pair<std::unique_ptr<libdnf::Nsvcap>, std::vector< 
libdnf::ModulePackage*>> resolve_module_spec(const std::string & module_spec, 
libdnf::ModulePackageContainer & container)
+{
+    std::unique_ptr<libdnf::Nsvcap> nsvcapObj(new libdnf::Nsvcap);
+    for (std::size_t i = 0; HY_MODULE_FORMS_MOST_SPEC[i] != 
_HY_MODULE_FORM_STOP_; ++i) {
+        if (nsvcapObj->parse(module_spec.c_str(), 
HY_MODULE_FORMS_MOST_SPEC[i])) {
+            auto result_modules = container.query(nsvcapObj->getName(),
+                                                  nsvcapObj->getStream(),
+                                                  nsvcapObj->getVersion(),
+                                                  nsvcapObj->getContext(),
+                                                  nsvcapObj->getArch());
+            if (!result_modules.empty()) {
+                return std::make_pair(std::move(nsvcapObj), 
std::move(result_modules));
+            }
+        }
+    }
+    return {};
+}
+
+static bool
+report_problems(const 
std::vector<std::tuple<libdnf::ModulePackageContainer::ModuleErrorType, 
std::string, std::string>> & messages)
+{
+    libdnf::ModulePackageContainer::ModuleErrorType typeMessage;
+    std::string report;
+    std::string argument;
+    auto logger(libdnf::Log::getLogger());
+    bool return_error = false;
+    for (auto & message : messages) {
+        std::tie(typeMessage, report, argument) = message;
+        switch (typeMessage) {
+            case libdnf::ModulePackageContainer::ModuleErrorType::NO_ERROR:
+                break;
+            case libdnf::ModulePackageContainer::ModuleErrorType::INFO:
+                logger->notice(report);
+                break;
+            case 
libdnf::ModulePackageContainer::ModuleErrorType::ERROR_IN_DEFAULTS:
+                logger->warning(tfm::format(_("Modular dependency problem with 
Defaults: %s"), report.c_str()));
+                break;
+            case libdnf::ModulePackageContainer::ModuleErrorType::ERROR:
+                logger->error(tfm::format(_("Modular dependency problem: %s"), 
report.c_str()));
+                return_error = true;
+                break;
+            case 
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_RESOLVE_MODULES:
+                logger->error(report);
+                return_error = true;
+                break;
+            case 
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_RESOLVE_MODULE_SPEC:
+                logger->error(report);
+                return_error = true;
+                break;
+            case 
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_ENABLE_MULTIPLE_STREAMS:
+                logger->error(report);
+                return_error = true;
+                break;
+            case 
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_MODIFY_MULTIPLE_TIMES_MODULE_STATE:
+                logger->error(report);
+                return_error = true;
+                break;
+        }
+    }
+    return return_error;
+}
+
+static std::vector<std::tuple<libdnf::ModulePackageContainer::ModuleErrorType, 
std::string, std::string>>
+modules_reset_or_disable(libdnf::ModulePackageContainer & container, const 
char ** module_specs, bool reset)
+{
+    std::vector<std::tuple<libdnf::ModulePackageContainer::ModuleErrorType, 
std::string, std::string>> messages;
+
+    for (const char ** specs = module_specs; *specs != NULL; ++specs) {
+        auto resolved_spec = resolve_module_spec(*specs, container);
+        if (!resolved_spec.first) {
+            messages.emplace_back(
+                
std::make_tuple(libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_RESOLVE_MODULE_SPEC,
+                                tfm::format(_("Unable to resolve argument 
'%s'"), *specs), *specs));
+            continue;
+        }
+        if (!resolved_spec.first->getStream().empty() || 
!resolved_spec.first->getProfile().empty() ||
+            !resolved_spec.first->getVersion().empty() || 
!resolved_spec.first->getContext().empty()) {
+        messages.emplace_back(std::make_tuple(
+            libdnf::ModulePackageContainer::ModuleErrorType::INFO,
+            tfm::format(_("Only module name is required. Ignoring unneeded 
information in argument: '%s'"), *specs),
+            *specs));
+        }
+        std::unordered_set<std::string> names;
+        for (auto * module : resolved_spec.second) {
+            names.insert(std::move(module->getName()));
+        }
+        for (auto & name : names) {
+            if (reset) {
+                try {
+                    container.reset(name);
+                } catch 
(libdnf::ModulePackageContainer::EnableMultipleStreamsException &) {
+                    messages.emplace_back(std::make_tuple(
+                        
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_MODIFY_MULTIPLE_TIMES_MODULE_STATE,
+                        tfm::format(_("Cannot reset module '%s': State of 
module already modified"), name), name));
+                    messages.emplace_back(
+                        
std::make_tuple(libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_RESOLVE_MODULE_SPEC,
+                                        tfm::format(_("Unable to resolve 
argument '%s'"), *specs), *specs));
+                }
+            } else {
+                try {
+                    container.disable(name);
+                } catch 
(libdnf::ModulePackageContainer::EnableMultipleStreamsException &) {
+                    messages.emplace_back(std::make_tuple(
+                        
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_MODIFY_MULTIPLE_TIMES_MODULE_STATE,
+                        tfm::format(_("Cannot disable module '%s': State of 
module already modified"), name), name));
+                    messages.emplace_back(
+                        
std::make_tuple(libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_RESOLVE_MODULE_SPEC,
+                                        tfm::format(_("Unable to resolve 
argument '%s'"), *specs), *specs));
+                }
+            }
+        }
+    }
+
+    return messages;
+}
+
+gboolean
+dnf_context_module_enable(DnfContext * context, const char ** module_specs, 
GError ** error) try
+{
+    DnfContextPrivate *priv = GET_PRIVATE (context);
+
+    /* create sack and add sources */
+    if (priv->sack == nullptr) {
+        dnf_state_reset (priv->state);
+        if (!dnf_context_setup_sack(context, priv->state, error)) {
+            return FALSE;
+        }
+    }
+
+    DnfSack * sack = priv->sack;
+    assert(sack);
+    assert(module_specs);
+
+    auto container = dnf_sack_get_module_container(sack);
+    if (!container) {
+        g_set_error_literal(error, DNF_ERROR, DNF_ERROR_FAILED, _("No modular 
data available"));
+        return FALSE;
+    }
+    std::vector<std::tuple<libdnf::ModulePackageContainer::ModuleErrorType, 
std::string, std::string>> messages;
+
+    std::vector<std::pair<const char *, std::map<std::string, 
std::map<std::string, std::vector<libdnf::ModulePackage *>>>>> 
all_resolved_module_dicts;
+    for (const char ** specs = module_specs; *specs != NULL; ++specs) {
+        auto resolved_spec = resolve_module_spec(*specs, *container);
+        if (!resolved_spec.first) {
+            messages.emplace_back(
+                
std::make_tuple(libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_RESOLVE_MODULE_SPEC,
+                                tfm::format(_("Unable to resolve argument 
'%s'"), *specs), *specs));
+            continue;
+        }
+        if (!resolved_spec.first->getProfile().empty() || 
!resolved_spec.first->getVersion().empty() ||
+            !resolved_spec.first->getContext().empty()) {
+        
messages.emplace_back(std::make_tuple(libdnf::ModulePackageContainer::ModuleErrorType::INFO,
+                                              tfm::format(_("Ignoring unneeded 
information in argument: '%s'"), *specs),
+                                              *specs));
+        }
+        auto module_dict = create_module_dict(resolved_spec.second);
+        auto message = modify_module_dict_and_enable_stream(module_dict, 
*container, true);
+        if (!message.empty()) {
+            messages.insert(
+                messages.end(),std::make_move_iterator(message.begin()), 
std::make_move_iterator(message.end()));
+            messages.emplace_back(
+                
std::make_tuple(libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_RESOLVE_MODULE_SPEC,
+                                tfm::format(_("Unable to resolve argument 
'%s'"), *specs), *specs));
+        } else {
+            all_resolved_module_dicts.emplace_back(make_pair(*specs, 
std::move(module_dict)));
+        }
+    }
+
+    std::vector<const char *> hotfixRepos;
+    // don't filter RPMs from repos with the 'module_hotfixes' flag set
+    for (unsigned int i = 0; i < priv->repos->len; i++) {
+        auto repo = static_cast<DnfRepo *>(g_ptr_array_index(priv->repos, i));
+        if (dnf_repo_get_module_hotfixes(repo)) {
+            hotfixRepos.push_back(dnf_repo_get_id(repo));
+        }
+    }
+    hotfixRepos.push_back(nullptr);
+    auto solver_error = recompute_modular_filtering(container, sack, 
hotfixRepos);
+    if (!solver_error.empty()) {
+        messages.insert(
+            messages.end(),std::make_move_iterator(solver_error.begin()), 
std::make_move_iterator(solver_error.end()));
+    }
+    for (auto & pair : all_resolved_module_dicts) {
+        for (auto module_dict_iter : pair.second) {
+            for (auto & stream_dict_iter : module_dict_iter.second) {
+                try {
+                    container->enableDependencyTree(stream_dict_iter.second);
+                } catch (const 
libdnf::ModulePackageContainer::EnableMultipleStreamsException & exception) {
+                    messages.emplace_back(std::make_tuple(
+                        
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_MODIFY_MULTIPLE_TIMES_MODULE_STATE,
+                        tfm::format(_("Problem during enablement of dependency 
tree for moduele '%1$s' stream '%2$s': %3$s"),
+                                     module_dict_iter.first, 
stream_dict_iter.first, exception.what()), pair.first));
+                    messages.emplace_back(std::make_tuple(
+                        
libdnf::ModulePackageContainer::ModuleErrorType::CANNOT_RESOLVE_MODULE_SPEC,
+                        tfm::format(_("Unable to resolve argument '%s'"), 
pair.first), pair.first));
+                }
+            }
+        }
+    }
+    bool return_error = report_problems(messages);
+
+    if (return_error) {
+        g_set_error_literal(error, DNF_ERROR, DNF_ERROR_FAILED, _("Problems 
appeared for module enable request"));
+        return FALSE;
+    }
+    return TRUE;
+} CATCH_TO_GERROR(FALSE)
+
+static gboolean
+context_modules_reset_or_disable(DnfContext * context, const char ** 
module_specs, GError ** error, bool reset)
+{
+    DnfContextPrivate *priv = GET_PRIVATE (context);
+
+    /* create sack and add sources */
+    if (priv->sack == nullptr) {
+        dnf_state_reset (priv->state);
+        if (!dnf_context_setup_sack(context, priv->state, error)) {
+            return FALSE;
+        }
+    }
+
+    DnfSack * sack = priv->sack;
+    assert(module_specs);
+
+    auto container = dnf_sack_get_module_container(sack);
+    if (!container) {
+        g_set_error_literal(error, DNF_ERROR, DNF_ERROR_FAILED, _("No modular 
data available"));
+        return FALSE;
+    }
+    std::vector<std::tuple<libdnf::ModulePackageContainer::ModuleErrorType, 
std::string, std::string>> messages;
+
+    auto disable_errors = modules_reset_or_disable(*container, module_specs, 
reset);
+    if (!disable_errors.empty()) {
+        messages.insert(
+            messages.end(),
+            std::make_move_iterator(disable_errors.begin()),
+            std::make_move_iterator(disable_errors.end()));
+    }
+
+    std::vector<const char *> hotfixRepos;
+    // don't filter RPMs from repos with the 'module_hotfixes' flag set
+    for (unsigned int i = 0; i < priv->repos->len; i++) {
+        auto repo = static_cast<DnfRepo *>(g_ptr_array_index(priv->repos, i));
+        if (dnf_repo_get_module_hotfixes(repo)) {
+            hotfixRepos.push_back(dnf_repo_get_id(repo));
+        }
+    }
+    hotfixRepos.push_back(nullptr);
+    auto solver_error = recompute_modular_filtering(container, sack, 
hotfixRepos);
+    if (!solver_error.empty()) {
+        messages.insert(
+            messages.end(),std::make_move_iterator(solver_error.begin()), 
std::make_move_iterator(solver_error.end()));
+    }
+    bool return_error = report_problems(messages);
+
+    if (return_error) {
+        if (reset) {
+            g_set_error_literal(error, DNF_ERROR, DNF_ERROR_FAILED, 
_("Problems appeared for module reset request"));
+        } else {
+            g_set_error_literal(error, DNF_ERROR, DNF_ERROR_FAILED, 
_("Problems appeared for module disable request"));
+        }
+        return FALSE;
+    }
+    return TRUE;
+}
+
+
+gboolean
+dnf_context_module_disable(DnfContext * context, const char ** module_specs, 
GError ** error) try
+{
+    return context_modules_reset_or_disable(context, module_specs, error, 
false);
+} CATCH_TO_GERROR(FALSE)
+
+gboolean
+dnf_context_module_reset(DnfContext * context, const char ** module_specs, 
GError ** error) try
+{
+    return context_modules_reset_or_disable(context, module_specs, error, 
true);
+} CATCH_TO_GERROR(FALSE)
+
+gboolean
+dnf_context_module_switched_check(DnfContext * context, GError ** error) try
+{
+    DnfContextPrivate *priv = GET_PRIVATE (context);
+    if (priv->sack == nullptr) {
+        return TRUE;
+    }
+    auto container = dnf_sack_get_module_container(priv->sack);
+    if (!container) {
+        return TRUE;
+    }
+    auto switched = container->getSwitchedStreams();
+    if (switched.empty()) {
+        return TRUE;
+    }
+    auto logger(libdnf::Log::getLogger());
+    const char * msg = _("The operation would result in switching of module 
'%s' stream '%s' to stream '%s'");
+    for (auto item : switched) {
+        logger->warning(tfm::format(msg, item.first.c_str(), 
item.second.first.c_str(), item.second.second.c_str()));
+    }
+    const char * msg_error = _("It is not possible to switch enabled streams 
of a module.\n"
+                       "It is recommended to remove all installed content from 
the module, and "
+                       "reset the module using 'microdnf module reset 
<module_name>' command. After "
+                       "you reset the module, you can install the other 
stream.");
+    g_set_error_literal(error, DNF_ERROR, DNF_ERROR_FAILED, msg_error);
+    return FALSE;
+} CATCH_TO_GERROR(FALSE)
+
 namespace libdnf {
 
 std::map<std::string, std::string> &
@@ -3045,3 +3466,4 @@
 }
 
 }
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/dnf-context.h 
new/libdnf-0.55.0/libdnf/dnf-context.h
--- old/libdnf-0.54.2/libdnf/dnf-context.h      2020-10-06 14:52:22.000000000 
+0200
+++ new/libdnf-0.55.0/libdnf/dnf-context.h      2020-11-09 15:42:13.000000000 
+0100
@@ -271,8 +271,8 @@
                                                          PluginHookId    id,
                                                          DnfPluginHookData 
*hookData,
                                                          DnfPluginError 
*error);
-
-
+/// String must be dealocated by g_free()
+gchar *          dnf_context_get_module_report          (DnfContext * context);
 gboolean         dnf_context_reset_modules              (DnfContext * context,
                                                          DnfSack * sack,
                                                          const char ** 
module_names,
@@ -280,6 +280,63 @@
 gboolean         dnf_context_reset_all_modules          (DnfContext * context,
                                                          DnfSack * sack,
                                                          GError ** error);
+/**
+ * dnf_context_module_enable:
+ * @context: DnfContext
+ * @module_specs: Module specs that should be enabled
+ * @error: Error
+ *
+ * Enable mudules, recalculate module filtration, but do not commit modular 
changes.
+ * To commit modular changes it requires to call dnf_context_run()
+ * Returns FALSE when an error is set.
+ *
+ * Since: 0.55.0
+ **/
+gboolean         dnf_context_module_enable              (DnfContext * context,
+                                                         const char ** 
module_specs,
+                                                         GError ** error);
+/**
+ * dnf_context_module_disable:
+ * @context: DnfContext
+ * @module_specs: Module specs that should be enabled
+ * @error: Error
+ *
+ * Disable mudules, recalculate module filtration, but do not commit modular 
changes.
+ * To commit modular changes it requires to call dnf_context_run()
+ * Returns FALSE when an error is set.
+ *
+ * Since: 0.55.0
+ **/
+gboolean         dnf_context_module_disable             (DnfContext * context,
+                                                         const char ** 
module_specs,
+                                                         GError ** error);
+/**
+ * dnf_context_module_reset:
+ * @context: DnfContext
+ * @module_specs: Module specs that should be enabled
+ * @error: Error
+ *
+ * Reset modules, recalculate module filtration, but do not commit modular 
changes.
+ * To commit modular changes it requires to call dnf_context_run()
+ * Returns FALSE when an error is set.
+ *
+ * Since: 0.55.0
+ **/
+gboolean         dnf_context_module_reset               (DnfContext * context,
+                                                         const char ** 
module_specs,
+                                                         GError ** error);
+/**
+ * dnf_context_module_switched_check:
+ * @context: DnfContext
+ * @error: Error
+ *
+ * Ceck if any module is switched and return FALSE and sets an error
+ * Returns FALSE when an error is set.
+ *
+ * Since: 0.55.0
+ **/
+gboolean         dnf_context_module_switched_check      (DnfContext * context,
+                                                         GError ** error);
 
 G_END_DECLS
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/dnf-sack.cpp 
new/libdnf-0.55.0/libdnf/dnf-sack.cpp
--- old/libdnf-0.54.2/libdnf/dnf-sack.cpp       2020-10-06 14:52:22.000000000 
+0200
+++ new/libdnf-0.55.0/libdnf/dnf-sack.cpp       2020-11-09 15:42:13.000000000 
+0100
@@ -107,6 +107,7 @@
     gboolean             have_set_arch;
     gboolean             all_arch;
     gboolean             provides_ready;
+    gboolean             allow_vendor_change;
     gchar               *cache_dir;
     char                *arch;
     dnf_sack_running_kernel_fn_t  running_kernel_fn;
@@ -189,6 +190,7 @@
     priv->running_kernel_fn = running_kernel;
     priv->considered_uptodate = TRUE;
     priv->cmdline_repo = NULL;
+    priv->allow_vendor_change = TRUE;
     queue_init(&priv->installonly);
 
     /* logging up after this*/
@@ -843,6 +845,46 @@
     return priv->all_arch;
 }
 
+/*
+ * dnf_sack_set_allow_vendor_change:
+ * @sack: a #DnfSack instance.
+ * @allow_vendor_change is a boolean.
+ *
+ * Sets the value of allow vendor change to use for
+ * SOLVER_FLAG_ALLOW_VENDORCHANGE flag
+ *
+ * Returns: Nothing.
+ *
+ * Since: 0.54.3
+ *
+ * */
+void
+dnf_sack_set_allow_vendor_change(DnfSack *sack, gboolean allow_vendor_change)
+{
+    DnfSackPrivate *priv = GET_PRIVATE(sack);
+    priv->allow_vendor_change = allow_vendor_change;
+}
+
+/*
+ * dnf_sack_get_allow_vendor_change:
+ * @sack: a #DnfSack instance.
+ * @allow_vendor_change is a boolean.
+ *
+ * Gets the value of allow vendor change to use for
+ * SOLVER_FLAG_ALLOW_VENDORCHANGE flag
+ *
+ * Returns: True if flag set to 1, False if set to 0
+ *
+ * Since: 0.54.3
+ *
+ * */
+gboolean
+dnf_sack_get_allow_vendor_change(DnfSack *sack)
+{
+    DnfSackPrivate *priv = GET_PRIVATE(sack);
+    return priv->allow_vendor_change;
+}
+
 /**
  * dnf_sack_get_arch
  * @sack: a #DnfSack instance.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/dnf-sack.h 
new/libdnf-0.55.0/libdnf/dnf-sack.h
--- old/libdnf-0.54.2/libdnf/dnf-sack.h 2020-10-06 14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/libdnf/dnf-sack.h 2020-11-09 15:42:13.000000000 +0100
@@ -96,6 +96,9 @@
 void         dnf_sack_set_all_arch          (DnfSack        *sack,
                                              gboolean        all_arch);
 gboolean     dnf_sack_get_all_arch          (DnfSack        *sack);
+void         dnf_sack_set_allow_vendor_change(DnfSack       *sack,
+                                             gboolean       
allow_vendor_change);
+gboolean     dnf_sack_get_allow_vendor_change(DnfSack       *sack);
 void         dnf_sack_set_rootdir           (DnfSack        *sack,
                                              const gchar    *value);
 gboolean     dnf_sack_setup                 (DnfSack        *sack,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/dnf-transaction.cpp 
new/libdnf-0.55.0/libdnf/dnf-transaction.cpp
--- old/libdnf-0.54.2/libdnf/dnf-transaction.cpp        2020-10-06 
14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/libdnf/dnf-transaction.cpp        2020-11-09 
15:42:13.000000000 +0100
@@ -1466,14 +1466,16 @@
 
     /* hmm, nothing was done... */
     if (priv->step != DNF_TRANSACTION_STEP_WRITING) {
-        ret = FALSE;
-        g_set_error(error,
-                    DNF_ERROR,
-                    DNF_ERROR_INTERNAL_ERROR,
-                    _("Transaction did not go to writing phase, "
-                      "but returned no error(%i)"),
-                    priv->step);
-        goto out;
+        if (priv->install->len > 0 || priv->remove->len > 0) {
+            ret = FALSE;
+            g_set_error(error,
+                        DNF_ERROR,
+                        DNF_ERROR_INTERNAL_ERROR,
+                        _("Transaction did not go to writing phase, "
+                        "but returned no error(%i)"),
+                        priv->step);
+            goto out;
+        }
     }
 
     /* this section done */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/goal/Goal.cpp 
new/libdnf-0.55.0/libdnf/goal/Goal.cpp
--- old/libdnf-0.54.2/libdnf/goal/Goal.cpp      2020-10-06 14:52:22.000000000 
+0200
+++ new/libdnf-0.55.0/libdnf/goal/Goal.cpp      2020-11-09 15:42:13.000000000 
+0100
@@ -1167,6 +1167,49 @@
     return pset;
 }
 
+static std::string string_join(const std::vector<std::string> & src, const 
std::string & delim)
+{
+    if (src.empty()) {
+        return {};
+    }
+    std::string output(*src.begin());
+    for (auto iter = std::next(src.begin()); iter != src.end(); ++iter) {
+        output.append(delim);
+        output.append(*iter);
+    }
+    return output;
+}
+
+std::string
+Goal::formatAllProblemRules(const std::vector<std::vector<std::string>> & 
problems)
+{
+    if (problems.empty()) {
+        return {};
+    }
+    bool single_problems = problems.size() == 1;
+    std::string output;
+
+    if (single_problems) {
+        output.append(_("Problem: "));
+        output.append(string_join(*problems.begin(), "\n  - "));
+        return output;
+    }
+
+    const char * problem_prefix = _("Problem %d: ");
+
+    output.append(tfm::format(problem_prefix, 1));
+    output.append(string_join(*problems.begin(), "\n  - "));
+
+    int index = 2;
+    for (auto iter = std::next(problems.begin()); iter != problems.end(); 
++iter) {
+        output.append("\n ");
+        output.append(tfm::format(problem_prefix, index));
+        output.append(string_join(*iter, "\n  - "));
+        ++index;
+    }
+    return output;
+}
+
 void
 Goal::Impl::allowUninstallAllButProtected(Queue *job, DnfGoalActions flags)
 {
@@ -1224,8 +1267,11 @@
         solver_free(solv);
     solv = solvNew;
 
-    /* no vendor locking */
-    solver_set_flag(solv, SOLVER_FLAG_ALLOW_VENDORCHANGE, 1);
+    /* vendor locking */
+    int vendor = dnf_sack_get_allow_vendor_change(sack) ? 1 : 0;
+    solver_set_flag(solv, SOLVER_FLAG_ALLOW_VENDORCHANGE, vendor);
+    solver_set_flag(solv, SOLVER_FLAG_DUP_ALLOW_VENDORCHANGE, vendor);
+
     /* don't erase packages that are no longer in repo during distupgrade */
     solver_set_flag(solv, SOLVER_FLAG_KEEP_ORPHANS, 1);
     /* no arch change for forcebest */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/goal/Goal.hpp 
new/libdnf-0.55.0/libdnf/goal/Goal.hpp
--- old/libdnf-0.54.2/libdnf/goal/Goal.hpp      2020-10-06 14:52:22.000000000 
+0200
+++ new/libdnf-0.55.0/libdnf/goal/Goal.hpp      2020-11-09 15:42:13.000000000 
+0100
@@ -143,6 +143,8 @@
     PackageSet listDowngrades();
     PackageSet listObsoletedByPackage(DnfPackage * pkg);
 
+    /// Concentrate all problems into a string
+    static std::string formatAllProblemRules(const 
std::vector<std::vector<std::string>> & problems);
 private:
     friend Query;
     class Impl;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/hy-package.cpp 
new/libdnf-0.55.0/libdnf/hy-package.cpp
--- old/libdnf-0.54.2/libdnf/hy-package.cpp     2020-10-06 14:52:22.000000000 
+0200
+++ new/libdnf-0.55.0/libdnf/hy-package.cpp     2020-11-09 15:42:13.000000000 
+0100
@@ -419,6 +419,23 @@
 }
 
 /**
+ * dnf_package_get_vendor:
+ * @pkg: a #DnfPackage instance.
+ *
+ * Gets the name for the package.
+ *
+ * Returns: a string, or %NULL
+ *
+ * Since: 0.54.4
+ */
+const char *
+dnf_package_get_vendor(DnfPackage *pkg)
+{
+    Pool *pool = dnf_package_get_pool(pkg);
+    return pool_id2str(pool, get_solvable(pkg)->vendor);
+}
+
+/**
  * dnf_package_get_packager:
  * @pkg: a #DnfPackage instance.
  *
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/hy-package.h 
new/libdnf-0.55.0/libdnf/hy-package.h
--- old/libdnf-0.54.2/libdnf/hy-package.h       2020-10-06 14:52:22.000000000 
+0200
+++ new/libdnf-0.55.0/libdnf/hy-package.h       2020-11-09 15:42:13.000000000 
+0100
@@ -62,6 +62,7 @@
 
 Id           dnf_package_get_id         (DnfPackage *pkg);
 const char  *dnf_package_get_name       (DnfPackage *pkg);
+const char  *dnf_package_get_vendor     (DnfPackage *pkg);
 const char  *dnf_package_get_arch       (DnfPackage *pkg);
 const unsigned char *dnf_package_get_chksum(DnfPackage *pkg, int *type);
 const char  *dnf_package_get_description(DnfPackage *pkg);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libdnf-0.54.2/libdnf/module/ModulePackageContainer.cpp 
new/libdnf-0.55.0/libdnf/module/ModulePackageContainer.cpp
--- old/libdnf-0.54.2/libdnf/module/ModulePackageContainer.cpp  2020-10-06 
14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/libdnf/module/ModulePackageContainer.cpp  2020-11-09 
15:42:13.000000000 +0100
@@ -607,7 +607,8 @@
 {
     if (modules.empty()) {
         activatedModules.reset();
-        return {};
+        return std::make_pair(std::vector<std::vector<std::string>>(),
+                              
ModulePackageContainer::ModuleErrorType::NO_ERROR);
     }
     dnf_sack_recompute_considered(moduleSack);
     dnf_sack_make_provides_ready(moduleSack);
@@ -822,9 +823,40 @@
 ModulePackageContainer::getReport()
 {
     std::string report;
+
+    auto installedProfiles = getInstalledProfiles();
+    if (!installedProfiles.empty()) {
+        report += _("Installing module profiles:\n");
+        for (auto & item: installedProfiles) {
+            for (auto & profile:item.second) {
+                report += "    ";
+                report += item.first;
+                report += ":";
+                report += profile;
+                report += "\n";
+            }
+        }
+        report += "\n";
+    }
+
+    auto removedProfiles = getRemovedProfiles();
+    if (!removedProfiles.empty()) {
+        report += _("Disabling module profiles:\n");
+        for (auto & item: removedProfiles) {
+            for (auto & profile:item.second) {
+                report += "    ";
+                report += item.first;
+                report += ":";
+                report += profile;
+                report += "\n";
+            }
+        }
+        report += "\n";
+    }
+
     auto enabled = getEnabledStreams();
     if (!enabled.empty()) {
-        report += "Module Enabling:\n";
+        report += _("Enabling module streams:\n");
         for (auto & item: enabled) {
             report += "    ";
             report += item.first;
@@ -834,20 +866,11 @@
         }
         report += "\n";
     }
-    auto disabled = getDisabledModules();
-    if (!disabled.empty()) {
-        report += "Module Disabling:\n";
-        for (auto & name: disabled) {
-            report += "    ";
-            report += name;
-            report += "\n";
-        }
-        report += "\n";
-    }
+
     auto switchedStreams = getSwitchedStreams();
     if (!switchedStreams.empty()) {
         std::string switchedReport;
-        switchedReport += "Module Switched Streams:\n";
+        switchedReport += _("Switching module streams:\n");
         for (auto & item: switchedStreams) {
             switchedReport += "    ";
             switchedReport += item.first;
@@ -862,31 +885,25 @@
         report += switchedReport;
         report += "\n";
     }
-    auto installedProfiles = getInstalledProfiles();
-    if (!installedProfiles.empty()) {
-        report += "Module Installing Profiles:\n";
-        for (auto & item: installedProfiles) {
-            for (auto & profile:item.second) {
-                report += "    ";
-                report += item.first;
-                report += ":";
-                report += profile;
-                report += "\n";
-            }
+
+    auto disabled = getDisabledModules();
+    if (!disabled.empty()) {
+        report += _("Disabling modules:\n");
+        for (auto & name: disabled) {
+            report += "    ";
+            report += name;
+            report += "\n";
         }
         report += "\n";
     }
-    auto removedProfiles = getRemovedProfiles();
-    if (!removedProfiles.empty()) {
-        report += "Module Removing Profiles:\n";
-        for (auto & item: removedProfiles) {
-            for (auto & profile:item.second) {
-                report += "    ";
-                report += item.first;
-                report += ":";
-                report += profile;
-                report += "\n";
-            }
+
+    auto reset = getResetModules();
+    if (!reset.empty()) {
+        report += _("Resetting modules:\n");
+        for (auto & name: reset) {
+            report += "    ";
+            report += name;
+            report += "\n";
         }
         report += "\n";
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libdnf-0.54.2/libdnf/module/ModulePackageContainer.hpp 
new/libdnf-0.55.0/libdnf/module/ModulePackageContainer.hpp
--- old/libdnf-0.54.2/libdnf/module/ModulePackageContainer.hpp  2020-10-06 
14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/libdnf/module/ModulePackageContainer.hpp  2020-11-09 
15:42:13.000000000 +0100
@@ -47,9 +47,16 @@
     
     enum class ModuleErrorType {
         NO_ERROR = 0,
+        INFO,
+        /// Error in module defaults detected during resovement of module 
dependencies
         ERROR_IN_DEFAULTS,
+        /// Error detected during resovement of module dependencies
         ERROR,
-        CANNOT_RESOLVE_MODULES
+        /// Error detected during resovement of module dependencies - 
Unexpected error!!!
+        CANNOT_RESOLVE_MODULES,
+        CANNOT_RESOLVE_MODULE_SPEC,
+        CANNOT_ENABLE_MULTIPLE_STREAMS,
+        CANNOT_MODIFY_MULTIPLE_TIMES_MODULE_STATE
     };
     
     struct Exception : public std::runtime_error
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/sack/query.cpp 
new/libdnf-0.55.0/libdnf/sack/query.cpp
--- old/libdnf-0.54.2/libdnf/sack/query.cpp     2020-10-06 14:52:22.000000000 
+0200
+++ new/libdnf-0.55.0/libdnf/sack/query.cpp     2020-11-09 15:42:13.000000000 
+0100
@@ -2143,6 +2143,7 @@
         return;
 
     Pool *pool = dnf_sack_get_pool(sack);
+    repo_internalize_all_trigger(pool);
     Map m;
     if (!result)
         initResult();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/utils/CMakeLists.txt 
new/libdnf-0.55.0/libdnf/utils/CMakeLists.txt
--- old/libdnf-0.54.2/libdnf/utils/CMakeLists.txt       2020-10-06 
14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/libdnf/utils/CMakeLists.txt       2020-11-09 
15:42:13.000000000 +0100
@@ -13,6 +13,7 @@
     ${CMAKE_CURRENT_SOURCE_DIR}/File.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/CompressedFile.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/logger.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/GLibLogger.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/os-release.cpp
     PARENT_SCOPE
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/utils/GLibLogger.cpp 
new/libdnf-0.55.0/libdnf/utils/GLibLogger.cpp
--- old/libdnf-0.54.2/libdnf/utils/GLibLogger.cpp       1970-01-01 
01:00:00.000000000 +0100
+++ new/libdnf-0.55.0/libdnf/utils/GLibLogger.cpp       2020-11-09 
15:42:13.000000000 +0100
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 Red Hat, Inc.
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library 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.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <GLibLogger.hpp>
+
+#include <glib.h>
+
+namespace libdnf {
+
+static inline void write_g_log(const std::string & domain, Logger::Level 
level, const std::string & message)
+{
+    GLogLevelFlags gLogLevel;
+    switch (level) {
+        // In GLib, the "G_LOG_LEVEL_ERROR" is more severe than 
"G_LOG_LEVEL_CRITICAL".
+        // In fact, the "G_LOG_LEVEL_ERROR" is always fatal. We won't use it 
now.
+        case Logger::Level::CRITICAL:
+            gLogLevel = G_LOG_LEVEL_CRITICAL;
+            break;
+        case Logger::Level::ERROR:
+            gLogLevel = G_LOG_LEVEL_CRITICAL;
+            break;
+
+        case Logger::Level::WARNING:
+            gLogLevel = G_LOG_LEVEL_WARNING;
+            break;
+        case Logger::Level::NOTICE:
+            gLogLevel = G_LOG_LEVEL_MESSAGE;
+            break;
+        case Logger::Level::INFO:
+            gLogLevel = G_LOG_LEVEL_INFO;
+            break;
+        case Logger::Level::DEBUG:
+        case Logger::Level::TRACE:
+        default:
+            gLogLevel = G_LOG_LEVEL_DEBUG;
+            break;
+    }
+    g_log(domain.c_str(), gLogLevel, "%s", message.c_str());
+}
+
+void GLibLogger::write(int /*source*/, Level level, const std::string & 
message)
+{
+    write_g_log(domain, level, message);
+}
+
+void GLibLogger::write(int /*source*/, time_t /*time*/, pid_t /*pid*/, Level 
level, const std::string & message)
+{
+    write_g_log(domain, level, message);
+}
+
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf/utils/GLibLogger.hpp 
new/libdnf-0.55.0/libdnf/utils/GLibLogger.hpp
--- old/libdnf-0.54.2/libdnf/utils/GLibLogger.hpp       1970-01-01 
01:00:00.000000000 +0100
+++ new/libdnf-0.55.0/libdnf/utils/GLibLogger.hpp       2020-11-09 
15:42:13.000000000 +0100
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 Red Hat, Inc.
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library 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.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _GLIB_LOGGER_HPP_
+#define _GLIB_LOGGER_HPP_
+
+#include <logger.hpp>
+
+namespace libdnf {
+
+class GLibLogger : public Logger {
+public:
+    explicit GLibLogger(std::string domain) : domain(domain) {}
+    void write(int source, Level level, const std::string & message) override;
+    void write(int source, time_t time, pid_t pid, Level level, const 
std::string & message) override;
+
+private:
+    std::string domain;
+};
+
+}
+
+#endif // _GLIB_LOGGER_HPP_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/libdnf.spec 
new/libdnf-0.55.0/libdnf.spec
--- old/libdnf-0.54.2/libdnf.spec       2020-10-06 14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/libdnf.spec       2020-11-09 15:42:13.000000000 +0100
@@ -1,13 +1,11 @@
-%define __cmake_in_source_build 1
-
 %global libsolv_version 0.7.7
 %global libmodulemd_version 2.5.0
 %global librepo_version 1.12.0
 %global dnf_conflict 4.3.0
 %global swig_version 3.0.12
 %global libdnf_major_version 0
-%global libdnf_minor_version 54
-%global libdnf_micro_version 2
+%global libdnf_minor_version 55
+%global libdnf_micro_version 0
 
 %define __cmake_in_source_build 1
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/python/hawkey/package-py.cpp 
new/libdnf-0.55.0/python/hawkey/package-py.cpp
--- old/libdnf-0.54.2/python/hawkey/package-py.cpp      2020-10-06 
14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/python/hawkey/package-py.cpp      2020-11-09 
15:42:13.000000000 +0100
@@ -302,6 +302,7 @@
     {(char*)"release",  (getter)get_str, NULL, NULL,
      (void *)dnf_package_get_release},
     {(char*)"name", (getter)get_str, NULL, NULL, (void *)dnf_package_get_name},
+    {(char*)"vendor", (getter)get_str, NULL, NULL, (void 
*)dnf_package_get_vendor},
     {(char*)"arch", (getter)get_str, NULL, NULL, (void *)dnf_package_get_arch},
     {(char*)"hdr_chksum", (getter)get_chksum, NULL, NULL,
      (void *)dnf_package_get_hdr_chksum},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/python/hawkey/sack-py.cpp 
new/libdnf-0.55.0/python/hawkey/sack-py.cpp
--- old/libdnf-0.54.2/python/hawkey/sack-py.cpp 2020-10-06 14:52:22.000000000 
+0200
+++ new/libdnf-0.55.0/python/hawkey/sack-py.cpp 2020-11-09 15:42:13.000000000 
+0100
@@ -415,10 +415,22 @@
     Py_RETURN_NONE;
 } CATCH_TO_PYTHON
 
+static int
+set_allow_vendor_change(_SackObject *self, PyObject *obj, void *unused) try
+{
+    gboolean vendor = PyObject_IsTrue(obj);
+    if (PyErr_Occurred())
+        return -1;
+    dnf_sack_set_allow_vendor_change(self->sack, vendor);
+    return 0;
+} CATCH_TO_PYTHON_INT
+
 static PyGetSetDef sack_getsetters[] = {
     {(char*)"cache_dir",        (getter)get_cache_dir, NULL, NULL, NULL},
     {(char*)"installonly",        NULL, (setter)set_installonly, NULL, NULL},
     {(char*)"installonly_limit",        NULL, (setter)set_installonly_limit, 
NULL, NULL},
+    {(char*)"allow_vendor_change", NULL,
+                                    (setter)set_allow_vendor_change, NULL, 
NULL},
     {(char*)"_moduleContainer",        (getter)get_module_container, 
(setter)set_module_container,
         NULL, NULL},
     {NULL}                        /* sentinel */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/tests/hawkey/CMakeLists.txt 
new/libdnf-0.55.0/tests/hawkey/CMakeLists.txt
--- old/libdnf-0.54.2/tests/hawkey/CMakeLists.txt       2020-10-06 
14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/tests/hawkey/CMakeLists.txt       2020-11-09 
15:42:13.000000000 +0100
@@ -28,7 +28,7 @@
 set_target_properties(test_hawkey_main PROPERTIES COMPILE_FLAGS -fPIC)
 target_link_libraries(test_hawkey_main
     libdnf
-    ${CHECK_LIBRARIES}
+    ${CHECK_LDFLAGS}
     ${SOLV_LIBRARY}
     ${SOLVEXT_LIBRARY}
     ${RPMDB_LIBRARY}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libdnf-0.54.2/tests/hawkey/test_package.cpp 
new/libdnf-0.55.0/tests/hawkey/test_package.cpp
--- old/libdnf-0.54.2/tests/hawkey/test_package.cpp     2020-10-06 
14:52:22.000000000 +0200
+++ new/libdnf-0.55.0/tests/hawkey/test_package.cpp     2020-11-09 
15:42:13.000000000 +0100
@@ -91,6 +91,18 @@
 }
 END_TEST
 
+START_TEST(test_vendor)
+{
+    DnfSack *sack = test_globals.sack;
+    DnfPackage *pkg = by_name_repo(sack, "foolish-grin", "vendor");
+    fail_if(pkg == NULL);
+    const char *vendor = dnf_package_get_vendor(pkg);
+
+    ck_assert_str_eq(vendor, "PerfectlyLoud");
+    g_object_unref(pkg);
+}
+END_TEST
+
 START_TEST(test_no_sourcerpm)
 {
     DnfSack *sack = test_globals.sack;
@@ -379,5 +391,10 @@
     tcase_add_test(tc, test_get_files_cmdline);
     suite_add_tcase(s, tc);
 
+    tc = tcase_create("Vendor");
+    tcase_add_unchecked_fixture(tc, fixture_with_vendor, teardown);
+    tcase_add_test(tc, test_vendor);
+    suite_add_tcase(s, tc);
+
     return s;
 }
_______________________________________________
openSUSE Commits mailing list -- commit@lists.opensuse.org
To unsubscribe, email commit-le...@lists.opensuse.org
List Netiquette: https://en.opensuse.org/openSUSE:Mailing_list_netiquette
List Archives: 
https://lists.opensuse.org/archives/list/commit@lists.opensuse.org

Reply via email to