diff --git a/SOURCES/0026-context-Substitute-all-repository-config-options-RhB.patch b/SOURCES/0026-context-Substitute-all-repository-config-options-RhB.patch
new file mode 100644
index 0000000..340d2c5
--- /dev/null
+++ b/SOURCES/0026-context-Substitute-all-repository-config-options-RhB.patch
@@ -0,0 +1,107 @@
+From 7d8f9cfcdf7725fef2c99ecb2dedcdff1e9506d7 Mon Sep 17 00:00:00 2001
+From: Jaroslav Rohel <jrohel@redhat.com>
+Date: Wed, 13 Apr 2022 12:26:10 +0200
+Subject: [PATCH 26/34] context: Substitute all repository config options
+ (RhBug:2076853)
+
+It also solves the problem: Substitution of variables in `baseurl`
+does not work in microdnf and PackageKit unless `metalink` or `mirrorlist`
+is set at the same time.
+---
+ libdnf/dnf-repo.cpp | 34 +++++++++++++++++++++++++---------
+ 1 file changed, 25 insertions(+), 9 deletions(-)
+
+diff --git a/libdnf/dnf-repo.cpp b/libdnf/dnf-repo.cpp
+index 710045fb..9d42e3e3 100644
+--- a/libdnf/dnf-repo.cpp
++++ b/libdnf/dnf-repo.cpp
+@@ -83,6 +83,7 @@ typedef struct
+     LrHandle        *repo_handle;
+     LrResult        *repo_result;
+     LrUrlVars       *urlvars;
++    bool            unit_test_mode;  /* ugly hack for unit tests */
+ } DnfRepoPrivate;
+ 
+ G_DEFINE_TYPE_WITH_PRIVATE(DnfRepo, dnf_repo, G_TYPE_OBJECT)
+@@ -847,8 +848,11 @@ dnf_repo_conf_reset(libdnf::ConfigRepo &config)
+ 
+ /* Loads repository configuration from GKeyFile */
+ static void
+-dnf_repo_conf_from_gkeyfile(libdnf::ConfigRepo &config, const char *repoId, GKeyFile *gkeyFile)
++dnf_repo_conf_from_gkeyfile(DnfRepo *repo, const char *repoId, GKeyFile *gkeyFile)
+ {
++    DnfRepoPrivate *priv = GET_PRIVATE(repo);
++    auto & config = *priv->repo->getConfig();
++
+     // Reset to the initial state before reloading the configuration.
+     dnf_repo_conf_reset(config);
+ 
+@@ -883,20 +887,31 @@ dnf_repo_conf_from_gkeyfile(libdnf::ConfigRepo &config, const char *repoId, GKey
+                         // list can be ['value1', 'value2, value3'] therefore we first join
+                         // to have 'value1, value2, value3'
+                         g_autofree gchar * tmp_strval = g_strjoinv(",", list);
++
++                        // Substitute vars.
++                        g_autofree gchar *subst_value = dnf_repo_substitute(repo, tmp_strval);
++
++                        if (strcmp(key, "baseurl") == 0 && strstr(tmp_strval, "file://$testdatadir") != NULL) {
++                            priv->unit_test_mode = true;
++                        }
++
+                         try {
+-                            optionItem.newString(libdnf::Option::Priority::REPOCONFIG, tmp_strval);
++                            optionItem.newString(libdnf::Option::Priority::REPOCONFIG, subst_value);
+                         } catch (const std::exception & ex) {
+-                            g_debug("Invalid configuration value: %s = %s in %s; %s", key, value.c_str(), repoId, ex.what());
++                            g_debug("Invalid configuration value: %s = %s in %s; %s", key, subst_value, repoId, ex.what());
+                         }
+                     }
+ 
+                 } else {
+-
+                     // process other (non list) options
++
++                    // Substitute vars.
++                    g_autofree gchar *subst_value = dnf_repo_substitute(repo, value.c_str());
++
+                     try {
+-                        optionItem.newString(libdnf::Option::Priority::REPOCONFIG, value);
++                        optionItem.newString(libdnf::Option::Priority::REPOCONFIG, subst_value);
+                     } catch (const std::exception & ex) {
+-                        g_debug("Invalid configuration value: %s = %s in %s; %s", key, value.c_str(), repoId, ex.what());
++                        g_debug("Invalid configuration value: %s = %s in %s; %s", key, subst_value, repoId, ex.what());
+                     }
+ 
+                 }
+@@ -950,7 +965,7 @@ dnf_repo_set_keyfile_data(DnfRepo *repo, gboolean reloadFromGKeyFile, GError **e
+ 
+     // Reload repository configuration from keyfile.
+     if (reloadFromGKeyFile) {
+-        dnf_repo_conf_from_gkeyfile(*conf, repoId, priv->keyfile);
++        dnf_repo_conf_from_gkeyfile(repo, repoId, priv->keyfile);
+         dnf_repo_apply_setopts(*conf, repoId);
+     }
+ 
+@@ -996,8 +1011,9 @@ dnf_repo_set_keyfile_data(DnfRepo *repo, gboolean reloadFromGKeyFile, GError **e
+         g_autofree gchar *url = NULL;
+         url = lr_prepend_url_protocol(baseurls[0]);
+         if (url != NULL && strncasecmp(url, "file://", 7) == 0) {
+-            if (g_strstr_len(url, -1, "$testdatadir") == NULL)
++            if (!priv->unit_test_mode) {
+                 priv->kind = DNF_REPO_KIND_LOCAL;
++            }
+             g_free(priv->location);
+             g_free(priv->keyring);
+             priv->location = dnf_repo_substitute(repo, url + 7);
+@@ -1224,7 +1240,7 @@ dnf_repo_setup(DnfRepo *repo, GError **error) try
+     auto repoId = priv->repo->getId().c_str();
+ 
+     auto conf = priv->repo->getConfig();
+-    dnf_repo_conf_from_gkeyfile(*conf, repoId, priv->keyfile);
++    dnf_repo_conf_from_gkeyfile(repo, repoId, priv->keyfile);
+     dnf_repo_apply_setopts(*conf, repoId);
+ 
+     auto sslverify = conf->sslverify().getValue();
+-- 
+2.31.1
+
diff --git a/SOURCES/0027-Use-environment-variable-in-unittest-instead-of-ugly.patch b/SOURCES/0027-Use-environment-variable-in-unittest-instead-of-ugly.patch
new file mode 100644
index 0000000..31fb712
--- /dev/null
+++ b/SOURCES/0027-Use-environment-variable-in-unittest-instead-of-ugly.patch
@@ -0,0 +1,50 @@
+From 074ca4cf643c79b8ec3db89a7fd5580ba387eb4d Mon Sep 17 00:00:00 2001
+From: Jaroslav Rohel <jrohel@redhat.com>
+Date: Wed, 20 Apr 2022 08:22:30 +0200
+Subject: [PATCH 27/34] Use environment variable in unittest instead of ugly
+ hack in libdnf
+
+Libdnf contains hacks for unit tests. This removes one hack.
+---
+ libdnf/dnf-repo.cpp          | 3 ---
+ tests/libdnf/dnf-self-test.c | 3 +++
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/libdnf/dnf-repo.cpp b/libdnf/dnf-repo.cpp
+index 9d42e3e3..c015d7fd 100644
+--- a/libdnf/dnf-repo.cpp
++++ b/libdnf/dnf-repo.cpp
+@@ -1191,7 +1191,6 @@ dnf_repo_setup(DnfRepo *repo, GError **error) try
+     DnfRepoEnabled enabled = DNF_REPO_ENABLED_NONE;
+     g_autofree gchar *basearch = NULL;
+     g_autofree gchar *release = NULL;
+-    g_autofree gchar *testdatadir = NULL;
+ 
+     basearch = g_key_file_get_string(priv->keyfile, "general", "arch", NULL);
+     if (basearch == NULL)
+@@ -1230,8 +1229,6 @@ dnf_repo_setup(DnfRepo *repo, GError **error) try
+     for (const auto & item : libdnf::dnf_context_get_vars(priv->context))
+         priv->urlvars = lr_urlvars_set(priv->urlvars, item.first.c_str(), item.second.c_str());
+ 
+-    testdatadir = dnf_realpath(TESTDATADIR);
+-    priv->urlvars = lr_urlvars_set(priv->urlvars, "testdatadir", testdatadir);
+     if (!lr_handle_setopt(priv->repo_handle, error, LRO_VARSUB, priv->urlvars))
+         return FALSE;
+     if (!lr_handle_setopt(priv->repo_handle, error, LRO_GNUPGHOMEDIR, priv->keyring))
+diff --git a/tests/libdnf/dnf-self-test.c b/tests/libdnf/dnf-self-test.c
+index 52958371..906f0e21 100644
+--- a/tests/libdnf/dnf-self-test.c
++++ b/tests/libdnf/dnf-self-test.c
+@@ -1225,6 +1225,9 @@ main(int argc, char **argv)
+     g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
+     g_log_set_always_fatal (G_LOG_FATAL_MASK);
+ 
++    /* Sets a variable to replace in repository configurations. */
++    g_setenv("DNF_VAR_testdatadir", TESTDATADIR, TRUE);
++
+     /* tests go here */
+     g_test_add_func("/libdnf/repo_loader{gpg-asc}", dnf_repo_loader_gpg_asc_func);
+     g_test_add_func("/libdnf/repo_loader{gpg-wrong-asc}", dnf_repo_loader_gpg_wrong_asc_func);
+-- 
+2.31.1
+
diff --git a/SOURCES/0028-Add-private-API-for-filling-reading-and-verifying-ne.patch b/SOURCES/0028-Add-private-API-for-filling-reading-and-verifying-ne.patch
new file mode 100644
index 0000000..dfbfcbd
--- /dev/null
+++ b/SOURCES/0028-Add-private-API-for-filling-reading-and-verifying-ne.patch
@@ -0,0 +1,169 @@
+From 983aeea57d75494fd4ea2ff2903f966136278c15 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= <amatej@redhat.com>
+Date: Wed, 9 Feb 2022 13:17:00 +0100
+Subject: [PATCH 28/34] Add private API for filling, reading and verifying new
+ dnf solv userdata
+
+---
+ libdnf/hy-iutil-private.hpp |  24 +++++++++
+ libdnf/hy-iutil.cpp         | 102 ++++++++++++++++++++++++++++++++++++
+ 2 files changed, 126 insertions(+)
+
+diff --git a/libdnf/hy-iutil-private.hpp b/libdnf/hy-iutil-private.hpp
+index e07b1b51..d498c032 100644
+--- a/libdnf/hy-iutil-private.hpp
++++ b/libdnf/hy-iutil-private.hpp
+@@ -24,6 +24,30 @@
+ #include "hy-iutil.h"
+ #include "hy-types.h"
+ #include "sack/packageset.hpp"
++#include <array>
++#include <utility>
++
++// Use 8 bytes for libsolv version (API: solv_toolversion)
++// to be future proof even though it currently is "1.2"
++static constexpr const size_t solv_userdata_solv_toolversion_size{8};
++static constexpr const std::array<char, 4> solv_userdata_magic{'\0', 'd', 'n', 'f'};
++static constexpr const std::array<char, 4> solv_userdata_dnf_version{'\0', '1', '.', '0'};
++
++static constexpr const int solv_userdata_size = solv_userdata_solv_toolversion_size + \
++                                                   solv_userdata_magic.size() + \
++                                                   solv_userdata_dnf_version.size() + \
++                                                   CHKSUM_BYTES;
++
++struct SolvUserdata {
++    char dnf_magic[solv_userdata_magic.size()];
++    char dnf_version[solv_userdata_dnf_version.size()];
++    char libsolv_version[solv_userdata_solv_toolversion_size];
++    unsigned char checksum[CHKSUM_BYTES];
++}__attribute__((packed)); ;
++
++int solv_userdata_fill(SolvUserdata *solv_userdata, const unsigned char *checksum, GError** error);
++std::unique_ptr<SolvUserdata> solv_userdata_read(FILE *fp);
++int solv_userdata_verify(const SolvUserdata *solv_userdata, const unsigned char *checksum);
+ 
+ /* crypto utils */
+ int checksum_cmp(const unsigned char *cs1, const unsigned char *cs2);
+diff --git a/libdnf/hy-iutil.cpp b/libdnf/hy-iutil.cpp
+index 2af13197..f81ca52f 100644
+--- a/libdnf/hy-iutil.cpp
++++ b/libdnf/hy-iutil.cpp
+@@ -43,6 +43,7 @@ extern "C" {
+ #include <solv/evr.h>
+ #include <solv/solver.h>
+ #include <solv/solverdebug.h>
++#include <solv/repo_solv.h>
+ #include <solv/util.h>
+ #include <solv/pool_parserpmrichdep.h>
+ }
+@@ -182,6 +183,107 @@ int checksum_write(const unsigned char *cs, FILE *fp)
+     return 0;
+ }
+ 
++static std::array<char, solv_userdata_solv_toolversion_size>
++get_padded_solv_toolversion()
++{
++    std::array<char, solv_userdata_solv_toolversion_size> padded_solv_toolversion{};
++    std::string solv_ver_str{solv_toolversion};
++    std::copy(solv_ver_str.rbegin(), solv_ver_str.rend(), padded_solv_toolversion.rbegin());
++
++    return padded_solv_toolversion;
++}
++
++int
++solv_userdata_fill(SolvUserdata *solv_userdata, const unsigned char *checksum, GError** error)
++{
++    if (strlen(solv_toolversion) > solv_userdata_solv_toolversion_size) {
++        g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR,
++                    _("Libsolv's solv_toolversion is: %zu long but we expect max of: %zu"),
++                    strlen(solv_toolversion), solv_userdata_solv_toolversion_size);
++        return 1;
++    }
++
++    // copy dnf solv file magic
++    memcpy(solv_userdata->dnf_magic, solv_userdata_magic.data(), solv_userdata_magic.size());
++
++    // copy dnf solv file version
++    memcpy(solv_userdata->dnf_version, solv_userdata_dnf_version.data(), solv_userdata_dnf_version.size());
++
++    // copy libsolv solv file version
++    memcpy(solv_userdata->libsolv_version, get_padded_solv_toolversion().data(), solv_userdata_solv_toolversion_size);
++
++    // copy checksum
++    memcpy(solv_userdata->checksum, checksum, CHKSUM_BYTES);
++
++    return 0;
++}
++
++
++std::unique_ptr<SolvUserdata>
++solv_userdata_read(FILE *fp)
++{
++    unsigned char *dnf_solvfile_userdata_read = NULL;
++    int dnf_solvfile_userdata_len_read;
++    if (!fp) {
++        return nullptr;
++    }
++
++    int ret_code = solv_read_userdata(fp, &dnf_solvfile_userdata_read, &dnf_solvfile_userdata_len_read);
++    // The userdata layout has to match our struct exactly so we can just cast the memory
++    // allocated by libsolv
++    std::unique_ptr<SolvUserdata> uniq_userdata(reinterpret_cast<SolvUserdata *>(dnf_solvfile_userdata_read));
++    if(ret_code) {
++        g_warning("Failed to read solv userdata: solv_read_userdata returned: %i", ret_code);
++        return nullptr;
++    }
++
++    if (dnf_solvfile_userdata_len_read != solv_userdata_size) {
++        g_warning("Solv userdata length mismatch, read: %i vs expected: %i",
++                  dnf_solvfile_userdata_len_read, solv_userdata_size);
++        return nullptr;
++    }
++
++    return uniq_userdata;
++}
++
++gboolean
++solv_userdata_verify(const SolvUserdata *solv_userdata, const unsigned char *checksum)
++{
++    // check dnf solvfile magic bytes
++    if (memcmp(solv_userdata->dnf_magic, solv_userdata_magic.data(), solv_userdata_magic.size()) != 0) {
++        // This is not dnf header do not read after it
++        g_warning("magic bytes don't match, read: %s vs. dnf solvfile magic: %s",
++                  solv_userdata->dnf_magic, solv_userdata_magic.data());
++        return FALSE;
++    }
++
++    // check dnf solvfile version
++    if (memcmp(solv_userdata->dnf_version, solv_userdata_dnf_version.data(), solv_userdata_dnf_version.size()) != 0) {
++        // Mismatching dnf solvfile version -> we need to regenerate
++        g_warning("dnf solvfile version doesn't match, read: %s vs. dnf solvfile version: %s",
++                  solv_userdata->dnf_version, solv_userdata_dnf_version.data());
++        return FALSE;
++    }
++
++    // check libsolv solvfile version
++    if (memcmp(solv_userdata->libsolv_version, get_padded_solv_toolversion().data(), solv_userdata_solv_toolversion_size) != 0) {
++        // Mismatching libsolv solvfile version -> we need to regenerate
++        g_warning("libsolv solvfile version doesn't match, read: %s vs. libsolv version: %s",
++                  solv_userdata->libsolv_version, solv_toolversion);
++        return FALSE;
++    }
++
++    // check solvfile checksum
++    if (checksum_cmp(solv_userdata->checksum, checksum)) {
++        // Mismatching solvfile checksum -> we need to regenerate
++        g_debug("solvfile checksum doesn't match, read: %s vs. repomd checksum: %s",
++                solv_userdata->checksum, checksum);
++        return FALSE;
++    }
++
++    return TRUE;
++}
++
+ int
+ checksum_type2length(int type)
+ {
+-- 
+2.31.1
+
diff --git a/SOURCES/0029-Use-dnf-solv-userdata-to-check-versions-and-checksum.patch b/SOURCES/0029-Use-dnf-solv-userdata-to-check-versions-and-checksum.patch
new file mode 100644
index 0000000..fd7feff
--- /dev/null
+++ b/SOURCES/0029-Use-dnf-solv-userdata-to-check-versions-and-checksum.patch
@@ -0,0 +1,417 @@
+From 465a6a59279bd7fa2680c626ca0f10c059276668 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= <amatej@redhat.com>
+Date: Wed, 9 Feb 2022 13:18:41 +0100
+Subject: [PATCH 29/34] Use dnf solv userdata to check versions and checksum
+ (RhBug:2027445)
+
+Remove unused functions for checksums
+
+= changelog =
+msg: Write and check versions and checksums for solvfile cache by using new dnf solvfile userdata (RhBug:2027445)
+     It is not possible to use old cache files, therefore cache regeneration is triggered automatically.
+type: bugfix
+resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2027445
+---
+ libdnf/dnf-sack.cpp         | 254 ++++++++++++++++++++++--------------
+ libdnf/hy-iutil-private.hpp |   2 -
+ libdnf/hy-iutil.cpp         |  20 ---
+ 3 files changed, 156 insertions(+), 120 deletions(-)
+
+diff --git a/libdnf/dnf-sack.cpp b/libdnf/dnf-sack.cpp
+index b9baeaef..61f4807c 100644
+--- a/libdnf/dnf-sack.cpp
++++ b/libdnf/dnf-sack.cpp
+@@ -225,17 +225,39 @@ dnf_sack_new(void)
+     return DNF_SACK(g_object_new(DNF_TYPE_SACK, NULL));
+ }
+ 
+-static int
+-can_use_repomd_cache(FILE *fp_solv, unsigned char cs_repomd[CHKSUM_BYTES])
+-{
+-    unsigned char cs_cache[CHKSUM_BYTES];
+-
+-    if (fp_solv &&
+-        !checksum_read(cs_cache, fp_solv) &&
+-        !checksum_cmp(cs_cache, cs_repomd))
+-        return 1;
++// Try to load cached solv file into repo otherwise return FALSE
++static gboolean
++try_to_use_cached_solvfile(const char *path, Repo *repo, int flags, const unsigned char *checksum, GError **err){
++    FILE *fp_cache = fopen(path, "r");
++    if (!fp_cache) {
++        // Missing cache files (ENOENT) are not an error and can even be expected in some cases
++        // (such as when repo doesn't have updateinfo/prestodelta metadata).
++        // Use g_debug in order not to pollute the log by default with such entries.
++        if (errno == ENOENT) {
++            g_debug("Failed to open solvfile cache: %s: %s", path, strerror(errno));
++        } else {
++            g_warning("Failed to open solvfile cache: %s: %s", path, strerror(errno));
++        }
++        return FALSE;
++    }
++    std::unique_ptr<SolvUserdata> solv_userdata = solv_userdata_read(fp_cache);
++    gboolean ret = TRUE;
++    if (solv_userdata && solv_userdata_verify(solv_userdata.get(), checksum)) {
++        // after reading the header rewind to the begining
++        fseek(fp_cache, 0, SEEK_SET);
++        if (repo_add_solv(repo, fp_cache, flags)) {
++            g_set_error (err,
++                         DNF_ERROR,
++                         DNF_ERROR_INTERNAL_ERROR,
++                         _("repo_add_solv() has failed."));
++            ret = FALSE;
++        }
++    } else {
++        ret = FALSE;
++    }
+ 
+-    return 0;
++    fclose(fp_cache);
++    return ret;
+ }
+ 
+ void
+@@ -375,33 +397,27 @@ load_ext(DnfSack *sack, HyRepo hrepo, _hy_repo_repodata which_repodata,
+     gboolean done = FALSE;
+ 
+     char *fn_cache =  dnf_sack_give_cache_fn(sack, name, suffix);
+-    fp = fopen(fn_cache, "r");
+     assert(libdnf::repoGetImpl(hrepo)->checksum);
+-    if (can_use_repomd_cache(fp, libdnf::repoGetImpl(hrepo)->checksum)) {
+-        int flags = 0;
+-        /* the updateinfo is not a real extension */
+-        if (which_repodata != _HY_REPODATA_UPDATEINFO)
+-            flags |= REPO_EXTEND_SOLVABLES;
+-        /* do not pollute the main pool with directory component ids */
+-        if (which_repodata == _HY_REPODATA_FILENAMES || which_repodata == _HY_REPODATA_OTHER)
+-            flags |= REPO_LOCALPOOL;
+-        done = TRUE;
++
++    int flags = 0;
++    /* the updateinfo is not a real extension */
++    if (which_repodata != _HY_REPODATA_UPDATEINFO)
++        flags |= REPO_EXTEND_SOLVABLES;
++    /* do not pollute the main pool with directory component ids */
++    if (which_repodata == _HY_REPODATA_FILENAMES || which_repodata == _HY_REPODATA_OTHER)
++        flags |= REPO_LOCALPOOL;
++    if (try_to_use_cached_solvfile(fn_cache, repo, flags, libdnf::repoGetImpl(hrepo)->checksum, error)) {
+         g_debug("%s: using cache file: %s", __func__, fn_cache);
+-        ret = repo_add_solv(repo, fp, flags);
+-        if (ret) {
+-            g_set_error_literal (error,
+-                                 DNF_ERROR,
+-                                 DNF_ERROR_INTERNAL_ERROR,
+-                                 _("failed to add solv"));
+-            return FALSE;
+-        } else {
+-            repo_update_state(hrepo, which_repodata, _HY_LOADED_CACHE);
+-            repo_set_repodata(hrepo, which_repodata, repo->nrepodata - 1);
+-        }
++        done = TRUE;
++        repo_update_state(hrepo, which_repodata, _HY_LOADED_CACHE);
++        repo_set_repodata(hrepo, which_repodata, repo->nrepodata - 1);
+     }
++    if (error && *error) {
++        g_prefix_error(error, _("Loading extension cache %s (%d) failed: "), fn_cache, which_repodata);
++        return FALSE;
++    }
++
+     g_free(fn_cache);
+-    if (fp)
+-        fclose(fp);
+     if (done)
+         return TRUE;
+ 
+@@ -514,35 +530,53 @@ write_main(DnfSack *sack, HyRepo hrepo, int switchtosolv, GError **error)
+                         strerror(errno));
+             goto done;
+         }
+-        rc = repo_write(repo, fp);
+-        rc |= checksum_write(repoImpl->checksum, fp);
+-        rc |= fclose(fp);
++
++        SolvUserdata solv_userdata;
++        if (solv_userdata_fill(&solv_userdata, repoImpl->checksum, error)) {
++            ret = FALSE;
++            fclose(fp);
++            goto done;
++        }
++
++        Repowriter *writer = repowriter_create(repo);
++        repowriter_set_userdata(writer, &solv_userdata, solv_userdata_size);
++        rc = repowriter_write(writer, fp);
++        repowriter_free(writer);
+         if (rc) {
++            ret = FALSE;
++            fclose(fp);
++            g_set_error(error,
++                       DNF_ERROR,
++                       DNF_ERROR_INTERNAL_ERROR,
++                       _("While writing primary cache %s repowriter write failed: %i, error: %s"),
++                       tmp_fn_templ, rc, pool_errstr(repo->pool));
++            goto done;
++        }
++
++        if (fclose(fp)) {
+             ret = FALSE;
+             g_set_error (error,
+                         DNF_ERROR,
+                         DNF_ERROR_FILE_INVALID,
+-                        _("write_main() failed writing data: %i"), rc);
++                        _("Failed closing tmp file %s: %s"),
++                        tmp_fn_templ, strerror(errno));
+             goto done;
+         }
+     }
+     if (switchtosolv && repo_is_one_piece(repo)) {
++        repo_empty(repo, 1);
+         /* switch over to written solv file activate paging */
+-        FILE *fp = fopen(tmp_fn_templ, "r");
+-        if (fp) {
+-            repo_empty(repo, 1);
+-            rc = repo_add_solv(repo, fp, 0);
+-            fclose(fp);
+-            if (rc) {
+-                /* this is pretty fatal */
+-                ret = FALSE;
+-                g_set_error_literal (error,
+-                                     DNF_ERROR,
+-                                     DNF_ERROR_FILE_INVALID,
+-                                     _("write_main() failed to re-load "
+-                                       "written solv file"));
+-                goto done;
+-            }
++        gboolean loaded = try_to_use_cached_solvfile(tmp_fn_templ, repo, 0, repoImpl->checksum, error);
++        if (error && *error) {
++            g_prefix_error(error, _("Failed to use newly written primary cache: %s: "), tmp_fn_templ);
++            ret = FALSE;
++            goto done;
++        }
++        if (!loaded) {
++            g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR,
++                        _("Failed to use newly written primary cache: %s"), tmp_fn_templ);
++            ret = FALSE;
++            goto done;
+         }
+     }
+ 
+@@ -569,20 +603,6 @@ write_ext_updateinfo_filter(Repo *repo, Repokey *key, void *kfdata)
+     return repo_write_stdkeyfilter(repo, key, 0);
+ }
+ 
+-static int
+-write_ext_updateinfo(HyRepo hrepo, Repodata *data, FILE *fp)
+-{
+-    auto repoImpl = libdnf::repoGetImpl(hrepo);
+-    Repo *repo = repoImpl->libsolvRepo;
+-    int oldstart = repo->start;
+-    repo->start = repoImpl->main_end;
+-    repo->nsolvables -= repoImpl->main_nsolvables;
+-    int res = repo_write_filtered(repo, fp, write_ext_updateinfo_filter, data, 0);
+-    repo->start = oldstart;
+-    repo->nsolvables += repoImpl->main_nsolvables;
+-    return res;
+-}
+-
+ static gboolean
+ write_ext(DnfSack *sack, HyRepo hrepo, _hy_repo_repodata which_repodata,
+           const char *suffix, GError **error)
+@@ -611,37 +631,78 @@ write_ext(DnfSack *sack, HyRepo hrepo, _hy_repo_repodata which_repodata,
+         FILE *fp = fdopen(tmp_fd, "w+");
+ 
+         g_debug("%s: storing %s to: %s", __func__, repo->name, tmp_fn_templ);
+-        if (which_repodata != _HY_REPODATA_UPDATEINFO)
+-            ret |= repodata_write(data, fp);
+-        else
+-            ret |= write_ext_updateinfo(hrepo, data, fp);
+-        ret |= checksum_write(repoImpl->checksum, fp);
+-        ret |= fclose(fp);
++
++        SolvUserdata solv_userdata;
++        if (solv_userdata_fill(&solv_userdata, repoImpl->checksum, error)) {
++            fclose(fp);
++            success = FALSE;
++            goto done;
++        }
++
++        Repowriter *writer = repowriter_create(repo);
++        repowriter_set_userdata(writer, &solv_userdata, solv_userdata_size);
++        if (which_repodata != _HY_REPODATA_UPDATEINFO) {
++            repowriter_set_repodatarange(writer, data->repodataid, data->repodataid + 1);
++            repowriter_set_flags(writer, REPOWRITER_NO_STORAGE_SOLVABLE);
++            ret = repowriter_write(writer, fp);
++        } else {
++            // write only updateinfo repodata
++            int oldstart = repo->start;
++            repo->start = repoImpl->main_end;
++            repo->nsolvables -= repoImpl->main_nsolvables;
++            repowriter_set_flags(writer, REPOWRITER_LEGACY);
++            repowriter_set_keyfilter(writer, write_ext_updateinfo_filter, data);
++            repowriter_set_keyqueue(writer, 0);
++            ret = repowriter_write(writer, fp);
++            repo->start = oldstart;
++            repo->nsolvables += repoImpl->main_nsolvables;
++        }
++        repowriter_free(writer);
+         if (ret) {
++            success = FALSE;
++            fclose(fp);
++            g_set_error (error,
++                        DNF_ERROR,
++                        DNF_ERROR_INTERNAL_ERROR,
++                        _("While writing extension cache %s (%d): repowriter write failed: %i, error: %s"),
++                        tmp_fn_templ, which_repodata, ret, pool_errstr(repo->pool));
++            goto done;
++        }
++
++        if (fclose(fp)) {
+             success = FALSE;
+             g_set_error (error,
+                         DNF_ERROR,
+-                        DNF_ERROR_FAILED,
+-                        _("write_ext(%1$d) has failed: %2$d"),
+-                        which_repodata, ret);
++                        DNF_ERROR_FILE_INVALID,
++                        _("While writing extension cache (%d): cannot close temporary file: %s"),
++                        which_repodata, tmp_fn_templ);
+             goto done;
+         }
+     }
+ 
+     if (repo_is_one_piece(repo) && which_repodata != _HY_REPODATA_UPDATEINFO) {
+         /* switch over to written solv file activate paging */
+-        FILE *fp = fopen(tmp_fn_templ, "r");
+-        if (fp) {
+-            int flags = REPO_USE_LOADING | REPO_EXTEND_SOLVABLES;
+-            /* do not pollute the main pool with directory component ids */
+-            if (which_repodata == _HY_REPODATA_FILENAMES || which_repodata == _HY_REPODATA_OTHER)
+-                flags |= REPO_LOCALPOOL;
+-            repodata_extend_block(data, repo->start, repo->end - repo->start);
+-            data->state = REPODATA_LOADING;
+-            repo_add_solv(repo, fp, flags);
+-            data->state = REPODATA_AVAILABLE;
+-            fclose(fp);
++        int flags = REPO_USE_LOADING | REPO_EXTEND_SOLVABLES;
++        /* do not pollute the main pool with directory component ids */
++        if (which_repodata == _HY_REPODATA_FILENAMES || which_repodata == _HY_REPODATA_OTHER)
++            flags |= REPO_LOCALPOOL;
++        repodata_extend_block(data, repo->start, repo->end - repo->start);
++        data->state = REPODATA_LOADING;
++        int loaded = try_to_use_cached_solvfile(tmp_fn_templ, repo, flags, repoImpl->checksum, error);
++        if (error && *error) {
++            g_prefix_error(error, _("Failed to use newly written extension cache: %s (%d): "),
++                           tmp_fn_templ, which_repodata);
++            success = FALSE;
++            goto done;
++        }
++        if (!loaded) {
++            g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR,
++                        _("Failed to use newly written extension cache: %s (%d)"), tmp_fn_templ, which_repodata);
++            success = FALSE;
++            goto done;
+         }
++
++        data->state = REPODATA_AVAILABLE;
+     }
+ 
+     if (!mv(tmp_fn_templ, fn, error)) {
+@@ -672,7 +733,7 @@ load_yum_repo(DnfSack *sack, HyRepo hrepo, GError **error)
+ 
+     FILE *fp_primary = NULL;
+     FILE *fp_repomd = NULL;
+-    FILE *fp_cache = fopen(fn_cache, "r");
++
+     if (!fn_repomd) {
+         g_set_error (error,
+                      DNF_ERROR,
+@@ -693,18 +754,17 @@ load_yum_repo(DnfSack *sack, HyRepo hrepo, GError **error)
+     }
+     checksum_fp(repoImpl->checksum, fp_repomd);
+ 
+-    if (can_use_repomd_cache(fp_cache, repoImpl->checksum)) {
++    if (try_to_use_cached_solvfile(fn_cache, repo, 0, repoImpl->checksum, error)) {
+         const char *chksum = pool_checksum_str(pool, repoImpl->checksum);
+         g_debug("using cached %s (0x%s)", name, chksum);
+-        if (repo_add_solv(repo, fp_cache, 0)) {
+-            g_set_error (error,
+-                         DNF_ERROR,
+-                         DNF_ERROR_INTERNAL_ERROR,
+-                         _("repo_add_solv() has failed."));
+-            retval = FALSE;
+-            goto out;
+-        }
+         repoImpl->state_main = _HY_LOADED_CACHE;
++        goto out;
++    }
++
++    if (error && *error) {
++        g_prefix_error(error, _("While loading repository failed to use %s: "), fn_cache);
++        retval = FALSE;
++        goto out;
+     } else {
+         auto primary = hrepo->getMetadataPath(MD_TYPE_PRIMARY);
+         if (primary.empty()) {
+@@ -733,8 +793,6 @@ load_yum_repo(DnfSack *sack, HyRepo hrepo, GError **error)
+         repoImpl->state_main = _HY_LOADED_FETCH;
+     }
+ out:
+-    if (fp_cache)
+-        fclose(fp_cache);
+     if (fp_repomd)
+         fclose(fp_repomd);
+     if (fp_primary)
+diff --git a/libdnf/hy-iutil-private.hpp b/libdnf/hy-iutil-private.hpp
+index d498c032..efc91c63 100644
+--- a/libdnf/hy-iutil-private.hpp
++++ b/libdnf/hy-iutil-private.hpp
+@@ -52,9 +52,7 @@ int solv_userdata_verify(const SolvUserdata *solv_userdata, const unsigned char
+ /* crypto utils */
+ int checksum_cmp(const unsigned char *cs1, const unsigned char *cs2);
+ int checksum_fp(unsigned char *out, FILE *fp);
+-int checksum_read(unsigned char *csout, FILE *fp);
+ int checksum_stat(unsigned char *out, FILE *fp);
+-int checksum_write(const unsigned char *cs, FILE *fp);
+ int checksumt_l2h(int type);
+ const char *pool_checksum_str(Pool *pool, const unsigned char *chksum);
+ 
+diff --git a/libdnf/hy-iutil.cpp b/libdnf/hy-iutil.cpp
+index f81ca52f..c409a10a 100644
+--- a/libdnf/hy-iutil.cpp
++++ b/libdnf/hy-iutil.cpp
+@@ -142,17 +142,6 @@ checksum_fp(unsigned char *out, FILE *fp)
+     return 0;
+ }
+ 
+-/* calls rewind(fp) before returning */
+-int
+-checksum_read(unsigned char *csout, FILE *fp)
+-{
+-    if (fseek(fp, -32, SEEK_END) ||
+-        fread(csout, CHKSUM_BYTES, 1, fp) != 1)
+-        return 1;
+-    rewind(fp);
+-    return 0;
+-}
+-
+ /* does not move the fp position */
+ int
+ checksum_stat(unsigned char *out, FILE *fp)
+@@ -174,15 +163,6 @@ checksum_stat(unsigned char *out, FILE *fp)
+     return 0;
+ }
+ 
+-/* moves fp to the end of file */
+-int checksum_write(const unsigned char *cs, FILE *fp)
+-{
+-    if (fseek(fp, 0, SEEK_END) ||
+-        fwrite(cs, CHKSUM_BYTES, 1, fp) != 1)
+-        return 1;
+-    return 0;
+-}
+-
+ static std::array<char, solv_userdata_solv_toolversion_size>
+ get_padded_solv_toolversion()
+ {
+-- 
+2.31.1
+
diff --git a/SOURCES/0030-Update-unittest-to-test-the-new-private-dnf-solvfile.patch b/SOURCES/0030-Update-unittest-to-test-the-new-private-dnf-solvfile.patch
new file mode 100644
index 0000000..56c96c5
--- /dev/null
+++ b/SOURCES/0030-Update-unittest-to-test-the-new-private-dnf-solvfile.patch
@@ -0,0 +1,83 @@
+From 1e0f8f66f6ff30e177c41be7d72330d5eccf2ff8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= <amatej@redhat.com>
+Date: Wed, 9 Feb 2022 13:24:06 +0100
+Subject: [PATCH 30/34] Update unittest to test the new private dnf solvfile
+ API
+
+---
+ tests/hawkey/test_iutil.cpp | 34 ++++++++++++++++++++++------------
+ 1 file changed, 22 insertions(+), 12 deletions(-)
+
+diff --git a/tests/hawkey/test_iutil.cpp b/tests/hawkey/test_iutil.cpp
+index 8d00cc94..f3c04782 100644
+--- a/tests/hawkey/test_iutil.cpp
++++ b/tests/hawkey/test_iutil.cpp
+@@ -24,6 +24,8 @@
+ 
+ 
+ #include <solv/pool.h>
++#include <solv/repo.h>
++#include <solv/repo_write.h>
+ 
+ 
+ #include "libdnf/hy-util.h"
+@@ -97,28 +99,36 @@ START_TEST(test_checksum)
+ }
+ END_TEST
+ 
+-START_TEST(test_checksum_write_read)
++START_TEST(test_dnf_solvfile_userdata)
+ {
+     char *new_file = solv_dupjoin(test_globals.tmpdir,
+-                                  "/test_checksum_write_read", NULL);
++                                  "/test_dnf_solvfile_userdata", NULL);
+     build_test_file(new_file);
+ 
+     unsigned char cs_computed[CHKSUM_BYTES];
+-    unsigned char cs_read[CHKSUM_BYTES];
+-    FILE *fp = fopen(new_file, "r");
++    FILE *fp = fopen(new_file, "r+");
+     checksum_fp(cs_computed, fp);
+-    // fails, file opened read-only:
+-    fail_unless(checksum_write(cs_computed, fp) == 1);
+-    fclose(fp);
+-    fp = fopen(new_file, "r+");
+-    fail_if(checksum_write(cs_computed, fp));
++
++    SolvUserdata solv_userdata;
++    fail_if(solv_userdata_fill(&solv_userdata, cs_computed, NULL));
++
++    Pool *pool = pool_create();
++    Repo *repo = repo_create(pool, "test_repo");
++    Repowriter *writer = repowriter_create(repo);
++    repowriter_set_userdata(writer, &solv_userdata, solv_userdata_size);
++    fail_if(repowriter_write(writer, fp));
++    repowriter_free(writer);
+     fclose(fp);
++
+     fp = fopen(new_file, "r");
+-    fail_if(checksum_read(cs_read, fp));
+-    fail_if(checksum_cmp(cs_computed, cs_read));
++    std::unique_ptr<SolvUserdata> dnf_solvfile = solv_userdata_read(fp);
++    fail_unless(dnf_solvfile);
++    fail_unless(solv_userdata_verify(dnf_solvfile.get(), cs_computed));
+     fclose(fp);
+ 
+     g_free(new_file);
++    repo_free(repo, 0);
++    pool_free(pool);
+ }
+ END_TEST
+ 
+@@ -181,7 +191,7 @@ iutil_suite(void)
+     TCase *tc = tcase_create("Main");
+     tcase_add_test(tc, test_abspath);
+     tcase_add_test(tc, test_checksum);
+-    tcase_add_test(tc, test_checksum_write_read);
++    tcase_add_test(tc, test_dnf_solvfile_userdata);
+     tcase_add_test(tc, test_mkcachedir);
+     tcase_add_test(tc, test_version_split);
+     suite_add_tcase(s, tc);
+-- 
+2.31.1
+
diff --git a/SOURCES/0031-Increase-required-libsolv-version-for-cache-versioni.patch b/SOURCES/0031-Increase-required-libsolv-version-for-cache-versioni.patch
new file mode 100644
index 0000000..37b9497
--- /dev/null
+++ b/SOURCES/0031-Increase-required-libsolv-version-for-cache-versioni.patch
@@ -0,0 +1,38 @@
+From 893eb087e56588d62e81e91e5d283003bd80552a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= <amatej@redhat.com>
+Date: Tue, 8 Mar 2022 11:43:38 +0100
+Subject: [PATCH 31/34] Increase required libsolv version for cache versioning
+
+---
+ CMakeLists.txt | 2 +-
+ libdnf.spec    | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 60cf1b8c..d895b2bf 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -51,7 +51,7 @@ endif()
+ 
+ # build dependencies
+ find_package(Gpgme REQUIRED)
+-find_package(LibSolv 0.6.30 REQUIRED COMPONENTS ext)
++find_package(LibSolv 0.7.20 REQUIRED COMPONENTS ext)
+ find_package(OpenSSL REQUIRED)
+ 
+ 
+diff --git a/libdnf.spec b/libdnf.spec
+index a849cdea..aa51dd28 100644
+--- a/libdnf.spec
++++ b/libdnf.spec
+@@ -1,5 +1,5 @@
+-%global libsolv_version 0.7.17
+-%global libmodulemd_version 2.11.2-2
++%global libsolv_version 0.7.21
++%global libmodulemd_version 2.13.0
+ %global librepo_version 1.13.1
+ %global dnf_conflict 4.3.0
+ %global swig_version 3.0.12
+-- 
+2.31.1
+
diff --git a/SOURCES/0032-Add-more-specific-error-handling-for-loading-repomd-.patch b/SOURCES/0032-Add-more-specific-error-handling-for-loading-repomd-.patch
new file mode 100644
index 0000000..1f0303c
--- /dev/null
+++ b/SOURCES/0032-Add-more-specific-error-handling-for-loading-repomd-.patch
@@ -0,0 +1,46 @@
+From b636af779fcdab326eef7bbb74912254c2fa2b0c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= <amatej@redhat.com>
+Date: Thu, 17 Mar 2022 10:34:24 +0100
+Subject: [PATCH 32/34] Add more specific error handling for loading repomd and
+ primary
+
+---
+ libdnf/dnf-sack.cpp | 19 +++++++++++++++----
+ 1 file changed, 15 insertions(+), 4 deletions(-)
+
+diff --git a/libdnf/dnf-sack.cpp b/libdnf/dnf-sack.cpp
+index 61f4807c..8e11b8f8 100644
+--- a/libdnf/dnf-sack.cpp
++++ b/libdnf/dnf-sack.cpp
+@@ -780,13 +780,24 @@ load_yum_repo(DnfSack *sack, HyRepo hrepo, GError **error)
+         fp_primary = solv_xfopen(primary.c_str(), "r");
+         assert(fp_primary);
+ 
+-        g_debug("fetching %s", name);
+-        if (repo_add_repomdxml(repo, fp_repomd, 0) || \
+-            repo_add_rpmmd(repo, fp_primary, 0, 0)) {
++        g_debug("Loading repomd: %s", fn_repomd);
++        if (repo_add_repomdxml(repo, fp_repomd, 0)) {
+             g_set_error (error,
+                          DNF_ERROR,
+                          DNF_ERROR_INTERNAL_ERROR,
+-                         _("repo_add_repomdxml/rpmmd() has failed."));
++                         _("Loading repomd has failed: %s"),
++                         pool_errstr(repo->pool));
++            retval = FALSE;
++            goto out;
++        }
++
++        g_debug("Loading primary: %s", primary.c_str());
++        if (repo_add_rpmmd(repo, fp_primary, 0, 0)) {
++            g_set_error (error,
++                         DNF_ERROR,
++                         DNF_ERROR_INTERNAL_ERROR,
++                         _("Loading primary has failed: %s"),
++                         pool_errstr(repo->pool));
+             retval = FALSE;
+             goto out;
+         }
+-- 
+2.31.1
+
diff --git a/SOURCES/0033-libdnf-transaction-RPMItem-Fix-handling-transaction-.patch b/SOURCES/0033-libdnf-transaction-RPMItem-Fix-handling-transaction-.patch
new file mode 100644
index 0000000..7398fc8
--- /dev/null
+++ b/SOURCES/0033-libdnf-transaction-RPMItem-Fix-handling-transaction-.patch
@@ -0,0 +1,74 @@
+From c5919efe898294420ec8e91e4eed5b9081e681c5 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= <lhrazky@redhat.com>
+Date: Thu, 17 Feb 2022 18:18:16 +0100
+Subject: [PATCH 33/34] libdnf/transaction/RPMItem: Fix handling transaction id
+ in resolveTransactionItemReason
+
+The maxTransactionId argument was ignored, the method was always returning the
+reason from the last transaction. This is the correct result for
+maxTransactionId = -1. In a couple of places the method is called with
+maxTransactionId = -2. Fixing this would mean nontrivial changes to the
+logic which could potentially break something else, so I'm leaving this
+behavior unchanged.
+
+For non-negative values of maxTransactionId (with which it's not being called
+anywhere in dnf codebase), the commit adds a condition to SELECT only
+transaction ids less than or equal to maxTransactionId.
+
+= changelog =
+msg: Fix handling transaction id in resolveTransactionItemReason
+type: bugfix
+resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2053014
+resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2010259
+---
+ libdnf/transaction/RPMItem.cpp | 21 ++++++++++++++++++---
+ 1 file changed, 18 insertions(+), 3 deletions(-)
+
+diff --git a/libdnf/transaction/RPMItem.cpp b/libdnf/transaction/RPMItem.cpp
+index 5f667ab9..ecce789d 100644
+--- a/libdnf/transaction/RPMItem.cpp
++++ b/libdnf/transaction/RPMItem.cpp
+@@ -255,7 +255,11 @@ RPMItem::resolveTransactionItemReason(SQLite3Ptr conn,
+                                       const std::string &arch,
+                                       int64_t maxTransactionId)
+ {
+-    const char *sql = R"**(
++    // NOTE: All negative maxTransactionId values are treated the same. The
++    // method is called with maxTransactionId = -2 in a couple of places, the
++    // semantics here have been the same as with -1 for a long time. If it
++    // ain't broke...
++    std::string sql = R"**(
+         SELECT
+             ti.action as action,
+             ti.reason as reason
+@@ -271,14 +275,25 @@ RPMItem::resolveTransactionItemReason(SQLite3Ptr conn,
+             AND ti.action not in (3, 5, 7, 10)
+             AND i.name = ?
+             AND i.arch = ?
++    )**";
++
++    if (maxTransactionId >= 0) {
++        sql.append(" AND ti.trans_id <= ?");
++    }
++
++    sql.append(R"**(
+         ORDER BY
+             ti.trans_id DESC
+         LIMIT 1
+-    )**";
++    )**");
+ 
+     if (arch != "") {
+         SQLite3::Query query(*conn, sql);
+-        query.bindv(name, arch);
++        if (maxTransactionId >= 0) {
++            query.bindv(name, arch, maxTransactionId);
++        } else {
++            query.bindv(name, arch);
++        }
+ 
+         if (query.step() == SQLite3::Statement::StepResult::ROW) {
+             auto action = static_cast< TransactionItemAction >(query.get< int64_t >("action"));
+-- 
+2.31.1
+
diff --git a/SOURCES/0034-libdnf-transaction-TransactionItem-Set-short-action-.patch b/SOURCES/0034-libdnf-transaction-TransactionItem-Set-short-action-.patch
new file mode 100644
index 0000000..c7290c1
--- /dev/null
+++ b/SOURCES/0034-libdnf-transaction-TransactionItem-Set-short-action-.patch
@@ -0,0 +1,33 @@
+From c303b7c3723f3e9fbc43963a62237ea17516fc6b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= <lhrazky@redhat.com>
+Date: Thu, 17 Feb 2022 18:30:14 +0100
+Subject: [PATCH 34/34] libdnf/transaction/TransactionItem: Set short action
+ for Reason Change
+
+Sets the "short" (one letter) representation of the Reason Change action
+to "C".
+
+This was likely not ever used before as the only way to create a
+transaction with a reason change and something else is rolling back
+multiple transactions, which was broken.
+---
+ libdnf/transaction/TransactionItem.cpp | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/libdnf/transaction/TransactionItem.cpp b/libdnf/transaction/TransactionItem.cpp
+index 3b43d1f1..4358038e 100644
+--- a/libdnf/transaction/TransactionItem.cpp
++++ b/libdnf/transaction/TransactionItem.cpp
+@@ -51,8 +51,7 @@ static const std::map< TransactionItemAction, std::string > transactionItemActio
+     {TransactionItemAction::REMOVE, "E"},
+     {TransactionItemAction::REINSTALL, "R"},
+     {TransactionItemAction::REINSTALLED, "R"},
+-    // TODO: replace "?" with something better
+-    {TransactionItemAction::REASON_CHANGE, "?"},
++    {TransactionItemAction::REASON_CHANGE, "C"},
+ };
+ 
+ /*
+-- 
+2.31.1
+
diff --git a/SPECS/libdnf.spec b/SPECS/libdnf.spec
index 94c6a0b..023fc81 100644
--- a/SPECS/libdnf.spec
+++ b/SPECS/libdnf.spec
@@ -1,4 +1,4 @@
-%global libsolv_version 0.7.17
+%global libsolv_version 0.7.20-3
 %global libmodulemd_version 2.11.2-2
 %global librepo_version 1.13.1
 %global dnf_conflict 4.3.0
@@ -56,7 +56,7 @@
 
 Name:           libdnf
 Version:        %{libdnf_major_version}.%{libdnf_minor_version}.%{libdnf_micro_version}
-Release:        7.1%{?dist}
+Release:        9.1%{?dist}
 Summary:        Library providing simplified C and Python API to libsolv
 License:        LGPLv2+
 URL:            https://github.com/rpm-software-management/libdnf
@@ -86,6 +86,15 @@ Patch22:        0022-hawkey-surrogateescape-error-handler-to-decode-UTF-8-string
 Patch23:        0023-Turn-off-strict-validation-of-modulemd-documents-RhBug200485320071662007167.patch
 Patch24:        0024-Add-unittest-for-setting-up-repo-with-empty-keyfile-RhBug1994614.patch
 Patch25:        0025-Add-getLatestModules.patch
+Patch26:        0026-context-Substitute-all-repository-config-options-RhB.patch
+Patch27:        0027-Use-environment-variable-in-unittest-instead-of-ugly.patch
+Patch28:        0028-Add-private-API-for-filling-reading-and-verifying-ne.patch
+Patch29:        0029-Use-dnf-solv-userdata-to-check-versions-and-checksum.patch
+Patch30:        0030-Update-unittest-to-test-the-new-private-dnf-solvfile.patch
+Patch31:        0031-Increase-required-libsolv-version-for-cache-versioni.patch
+Patch32:        0032-Add-more-specific-error-handling-for-loading-repomd-.patch
+Patch33:        0033-libdnf-transaction-RPMItem-Fix-handling-transaction-.patch
+Patch34:        0034-libdnf-transaction-TransactionItem-Set-short-action-.patch
 
 Patch9999:      1164-no-caching.patch
 Requires:       librepo(pr234)
@@ -335,6 +344,15 @@ popd
 %endif
 
 %changelog
+* Mon May 16 2022 Manu Bretelle <chantra@fb.com> - 0.63.0-9.1
+- Rebuild for Hyperscale
+
+* Wed May 04 2022 Lukas Hrazky <lhrazky@redhat.com> - 0.63.0-8
+- Substitute all repository config options (fixes substitution of baseurl)
+- Use solvfile userdata to store and check checksums and solv versions
+- Fix handling transaction id in resolveTransactionItemReason
+- Set short action for Reason Change
+
 * Thu Feb 17 2022 Manu Bretelle <chantra@fb.com> - 0.63.0-7.1
 - Merge 0.63.0 update into hsx branch