Blame SOURCES/0011-Add-new-command-modulesync-RhBug1868047.patch

d6d64b
From 6ea94d9c768eb45975f314e11ab9dd88284fa380 Mon Sep 17 00:00:00 2001
d6d64b
From: Jaroslav Mracek <jmracek@redhat.com>
d6d64b
Date: Mon, 27 Sep 2021 11:29:01 +0200
d6d64b
Subject: [PATCH] Add new command modulesync (RhBug:1868047)
d6d64b
d6d64b
It will download module metadata from all enabled repositories,
d6d64b
module artifacts and profiles of matching modules. Then it creates
d6d64b
a repository.
d6d64b
d6d64b
= changelog =
d6d64b
msg:           Add a new subpackage with modulesync command. The command
d6d64b
downloads packages from modules and/or creates a repository with modular
d6d64b
data.
d6d64b
type:          enhancement
d6d64b
resolves:      https://bugzilla.redhat.com/show_bug.cgi?id=1868047
d6d64b
---
d6d64b
 dnf-plugins-core.spec  |  20 ++++++++++++++++++++
d6d64b
 doc/CMakeLists.txt     |   1 +
d6d64b
 doc/conf.py            |   1 +
d6d64b
 doc/index.rst          |   1 +
d6d64b
 doc/modulesync.rst     | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
d6d64b
 plugins/CMakeLists.txt |   1 +
d6d64b
 plugins/modulesync.py  | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
d6d64b
 7 files changed, 335 insertions(+)
d6d64b
 create mode 100644 doc/modulesync.rst
d6d64b
 create mode 100644 plugins/modulesync.py
d6d64b
d6d64b
diff --git a/dnf-plugins-core.spec b/dnf-plugins-core.spec
d6d64b
index cef836f..afdbcbb 100644
d6d64b
--- a/dnf-plugins-core.spec
d6d64b
+++ b/dnf-plugins-core.spec
d6d64b
@@ -402,6 +402,19 @@ versions of those packages. This allows you to e.g. protect packages from being
d6d64b
 updated by newer versions.
d6d64b
 %endif
d6d64b
 
d6d64b
+%if %{with python3}
d6d64b
+%package -n python3-dnf-plugin-modulesync
d6d64b
+Summary:        Download module metadata and packages and create repository
d6d64b
+Requires:       python3-%{name} = %{version}-%{release}
d6d64b
+Requires:       createrepo_c >= 0.17.4
d6d64b
+Provides:       dnf-plugin-modulesync =  %{version}-%{release}
d6d64b
+Provides:       dnf-command(modulesync)
d6d64b
+
d6d64b
+%description -n python3-dnf-plugin-modulesync
d6d64b
+Download module metadata from all enabled repositories, module artifacts and profiles of matching modules and create
d6d64b
+repository.
d6d64b
+%endif
d6d64b
+
d6d64b
 %prep
d6d64b
 %autosetup
d6d64b
 %if %{with python2}
d6d64b
@@ -762,6 +775,13 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/
d6d64b
 %endif
d6d64b
 %endif
d6d64b
 
d6d64b
+%if %{with python3}
d6d64b
+%files -n python3-dnf-plugin-modulesync
d6d64b
+%{python3_sitelib}/dnf-plugins/modulesync.*
d6d64b
+%{python3_sitelib}/dnf-plugins/__pycache__/modulesync.*
d6d64b
+%{_mandir}/man8/dnf-modulesync.*
d6d64b
+%endif
d6d64b
+
d6d64b
 %changelog
d6d64b
 * Mon Apr 12 2021 Nicola Sella <nsella@redhat.com> - 4.0.21-1
d6d64b
 - Add missing command line option to documentation
d6d64b
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
d6d64b
index 3fb665d..ff84cf8 100644
d6d64b
--- a/doc/CMakeLists.txt
d6d64b
+++ b/doc/CMakeLists.txt
d6d64b
@@ -28,6 +28,7 @@ INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf-builddep.8
d6d64b
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-generate_completion_cache.8
d6d64b
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-groups-manager.8
d6d64b
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-leaves.8
d6d64b
+    ${CMAKE_CURRENT_BINARY_DIR}/dnf-modulesync.8
d6d64b
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-needs-restarting.8
d6d64b
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-repoclosure.8
d6d64b
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-repodiff.8
d6d64b
diff --git a/doc/conf.py b/doc/conf.py
d6d64b
index 645185a..41d6936 100644
d6d64b
--- a/doc/conf.py
d6d64b
+++ b/doc/conf.py
d6d64b
@@ -254,6 +254,7 @@ man_pages = [
d6d64b
     ('groups-manager', 'dnf-groups-manager', u'DNF groups-manager Plugin', AUTHORS, 8),
d6d64b
     ('leaves', 'dnf-leaves', u'DNF leaves Plugin', AUTHORS, 8),
d6d64b
     ('local', 'dnf-local', u'DNF local Plugin', AUTHORS, 8),
d6d64b
+    ('modulesync', 'dnf-modulesync', u'DNF modulesync Plugin', AUTHORS, 8),
d6d64b
     ('needs_restarting', 'dnf-needs-restarting', u'DNF needs_restarting Plugin', AUTHORS, 8),
d6d64b
     ('repoclosure', 'dnf-repoclosure', u'DNF repoclosure Plugin', AUTHORS, 8),
d6d64b
     ('repodiff', 'dnf-repodiff', u'DNF repodiff Plugin', AUTHORS, 8),
d6d64b
diff --git a/doc/index.rst b/doc/index.rst
d6d64b
index 7213253..07f6052 100644
d6d64b
--- a/doc/index.rst
d6d64b
+++ b/doc/index.rst
d6d64b
@@ -37,6 +37,7 @@ This documents core plugins of DNF:
d6d64b
    leaves
d6d64b
    local
d6d64b
    migrate
d6d64b
+   modulesync
d6d64b
    needs_restarting
d6d64b
    post-transaction-actions
d6d64b
    repoclosure
d6d64b
diff --git a/doc/modulesync.rst b/doc/modulesync.rst
d6d64b
new file mode 100644
d6d64b
index 0000000..2837287
d6d64b
--- /dev/null
d6d64b
+++ b/doc/modulesync.rst
d6d64b
@@ -0,0 +1,103 @@
d6d64b
+..
d6d64b
+  Copyright (C) 2015  Red Hat, Inc.
d6d64b
+
d6d64b
+  This copyrighted material is made available to anyone wishing to use,
d6d64b
+  modify, copy, or redistribute it subject to the terms and conditions of
d6d64b
+  the GNU General Public License v.2, or (at your option) any later version.
d6d64b
+  This program is distributed in the hope that it will be useful, but WITHOUT
d6d64b
+  ANY WARRANTY expressed or implied, including the implied warranties of
d6d64b
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
d6d64b
+  Public License for more details.  You should have received a copy of the
d6d64b
+  GNU General Public License along with this program; if not, write to the
d6d64b
+  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
d6d64b
+  02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
d6d64b
+  source code or documentation are not subject to the GNU General Public
d6d64b
+  License and may only be used or replicated with the express permission of
d6d64b
+  Red Hat, Inc.
d6d64b
+
d6d64b
+====================
d6d64b
+DNF modulesync Plugin
d6d64b
+====================
d6d64b
+
d6d64b
+Download packages from modules and/or create a repository with modular data.
d6d64b
+
d6d64b
+--------
d6d64b
+Synopsis
d6d64b
+--------
d6d64b
+
d6d64b
+``dnf modulesync [options] [<module-spec>...]``
d6d64b
+
d6d64b
+-----------
d6d64b
+Description
d6d64b
+-----------
d6d64b
+
d6d64b
+`modulesync` downloads packages from modules according to provided arguments and creates a repository with modular data
d6d64b
+in working directory. In environment with modules it is recommend to use the command for redistribution of packages,
d6d64b
+because DNF does not allow installation of modular packages without modular metadata on the system (Fail-safe
d6d64b
+mechanism). The command without an argument creates a repository like `createrepo_c` but with modular metadata collected
d6d64b
+from all available repositories.
d6d64b
+
d6d64b
+See examples.
d6d64b
+
d6d64b
+---------
d6d64b
+Arguments
d6d64b
+---------
d6d64b
+
d6d64b
+``<module-spec>``
d6d64b
+    Module specification for the package to download. The argument is an optional.
d6d64b
+
d6d64b
+-------
d6d64b
+Options
d6d64b
+-------
d6d64b
+
d6d64b
+All general DNF options are accepted. Namely, the ``--destdir`` option can be used to specify directory where packages
d6d64b
+will be downloaded and the new repository created. See `Options` in :manpage:`dnf(8)` for details.
d6d64b
+
d6d64b
+
d6d64b
+``-n, --newest-only``
d6d64b
+    Download only packages from the newest modules.
d6d64b
+
d6d64b
+``--enable_source_repos``
d6d64b
+    Enable repositories with source packages
d6d64b
+
d6d64b
+``--enable_debug_repos``
d6d64b
+    Enable repositories with debug-info and debug-source packages
d6d64b
+
d6d64b
+``--resolve``
d6d64b
+    Resolve and download needed dependencies
d6d64b
+
d6d64b
+--------
d6d64b
+Examples
d6d64b
+--------
d6d64b
+
d6d64b
+``dnf modulesync nodejs``
d6d64b
+    Download packages from `nodejs` module and crete a repository with modular metadata in working directory
d6d64b
+
d6d64b
+``dnf download nodejs``
d6d64b
+
d6d64b
+``dnf modulesync``
d6d64b
+    The first `download` command downloads nodejs package into working directory. In environment with modules `nodejs`
d6d64b
+    package can be a modular package therefore when I create a repository I have to insert also modular metadata
d6d64b
+    from available repositories to ensure 100% functionality. Instead of `createrepo_c` use `dnf modulesync`
d6d64b
+    to create a repository in working directory with nodejs package and modular metadata.
d6d64b
+
d6d64b
+``dnf --destdir=/tmp/my-temp modulesync nodejs:14/minimal --resolve``
d6d64b
+    Download package required for installation of `minimal` profile from module `nodejs` and stream `14` into directory
d6d64b
+    `/tmp/my-temp` and all required dependencies. Then it will create a repository in `/tmp/my-temp` directory with
d6d64b
+    previously downloaded packages and modular metadata from all available repositories.
d6d64b
+
d6d64b
+``dnf module install nodejs:14/minimal --downloadonly --destdir=/tmp/my-temp``
d6d64b
+
d6d64b
+``dnf modulesync --destdir=/tmp/my-temp``
d6d64b
+    The first `dnf module install` command downloads package from required for installation of `minimal` profile from module
d6d64b
+    `nodejs` and stream `14` into directory `/tmp/my-temp`. The second command `dnf modulesync` will create
d6d64b
+    a repository in `/tmp/my-temp` directory with previously downloaded packages and modular metadata from all
d6d64b
+    available repositories. In comparison to `dnf --destdir=/tmp/my-temp modulesync nodejs:14/minimal --resolve` it will
d6d64b
+    only download packages required for installation on current system.
d6d64b
+
d6d64b
+
d6d64b
+--------
d6d64b
+See Also
d6d64b
+--------
d6d64b
+
d6d64b
+* :manpage:`dnf(8)`, DNF Command Reference
d6d64b
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
d6d64b
index f66d3df..59f148f 100644
d6d64b
--- a/plugins/CMakeLists.txt
d6d64b
+++ b/plugins/CMakeLists.txt
d6d64b
@@ -22,6 +22,7 @@ INSTALL (FILES repograph.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
d6d64b
 INSTALL (FILES repomanage.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
d6d64b
 INSTALL (FILES reposync.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
d6d64b
 INSTALL (FILES show_leaves.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
d6d64b
+INSTALL (FILES modulesync.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
d6d64b
 INSTALL (FILES versionlock.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
d6d64b
 
d6d64b
 ADD_SUBDIRECTORY (dnfpluginscore)
d6d64b
diff --git a/plugins/modulesync.py b/plugins/modulesync.py
d6d64b
new file mode 100644
d6d64b
index 0000000..c1c33e4
d6d64b
--- /dev/null
d6d64b
+++ b/plugins/modulesync.py
d6d64b
@@ -0,0 +1,208 @@
d6d64b
+# Copyright (C) 2021  Red Hat, Inc.
d6d64b
+#
d6d64b
+# This copyrighted material is made available to anyone wishing to use,
d6d64b
+# modify, copy, or redistribute it subject to the terms and conditions of
d6d64b
+# the GNU General Public License v.2, or (at your option) any later version.
d6d64b
+# This program is distributed in the hope that it will be useful, but WITHOUT
d6d64b
+# ANY WARRANTY expressed or implied, including the implied warranties of
d6d64b
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
d6d64b
+# Public License for more details.  You should have received a copy of the
d6d64b
+# GNU General Public License along with this program; if not, write to the
d6d64b
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
d6d64b
+# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
d6d64b
+# source code or documentation are not subject to the GNU General Public
d6d64b
+# License and may only be used or replicated with the express permission of
d6d64b
+# Red Hat, Inc.
d6d64b
+#
d6d64b
+
d6d64b
+from __future__ import absolute_import
d6d64b
+from __future__ import unicode_literals
d6d64b
+from dnfpluginscore import _, P_, logger
d6d64b
+from dnf.cli.option_parser import OptionParser
d6d64b
+
d6d64b
+import os
d6d64b
+import shutil
d6d64b
+import subprocess
d6d64b
+
d6d64b
+import dnf
d6d64b
+import dnf.cli
d6d64b
+import dnf.i18n
d6d64b
+import hawkey
d6d64b
+
d6d64b
+
d6d64b
+@dnf.plugin.register_command
d6d64b
+class SyncToolCommand(dnf.cli.Command):
d6d64b
+
d6d64b
+    aliases = ['modulesync']
d6d64b
+    summary = _('Download packages from modules and/or create a repository with modular data')
d6d64b
+
d6d64b
+    def __init__(self, cli):
d6d64b
+        super(SyncToolCommand, self).__init__(cli)
d6d64b
+
d6d64b
+    @staticmethod
d6d64b
+    def set_argparser(parser):
d6d64b
+        parser.add_argument('module', nargs='*', metavar=_('MODULE'),
d6d64b
+                            help=_('modules to download'))
d6d64b
+        parser.add_argument("--enable_source_repos", action='store_true',
d6d64b
+                            help=_('enable repositories with source packages'))
d6d64b
+        parser.add_argument("--enable_debug_repos", action='store_true',
d6d64b
+                            help=_('enable repositories with debug-info and debug-source packages'))
d6d64b
+        parser.add_argument('--resolve', action='store_true',
d6d64b
+                            help=_('resolve and download needed dependencies'))
d6d64b
+        parser.add_argument('-n', '--newest-only', default=False, action='store_true',
d6d64b
+                            help=_('download only packages from newest modules'))
d6d64b
+
d6d64b
+    def configure(self):
d6d64b
+        # setup sack and populate it with enabled repos
d6d64b
+        demands = self.cli.demands
d6d64b
+        demands.sack_activation = True
d6d64b
+        demands.available_repos = True
d6d64b
+
d6d64b
+        demands.load_system_repo = False
d6d64b
+
d6d64b
+        if self.opts.enable_source_repos:
d6d64b
+            self.base.repos.enable_source_repos()
d6d64b
+
d6d64b
+        if self.opts.enable_debug_repos:
d6d64b
+            self.base.repos.enable_debug_repos()
d6d64b
+
d6d64b
+        if self.opts.destdir:
d6d64b
+            self.base.conf.destdir = self.opts.destdir
d6d64b
+        else:
d6d64b
+            self.base.conf.destdir = dnf.i18n.ucd(os.getcwd())
d6d64b
+
d6d64b
+    def run(self):
d6d64b
+        """Execute the util action here."""
d6d64b
+
d6d64b
+        pkgs = self.base.sack.query().filterm(empty=True)
d6d64b
+        no_matched_spec = []
d6d64b
+        for module_spec in self.opts.module:
d6d64b
+            try:
d6d64b
+                pkgs = pkgs.union(self._get_packages_from_modules(module_spec))
d6d64b
+            except dnf.exceptions.Error:
d6d64b
+                no_matched_spec.append(module_spec)
d6d64b
+        if no_matched_spec:
d6d64b
+            msg = P_("Unable to find a match for argument: '{}'", "Unable to find a match for arguments: '{}'",
d6d64b
+                     len(no_matched_spec)).format("' '".join(no_matched_spec))
d6d64b
+            raise dnf.exceptions.Error(msg)
d6d64b
+
d6d64b
+        if self.opts.resolve:
d6d64b
+            pkgs = pkgs.union(self._get_providers_of_requires(pkgs))
d6d64b
+
d6d64b
+        # download rpms
d6d64b
+        self._do_downloads(pkgs)
d6d64b
+
d6d64b
+        # Create a repository at destdir with modular data
d6d64b
+        remove_tmp_moduleyamls_files = []
d6d64b
+        for repo in self.base.repos.iter_enabled():
d6d64b
+            module_md_path = repo.get_metadata_path('modules')
d6d64b
+            if module_md_path:
d6d64b
+                filename = "".join([repo.id, "-", os.path.basename(module_md_path)])
d6d64b
+                dest_path = os.path.join(self.base.conf.destdir, filename)
d6d64b
+                shutil.copy(module_md_path, dest_path)
d6d64b
+                remove_tmp_moduleyamls_files.append(dest_path)
d6d64b
+        args = ["createrepo_c", "--update", "--unique-md-filenames", self.base.conf.destdir]
d6d64b
+        p = subprocess.run(args)
d6d64b
+        if p.returncode:
d6d64b
+            msg = _("Creation of repository failed with return code {}. All downloaded content was kept on the system")
d6d64b
+            msg = msg.format(p.returncode)
d6d64b
+            raise dnf.exceptions.Error(msg)
d6d64b
+        for file_path in remove_tmp_moduleyamls_files:
d6d64b
+            os.remove(file_path)
d6d64b
+
d6d64b
+    def _do_downloads(self, pkgs):
d6d64b
+        """
d6d64b
+        Perform the download for a list of packages
d6d64b
+        """
d6d64b
+        pkg_dict = {}
d6d64b
+        for pkg in pkgs:
d6d64b
+            pkg_dict.setdefault(str(pkg), []).append(pkg)
d6d64b
+
d6d64b
+        to_download = []
d6d64b
+
d6d64b
+        for pkg_list in pkg_dict.values():
d6d64b
+            pkg_list.sort(key=lambda x: (x.repo.priority, x.repo.cost))
d6d64b
+            to_download.append(pkg_list[0])
d6d64b
+        if to_download:
d6d64b
+            self.base.download_packages(to_download, self.base.output.progress)
d6d64b
+
d6d64b
+    def _get_packages_from_modules(self, module_spec):
d6d64b
+        """Gets packages from modules matching module spec
d6d64b
+        1. From module artifacts
d6d64b
+        2. From module profiles"""
d6d64b
+        result_query = self.base.sack.query().filterm(empty=True)
d6d64b
+        module_base = dnf.module.module_base.ModuleBase(self.base)
d6d64b
+        module_list, nsvcap = module_base.get_modules(module_spec)
d6d64b
+        if self.opts.newest_only:
d6d64b
+            module_list = self.base._moduleContainer.getLatestModules(module_list, False)
d6d64b
+        for module in module_list:
d6d64b
+            for artifact in module.getArtifacts():
d6d64b
+                query = self.base.sack.query(flags=hawkey.IGNORE_EXCLUDES).filterm(nevra_strict=artifact)
d6d64b
+                if query:
d6d64b
+                    result_query = result_query.union(query)
d6d64b
+                else:
d6d64b
+                    msg = _("No match for artifact '{0}' from module '{1}'").format(
d6d64b
+                        artifact, module.getFullIdentifier())
d6d64b
+                    logger.warning(msg)
d6d64b
+            if nsvcap.profile:
d6d64b
+                profiles_set = module.getProfiles(nsvcap.profile)
d6d64b
+            else:
d6d64b
+                profiles_set = module.getProfiles()
d6d64b
+            if profiles_set:
d6d64b
+                for profile in profiles_set:
d6d64b
+                    for pkg_name in profile.getContent():
d6d64b
+                        query = self.base.sack.query(flags=hawkey.IGNORE_EXCLUDES).filterm(name=pkg_name)
d6d64b
+                        # Prefer to add modular providers selected by argument
d6d64b
+                        if result_query.intersection(query):
d6d64b
+                            continue
d6d64b
+                        # Add all packages with the same name as profile described
d6d64b
+                        elif query:
d6d64b
+                            result_query = result_query.union(query)
d6d64b
+                        else:
d6d64b
+                            msg = _("No match for package name '{0}' in profile {1} from module {2}")\
d6d64b
+                                .format(pkg_name, profile.getName(), module.getFullIdentifier())
d6d64b
+                            logger.warning(msg)
d6d64b
+        if not module_list:
d6d64b
+            msg = _("No mach for argument '{}'").format(module_spec)
d6d64b
+            raise dnf.exceptions.Error(msg)
d6d64b
+
d6d64b
+        return result_query
d6d64b
+
d6d64b
+    def _get_providers_of_requires(self, to_test, done=None, req_dict=None):
d6d64b
+        done = done if done else to_test
d6d64b
+        # req_dict = {}  {req : set(pkgs)}
d6d64b
+        if req_dict is None:
d6d64b
+            req_dict = {}
d6d64b
+        test_requires = []
d6d64b
+        for pkg in to_test:
d6d64b
+            for require in pkg.requires:
d6d64b
+                if require not in req_dict:
d6d64b
+                    test_requires.append(require)
d6d64b
+                req_dict.setdefault(require, set()).add(pkg)
d6d64b
+
d6d64b
+        if self.opts.newest_only:
d6d64b
+            #  Prepare cache with all packages related affected by modular filtering
d6d64b
+            names = set()
d6d64b
+            for module in self.base._moduleContainer.getModulePackages():
d6d64b
+                for artifact in module.getArtifacts():
d6d64b
+                    name, __, __ = artifact.rsplit("-", 2)
d6d64b
+                    names.add(name)
d6d64b
+            modular_related = self.base.sack.query(flags=hawkey.IGNORE_EXCLUDES).filterm(provides=names)
d6d64b
+
d6d64b
+        requires = self.base.sack.query().filterm(empty=True)
d6d64b
+        for require in test_requires:
d6d64b
+            q = self.base.sack.query(flags=hawkey.IGNORE_EXCLUDES).filterm(provides=require)
d6d64b
+
d6d64b
+            if not q:
d6d64b
+                #  TODO(jmracek) Shell we end with an error or with RC 1?
d6d64b
+                logger.warning((_("Unable to satisfy require {}").format(require)))
d6d64b
+            else:
d6d64b
+                if self.opts.newest_only:
d6d64b
+                    if not modular_related.intersection(q):
d6d64b
+                        q.filterm(latest_per_arch_by_priority=1)
d6d64b
+                requires = requires.union(q.difference(done))
d6d64b
+        done = done.union(requires)
d6d64b
+        if requires:
d6d64b
+            done = self._get_providers_of_requires(requires, done=done, req_dict=req_dict)
d6d64b
+
d6d64b
+        return done
d6d64b
--
d6d64b
libgit2 1.1.0
d6d64b