Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package dnf-plugins-core for 
openSUSE:Factory checked in at 2021-02-03 19:55:53
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/dnf-plugins-core (Old)
 and      /work/SRC/openSUSE:Factory/.dnf-plugins-core.new.28504 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "dnf-plugins-core"

Wed Feb  3 19:55:53 2021 rev:15 rq:868795 version:4.0.19

Changes:
--------
--- /work/SRC/openSUSE:Factory/dnf-plugins-core/dnf-plugins-core.changes        
2020-10-28 11:26:15.986902896 +0100
+++ 
/work/SRC/openSUSE:Factory/.dnf-plugins-core.new.28504/dnf-plugins-core.changes 
    2021-02-03 19:56:00.257694933 +0100
@@ -1,0 +2,11 @@
+Wed Feb  3 02:10:58 UTC 2021 - Neal Gompa <[email protected]>
+
+- Update to version 4.0.19
+  + copr: allow only 2 arguments with copr enable command
+  + [needs-restarting] fix -r in nspawn containers (rh#1913962, rh#1914251)
+  + Add --gpgcheck option to reposync (rh#1856818) (rh#1856818)
+  + Re-introduce yum-groups-manager functionality (rh#1826016)
+  + [repomanage] Don't use cached metadata (rh#1899852)
+  + [needs-restarting] add -s to list services (rh#1772939) (rh#1772939)
+
+-------------------------------------------------------------------

Old:
----
  dnf-plugins-core-4.0.18.tar.gz

New:
----
  dnf-plugins-core-4.0.19.tar.gz

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

Other differences:
------------------
++++++ dnf-plugins-core.spec ++++++
--- /var/tmp/diff_new_pack.68OkfZ/_old  2021-02-03 19:56:01.097695945 +0100
+++ /var/tmp/diff_new_pack.68OkfZ/_new  2021-02-03 19:56:01.097695945 +0100
@@ -2,7 +2,7 @@
 # spec file for package dnf-plugins-core
 #
 # Copyright (c) 2020 SUSE LLC
-# Copyright (c) 2020 Neal Gompa <[email protected]>.
+# Copyright (c) 2020-2021 Neal Gompa <[email protected]>.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -56,7 +56,7 @@
 #global prerel rc1
 
 Name:           dnf-plugins-core
-Version:        4.0.18
+Version:        4.0.19
 Release:        0
 Summary:        Core Plugins for DNF
 License:        GPL-2.0-or-later
@@ -84,6 +84,7 @@
 Provides:       dnf-command(debug-restore)
 Provides:       dnf-command(debuginfo-install)
 Provides:       dnf-command(download)
+Provides:       dnf-command(groups-manager)
 Provides:       dnf-command(repoclosure)
 Provides:       dnf-command(repodiff)
 Provides:       dnf-command(repograph)
@@ -101,6 +102,7 @@
 Provides:       dnf-plugin-debuginfo-install = %{version}-%{release}
 Provides:       dnf-plugin-download = %{version}-%{release}
 Provides:       dnf-plugin-generate_completion_cache = %{version}-%{release}
+Provides:       dnf-plugin-groups-manager = %{version}-%{release}
 Provides:       dnf-plugin-needs_restarting = %{version}-%{release}
 Provides:       dnf-plugin-repoclosure = %{version}-%{release}
 Provides:       dnf-plugin-repograph = %{version}-%{release}
@@ -111,18 +113,20 @@
 
 %description
 Core Plugins for DNF. This package enhances DNF with the builddep, 
config-manager,
-%{?_with_copr_plugin:copr, }debug, debuginfo-install, download, 
needs-restarting,
-repoclosure, repograph, repomanage, and reposync commands. Additionally, it 
provides
-the generate_completion_cache passive plugin.
+%{?_with_copr_plugin:copr, }debug, debuginfo-install, download, groups-manager,
+needs-restarting, repoclosure, repograph, repomanage, and reposync commands.
+Additionally, it provides the generate_completion_cache passive plugin.
 
 %package -n python3-dnf-plugins-core
 Summary:        Python 3 interface to core plugins for DNF
 Group:          System/Packages
 BuildRequires:  python3-Sphinx
+BuildRequires:  python3-dbus-python
 BuildRequires:  python3-devel
 BuildRequires:  python3-dnf >= %{dnf_lowest_compatible}
 BuildRequires:  python3-pytest
 Requires:       python3-dateutil
+Requires:       python3-dbus-python
 Requires:       python3-distro
 Requires:       python3-dnf >= %{dnf_lowest_compatible}
 Requires:       python3-hawkey >= %{hawkey_version}
@@ -145,7 +149,9 @@
 %description -n python3-dnf-plugins-core
 Core Plugins for DNF, Python 3 interface. This package enhances DNF with
 the builddep, config-manager, %{?_with_copr_plugin:copr, }debug, 
debuginfo-install,
-download, needs-restarting, repoclosure, repograph, repomanage, and reposync 
commands.
+download, groups-manager, needs-restarting, repoclosure, repograph, repomanage,
+and reposync commands.
+
 Additionally, it provides the generate_completion_cache passive plugin.
 
 %package -n %{yum_utils_subpackage_name}
@@ -174,9 +180,9 @@
 
 %description -n %{yum_utils_subpackage_name}
 As a Yum-utils CLI compatibility layer, supplies in CLI shims for
-debuginfo-install, repograph, package-cleanup, repoclosure, repomanage,
-repoquery, reposync, repotrack, builddep, config-manager, debug, and
-download that use new implementations using DNF.
+debuginfo-install, groups-manager, repograph, package-cleanup, repoclosure,
+repomanage, repoquery, reposync, repotrack, builddep, config-manager, debug,
+and download that use new implementations using DNF.
 
 
 %package -n python3-dnf-plugin-leaves
@@ -316,6 +322,7 @@
 ln -sf %{_libexecdir}/dnf-utils %{buildroot}%{_bindir}/yum-config-manager
 ln -sf %{_libexecdir}/dnf-utils %{buildroot}%{_bindir}/yum-debug-dump
 ln -sf %{_libexecdir}/dnf-utils %{buildroot}%{_bindir}/yum-debug-restore
+ln -sf %{_libexecdir}/dnf-utils %{buildroot}%{_bindir}/yum-groups-manager
 ln -sf %{_libexecdir}/dnf-utils %{buildroot}%{_bindir}/yumdownloader
 
 %if %{with deconflict}
@@ -354,6 +361,7 @@
 %{_mandir}/man8/dnf-debuginfo-install.*
 %{_mandir}/man8/dnf-download.*
 %{_mandir}/man8/dnf-generate_completion_cache.*
+%{_mandir}/man8/dnf-groups-manager.*
 %{_mandir}/man8/dnf-needs-restarting.*
 %{_mandir}/man8/dnf-repoclosure.*
 %{_mandir}/man8/dnf-repograph.*
@@ -382,6 +390,7 @@
 %{python3_sitelib}/dnf-plugins/debuginfo-install.py
 %{python3_sitelib}/dnf-plugins/download.py
 %{python3_sitelib}/dnf-plugins/generate_completion_cache.py
+%{python3_sitelib}/dnf-plugins/groups_manager.py
 %{python3_sitelib}/dnf-plugins/needs_restarting.py
 %{python3_sitelib}/dnf-plugins/repoclosure.py
 %{python3_sitelib}/dnf-plugins/repograph.py
@@ -420,6 +429,8 @@
 %{_mandir}/man1/yum-debug-dump.1*
 %{_bindir}/yum-debug-restore
 %{_mandir}/man1/yum-debug-restore.1*
+%{_bindir}/yum-groups-manager
+%{_mandir}/man1/yum-groups-manager.1*
 %{_bindir}/yumdownloader
 %{_mandir}/man1/yumdownloader.1*
 %{_mandir}/man1/yum-changelog.1*

++++++ dnf-plugins-core-4.0.18.tar.gz -> dnf-plugins-core-4.0.19.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/CMakeLists.txt 
new/dnf-plugins-core-4.0.19/CMakeLists.txt
--- old/dnf-plugins-core-4.0.18/CMakeLists.txt  2020-10-06 18:54:52.000000000 
+0200
+++ new/dnf-plugins-core-4.0.19/CMakeLists.txt  2021-01-28 18:02:06.000000000 
+0100
@@ -29,4 +29,7 @@
 ADD_SUBDIRECTORY (plugins)
 ADD_SUBDIRECTORY (po)
 
+ENABLE_TESTING()
+ADD_SUBDIRECTORY (tests)
+
 CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/libexec/dnf-utils.in 
${CMAKE_SOURCE_DIR}/libexec/dnf-utils-${PYTHON_VERSION_MAJOR} @ONLY)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/bors.toml 
new/dnf-plugins-core-4.0.19/bors.toml
--- old/dnf-plugins-core-4.0.18/bors.toml       1970-01-01 01:00:00.000000000 
+0100
+++ new/dnf-plugins-core-4.0.19/bors.toml       2021-01-28 18:02:06.000000000 
+0100
@@ -0,0 +1,2 @@
+status = ["DNF CI"]
+timeout_sec = 10800  # 3 hours
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/dnf-plugins-core.spec 
new/dnf-plugins-core-4.0.19/dnf-plugins-core.spec
--- old/dnf-plugins-core-4.0.18/dnf-plugins-core.spec   2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/dnf-plugins-core.spec   2021-01-28 
18:02:06.000000000 +0100
@@ -33,7 +33,7 @@
 %endif
 
 Name:           dnf-plugins-core
-Version:        4.0.18
+Version:        4.0.19
 Release:        1%{?dist}
 Summary:        Core Plugins for DNF
 License:        GPLv2+
@@ -58,6 +58,7 @@
 Provides:       dnf-command(debug-restore)
 Provides:       dnf-command(debuginfo-install)
 Provides:       dnf-command(download)
+Provides:       dnf-command(groups-manager)
 Provides:       dnf-command(repoclosure)
 Provides:       dnf-command(repograph)
 Provides:       dnf-command(repomanage)
@@ -73,6 +74,7 @@
 Provides:       dnf-plugin-download = %{version}-%{release}
 Provides:       dnf-plugin-generate_completion_cache = %{version}-%{release}
 Provides:       dnf-plugin-needs_restarting = %{version}-%{release}
+Provides:       dnf-plugin-groups-manager = %{version}-%{release}
 Provides:       dnf-plugin-repoclosure = %{version}-%{release}
 Provides:       dnf-plugin-repodiff = %{version}-%{release}
 Provides:       dnf-plugin-repograph = %{version}-%{release}
@@ -87,7 +89,7 @@
 
 %description
 Core Plugins for DNF. This package enhances DNF with builddep, config-manager,
-copr, debug, debuginfo-install, download, needs-restarting, repoclosure,
+copr, debug, debuginfo-install, download, needs-restarting, groups-manager, 
repoclosure,
 repograph, repomanage, reposync, changelog and repodiff commands. Additionally
 provides generate_completion_cache passive plugin.
 
@@ -97,8 +99,10 @@
 %{?python_provide:%python_provide python2-%{name}}
 BuildRequires:  python2-dnf >= %{dnf_lowest_compatible}
 %if 0%{?rhel} && 0%{?rhel} <= 7
+BuildRequires:  dbus-python
 BuildRequires:  python-nose
 %else
+BuildRequires:  python2-dbus
 BuildRequires:  python2-nose
 %endif
 BuildRequires:  python2-devel
@@ -108,8 +112,10 @@
 Requires:       python2-dnf >= %{dnf_lowest_compatible}
 Requires:       python2-hawkey >= %{hawkey_version}
 %if 0%{?rhel} && 0%{?rhel} <= 7
+Requires:       dbus-python
 Requires:       python-dateutil
 %else
+Requires:       python2-dbus
 Requires:       python2-dateutil
 %endif
 Provides:       python2-dnf-plugins-extras-debug = %{version}-%{release}
@@ -129,7 +135,8 @@
 %description -n python2-%{name}
 Core Plugins for DNF, Python 2 interface. This package enhances DNF with 
builddep,
 config-manager, copr, degug, debuginfo-install, download, needs-restarting,
-repoclosure, repograph, repomanage, reposync, changelog and repodiff commands.
+groups-manager, repoclosure, repograph, repomanage, reposync, changelog
+and repodiff commands.
 Additionally provides generate_completion_cache passive plugin.
 %endif
 
@@ -137,12 +144,14 @@
 %package -n python3-%{name}
 Summary:    Core Plugins for DNF
 %{?python_provide:%python_provide python3-%{name}}
+BuildRequires:  python3-dbus
 BuildRequires:  python3-devel
 BuildRequires:  python3-dnf >= %{dnf_lowest_compatible}
 BuildRequires:  python3-nose
 %if 0%{?fedora}
 Requires:       python3-distro
 %endif
+Requires:       python3-dbus
 Requires:       python3-dnf >= %{dnf_lowest_compatible}
 Requires:       python3-hawkey >= %{hawkey_version}
 Requires:       python3-dateutil
@@ -163,7 +172,8 @@
 %description -n python3-%{name}
 Core Plugins for DNF, Python 3 interface. This package enhances DNF with 
builddep,
 config-manager, copr, debug, debuginfo-install, download, needs-restarting,
-repoclosure, repograph, repomanage, reposync, changelog and repodiff commands.
+groups-manager, repoclosure, repograph, repomanage, reposync, changelog
+and repodiff commands.
 Additionally provides generate_completion_cache passive plugin.
 %endif
 
@@ -190,8 +200,8 @@
 %description -n %{yum_utils_subpackage_name}
 As a Yum-utils CLI compatibility layer, supplies in CLI shims for
 debuginfo-install, repograph, package-cleanup, repoclosure, repomanage,
-repoquery, reposync, repotrack, repodiff, builddep, config-manager, debug
-and download that use new implementations using DNF.
+repoquery, reposync, repotrack, repodiff, builddep, config-manager, debug,
+download and yum-groups-manager that use new implementations using DNF.
 %endif
 
 %if 0%{?rhel} == 0 && %{with python2}
@@ -458,6 +468,7 @@
 ln -sf %{_libexecdir}/dnf-utils %{buildroot}%{_bindir}/yum-config-manager
 ln -sf %{_libexecdir}/dnf-utils %{buildroot}%{_bindir}/yum-debug-dump
 ln -sf %{_libexecdir}/dnf-utils %{buildroot}%{_bindir}/yum-debug-restore
+ln -sf %{_libexecdir}/dnf-utils %{buildroot}%{_bindir}/yum-groups-manager
 ln -sf %{_libexecdir}/dnf-utils %{buildroot}%{_bindir}/yumdownloader
 # These commands don't have a dedicated man page, so let's just point them
 # to the utils page which contains their descriptions.
@@ -468,10 +479,14 @@
 
 %check
 %if %{with python2}
-PYTHONPATH=./plugins nosetests-%{python2_version} -s tests/
+    pushd build-py2
+    ctest -VV
+    popd
 %endif
 %if %{with python3}
-PYTHONPATH=./plugins nosetests-%{python3_version} -s tests/
+    pushd build-py3
+    ctest -VV
+    popd
 %endif
 
 %files
@@ -483,6 +498,7 @@
 %{_mandir}/man8/dnf-debuginfo-install.*
 %{_mandir}/man8/dnf-download.*
 %{_mandir}/man8/dnf-generate_completion_cache.*
+%{_mandir}/man8/dnf-groups-manager.*
 %{_mandir}/man8/dnf-needs-restarting.*
 %{_mandir}/man8/dnf-repoclosure.*
 %{_mandir}/man8/dnf-repodiff.*
@@ -513,6 +529,7 @@
 %{python2_sitelib}/dnf-plugins/debuginfo-install.*
 %{python2_sitelib}/dnf-plugins/download.*
 %{python2_sitelib}/dnf-plugins/generate_completion_cache.*
+%{python2_sitelib}/dnf-plugins/groups_manager.*
 %{python2_sitelib}/dnf-plugins/needs_restarting.*
 %{python2_sitelib}/dnf-plugins/repoclosure.*
 %{python2_sitelib}/dnf-plugins/repodiff.*
@@ -538,6 +555,7 @@
 %{python3_sitelib}/dnf-plugins/debuginfo-install.py
 %{python3_sitelib}/dnf-plugins/download.py
 %{python3_sitelib}/dnf-plugins/generate_completion_cache.py
+%{python3_sitelib}/dnf-plugins/groups_manager.py
 %{python3_sitelib}/dnf-plugins/needs_restarting.py
 %{python3_sitelib}/dnf-plugins/repoclosure.py
 %{python3_sitelib}/dnf-plugins/repodiff.py
@@ -552,6 +570,7 @@
 %{python3_sitelib}/dnf-plugins/__pycache__/debuginfo-install.*
 %{python3_sitelib}/dnf-plugins/__pycache__/download.*
 %{python3_sitelib}/dnf-plugins/__pycache__/generate_completion_cache.*
+%{python3_sitelib}/dnf-plugins/__pycache__/groups_manager.*
 %{python3_sitelib}/dnf-plugins/__pycache__/needs_restarting.*
 %{python3_sitelib}/dnf-plugins/__pycache__/repoclosure.*
 %{python3_sitelib}/dnf-plugins/__pycache__/repodiff.*
@@ -579,6 +598,7 @@
 %{_bindir}/yum-config-manager
 %{_bindir}/yum-debug-dump
 %{_bindir}/yum-debug-restore
+%{_bindir}/yum-groups-manager
 %{_bindir}/yumdownloader
 %{_mandir}/man1/debuginfo-install.*
 %{_mandir}/man1/needs-restarting.*
@@ -591,6 +611,7 @@
 %{_mandir}/man1/yum-config-manager.*
 %{_mandir}/man1/yum-debug-dump.*
 %{_mandir}/man1/yum-debug-restore.*
+%{_mandir}/man1/yum-groups-manager.*
 %{_mandir}/man1/yumdownloader.*
 %{_mandir}/man1/package-cleanup.*
 %{_mandir}/man1/dnf-utils.*
@@ -612,6 +633,7 @@
 %exclude %{_mandir}/man1/yum-config-manager.*
 %exclude %{_mandir}/man1/yum-debug-dump.*
 %exclude %{_mandir}/man1/yum-debug-restore.*
+%exclude %{_mandir}/man1/yum-groups-manager.*
 %exclude %{_mandir}/man1/yumdownloader.*
 %exclude %{_mandir}/man1/package-cleanup.*
 %exclude %{_mandir}/man1/dnf-utils.*
@@ -744,6 +766,14 @@
 %endif
 
 %changelog
+* Thu Jan 28 2021 Nicola Sella <[email protected]> - 4.0.19-1
+- copr: allow only 2 arguments with copr enable command
+- [needs-restarting] fix -r in nspawn containers (RhBug:1913962,1914251)
+- Add --gpgcheck option to reposync (RhBug:1856818) (RhBug:1856818)
+- Re-introduce yum-groups-manager functionality (RhBug:1826016)
+- [repomanage] Don't use cached metadata (RhBug:1899852)
+- [needs-restarting] add -s to list services (RhBug:1772939) (RhBug:1772939)
+
 * Tue Oct 06 2020 Nicola Sella <[email protected]> - 4.0.18-1
 - [needs-restarting] Fix plugin fail if needs-restarting.d does not exist
 - [needs-restarting] add kernel-rt to reboot list
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/doc/CMakeLists.txt 
new/dnf-plugins-core-4.0.19/doc/CMakeLists.txt
--- old/dnf-plugins-core-4.0.18/doc/CMakeLists.txt      2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/doc/CMakeLists.txt      2021-01-28 
18:02:06.000000000 +0100
@@ -26,6 +26,7 @@
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-debuginfo-install.8
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-download.8
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-generate_completion_cache.8
+    ${CMAKE_CURRENT_BINARY_DIR}/dnf-groups-manager.8
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-leaves.8
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-needs-restarting.8
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-repoclosure.8
@@ -61,6 +62,7 @@
     ${CMAKE_CURRENT_BINARY_DIR}/yum-config-manager.1
     ${CMAKE_CURRENT_BINARY_DIR}/yum-debug-dump.1
     ${CMAKE_CURRENT_BINARY_DIR}/yum-debug-restore.1
+    ${CMAKE_CURRENT_BINARY_DIR}/yum-groups-manager.1
     ${CMAKE_CURRENT_BINARY_DIR}/yumdownloader.1
     ${CMAKE_CURRENT_BINARY_DIR}/package-cleanup.1
     ${CMAKE_CURRENT_BINARY_DIR}/dnf-utils.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/doc/conf.py 
new/dnf-plugins-core-4.0.19/doc/conf.py
--- old/dnf-plugins-core-4.0.18/doc/conf.py     2020-10-06 18:54:52.000000000 
+0200
+++ new/dnf-plugins-core-4.0.19/doc/conf.py     2021-01-28 18:02:06.000000000 
+0100
@@ -251,6 +251,7 @@
     ('download', 'dnf-download', u'DNF download Plugin', AUTHORS, 8),
     ('generate_completion_cache', 'dnf-generate_completion_cache',
         u'DNF generate_completion_cache Plugin', AUTHORS, 8),
+    ('groups-manager', 'dnf-groups-manager', u'DNF groups-manager Plugin', 
AUTHORS, 8),
     ('leaves', 'dnf-leaves', u'DNF leaves Plugin', AUTHORS, 8),
     ('local', 'dnf-local', u'DNF local Plugin', AUTHORS, 8),
     ('needs_restarting', 'dnf-needs-restarting', u'DNF needs_restarting 
Plugin', AUTHORS, 8),
@@ -268,6 +269,7 @@
     ('copr', 'yum-copr', u'redirecting to DNF copr Plugin', AUTHORS, 8),
     ('debuginfo-install', 'debuginfo-install', u'redirecting to DNF 
debuginfo-install Plugin',
      AUTHORS, 1),
+    ('groups-manager', 'yum-groups-manager', u'redirecting to DNF 
groups-manager Plugin', AUTHORS, 1),
     ('needs_restarting', 'needs-restarting', u'redirecting to DNF 
needs-restarting Plugin',
      AUTHORS, 1),
     ('repoclosure', 'repoclosure', u'redirecting to DNF repoclosure Plugin', 
AUTHORS, 1),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/doc/groups-manager.rst 
new/dnf-plugins-core-4.0.19/doc/groups-manager.rst
--- old/dnf-plugins-core-4.0.18/doc/groups-manager.rst  1970-01-01 
01:00:00.000000000 +0100
+++ new/dnf-plugins-core-4.0.19/doc/groups-manager.rst  2021-01-28 
18:02:06.000000000 +0100
@@ -0,0 +1,94 @@
+..
+  Copyright (C) 2020  Red Hat, Inc.
+
+  This copyrighted material is made available to anyone wishing to use,
+  modify, copy, or redistribute it subject to the terms and conditions of
+  the GNU General Public License v.2, or (at your option) any later version.
+  This program is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY expressed or implied, including the implied warranties of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+  Public License for more details.  You should have received a copy of the
+  GNU General Public License along with this program; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+  02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
+  source code or documentation are not subject to the GNU General Public
+  License and may only be used or replicated with the express permission of
+  Red Hat, Inc.
+
+=========================
+DNF groups-manager Plugin
+=========================
+
+Create and edit groups repository metadata files.
+
+--------
+Synopsis
+--------
+
+``dnf groups-manager [options] [package-name-spec [package-name-spec ...]]``
+
+-----------
+Description
+-----------
+groups-manager plugin is used to create or edit a group metadata file for a 
repository. This is often much easier than writing/editing the XML by hand. The 
groups-manager can load an entire file of groups metadata and either create a 
new group or edit an existing group and then write all of the groups metadata 
back out.
+
+---------
+Arguments
+---------
+
+``<package-name-spec>``
+    Package to add to a group or remove from a group.
+
+-------
+Options
+-------
+
+All general DNF options are accepted, see `Options` in :manpage:`dnf(8)` for 
details.
+
+``--load=<path_to_comps.xml>``
+    Load the groups metadata information from the specified file before 
performing any operations. Metadata from all files are merged together if the 
option is specified multiple times.
+
+``--save=<path_to_comps.xml>``
+    Save the result to this file. You can specify the name of a file you are 
loading from as the data will only be saved when all the operations have been 
performed. This option can also be specified multiple times.
+
+``--merge=<path_to_comps.xml>``
+    This is the same as loading and saving a file, however the "merge" file is 
loaded before any others and saved last.
+
+``--print``
+    Also print the result to stdout.
+
+``--id=<id>``
+    The id to lookup/use for the group. If you don't specify an ``<id>``, but 
do specify a name that doesn't refer to an existing group, then an id for the 
group is generated based on the name.
+
+``-n <name>, --name=<name>``
+    The name to lookup/use for the group. If you specify an existing group id, 
then the group with that id will have it's name changed to this value.
+
+``--description=<description>``
+    The description to use for the group.
+
+``--display-order=<display_order>``
+    Change the integer which controls the order groups are presented in, for 
example in ``dnf grouplist``.
+
+``--translated-name=<lang:text>``
+    A translation of the group name in the given language. The syntax is 
``lang:text``. Eg. ``en:my-group-name-in-english``
+
+``--translated-description=<lang:text>``
+    A translation of the group description in the given language. The syntax 
is ``lang:text``. Eg. ``en:my-group-description-in-english``.
+
+``--user-visible``
+    Make the group visible in ``dnf grouplist`` (this is the default).
+
+``--not-user-visible``
+    Make the group not visible in ``dnf grouplist``.
+
+``--mandatory``
+    Store the package names specified within the mandatory section of the 
specified group, the default is to use the default section.
+
+``--optional``
+    Store the package names specified within the optional section of the 
specified group, the default is to use the default section.
+
+``--remove``
+    Instead of adding packages remove them. Note that the packages are removed 
from all sections (default, mandatory and optional).
+
+``--dependencies``
+    Also include the names of the direct dependencies for each package 
specified.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/doc/index.rst 
new/dnf-plugins-core-4.0.19/doc/index.rst
--- old/dnf-plugins-core-4.0.18/doc/index.rst   2020-10-06 18:54:52.000000000 
+0200
+++ new/dnf-plugins-core-4.0.19/doc/index.rst   2021-01-28 18:02:06.000000000 
+0100
@@ -33,6 +33,7 @@
    debuginfo-install
    download
    generate_completion_cache
+   groups-manager
    leaves
    local
    migrate
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/doc/needs_restarting.rst 
new/dnf-plugins-core-4.0.19/doc/needs_restarting.rst
--- old/dnf-plugins-core-4.0.18/doc/needs_restarting.rst        2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/doc/needs_restarting.rst        2021-01-28 
18:02:06.000000000 +0100
@@ -48,3 +48,6 @@
 ``-r, --reboothint``
 
     Only report whether a reboot is required (exit code 1) or not (exit code 
0).
+
+``-s, --services``
+    Only list the affected systemd services.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/dnf-plugins-core-4.0.18/doc/post-transaction-actions.rst 
new/dnf-plugins-core-4.0.19/doc/post-transaction-actions.rst
--- old/dnf-plugins-core-4.0.18/doc/post-transaction-actions.rst        
2020-10-06 18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/doc/post-transaction-actions.rst        
2021-01-28 18:02:06.000000000 +0100
@@ -76,7 +76,12 @@
    The shell command will be evaluated for each package that matched the 
``package_filter`` and
    the ``transaction_state``. However, after variable substitution, any 
duplicate commands will be
    removed and each command will only be executed once per transaction. The 
order of execution
-   of the commands may differ from the order of packages in the transaction.
+   of the commands follows the order in the action files, but may differ from 
the order of
+   packages in the transaction.  In other words, when you define several 
action lines for the
+   same ``package_filter`` these lines will be executed in the order they were 
defined in the
+   action file when the ``package_filter`` matches a package during the 
``trasaction_state`` state.
+   However, the order of when a particular ``package_filter`` is invoked 
depends on the position
+   of the corresponding package in the transaction.
 
 An example action file:
 ^^^^^^^^^^^^^^^^^^^^^^^
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/doc/release_notes.rst 
new/dnf-plugins-core-4.0.19/doc/release_notes.rst
--- old/dnf-plugins-core-4.0.18/doc/release_notes.rst   2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/doc/release_notes.rst   2021-01-28 
18:02:06.000000000 +0100
@@ -22,6 +22,34 @@
 .. contents::
 
 ====================
+4.0.19 Release Notes
+====================
+
+- copr: allow only 2 arguments with copr enable command
+- [needs-restarting] fix -r in nspawn containers (RhBug:1913962,1914251)
+- Add --gpgcheck option to reposync (RhBug:1856818) (RhBug:1856818)
+- Re-introduce yum-groups-manager functionality (RhBug:1826016)
+- [repomanage] Don't use cached metadata (RhBug:1899852)
+- [needs-restarting] add -s to list services (RhBug:1772939) (RhBug:1772939)
+
+- New features:
+  - Add --gpgcheck option to reposync (RhBug:1856818) (RhBug:1856818)
+  - Re-introduce yum-groups-manager functionality (RhBug:1826016)
+
+- Bug fixes:
+  - [repomanage] Don't use cached metadata (RhBug:1899852)
+  - [repomanage] Fix ordering of modular stream versions
+  - [needs-restarting] add -s to list services (RhBug:1772939) (RhBug:1772939)
+
+Bugs fixed in 4.0.19:
+
+* :rhbug:`1913962`
+* :rhbug:`1772939`
+* :rhbug:`1914251`
+* :rhbug:`1899852`
+* :rhbug:`1856818`
+
+====================
 4.0.18 Release Notes
 ====================
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/doc/reposync.rst 
new/dnf-plugins-core-4.0.19/doc/reposync.rst
--- old/dnf-plugins-core-4.0.18/doc/reposync.rst        2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/doc/reposync.rst        2021-01-28 
18:02:06.000000000 +0100
@@ -39,36 +39,40 @@
 
 All general DNF options are accepted. Namely, the ``--repoid`` option can be 
used to specify the repositories to synchronize. See `Options` in 
:manpage:`dnf(8)` for details.
 
-``-p <download-path>, --download-path=<download-path>``
-    Root path under which the downloaded repositories are stored, relative to 
the current working directory. Defaults to the current working directory. Every 
downloaded repository has a subdirectory named after its ID under this path.
-    
-``--norepopath``
-    Don't add the reponame to the download path. Can only be used when syncing 
a single repository (default is to add the reponame).
+``-a <architecture>, --arch=<architecture>``
+    Download only packages of given architectures (default is all 
architectures). Can be used multiple times.
+
+``--delete``
+    Delete local packages no longer present in repository.
 
 ``--download-metadata``
     Download all repository metadata. Downloaded copy is instantly usable as a 
repository, no need to run createrepo_c on it.
 
-``-a <architecture>, --arch=<architecture>``
-    Download only packages of given architectures (default is all 
architectures). Can be used multiple times.
-
-``--source``
-    Operate on source packages.
+``-g, --gpgcheck``
+    Remove packages that fail GPG signature checking after downloading. Exit 
code is ``1`` if at least one package was removed.
+    Note that for repositories with ``gpgcheck=0`` set in their configuration 
the GPG signature is not checked even with this option used.
 
 ``-m, --downloadcomps``
     Also download and uncompress comps.xml. Consider using 
``--download-metadata`` option which will download all available repository 
metadata.
 
+``--metadata-path``
+    Root path under which the downloaded metadata are stored. It defaults to 
``--download-path`` value if not given.
+
 ``-n, --newest-only``
     Download only newest packages per-repo.
 
-``--delete``
-    Delete local packages no longer present in repository.
+``--norepopath``
+    Don't add the reponame to the download path. Can only be used when syncing 
a single repository (default is to add the reponame).
 
-``--metadata-path``
-    Root path under which the downloaded metadata are stored. It defaults to 
``--download-path`` value if not given.
+``-p <download-path>, --download-path=<download-path>``
+    Root path under which the downloaded repositories are stored, relative to 
the current working directory. Defaults to the current working directory. Every 
downloaded repository has a subdirectory named after its ID under this path.
 
 ``--remote-time``
     Try to set the timestamps of the downloaded files to those on the remote 
side.
 
+``--source``
+    Operate on source packages.
+
 ``-u, --urls``
     Just print urls of what would be downloaded, don't download.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/doc/summaries_cache 
new/dnf-plugins-core-4.0.19/doc/summaries_cache
--- old/dnf-plugins-core-4.0.18/doc/summaries_cache     2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/doc/summaries_cache     2021-01-28 
18:02:06.000000000 +0100
@@ -586,5 +586,25 @@
     [
         1844925,
         "when i execute \"dnf download\" failed i want to get return value 1 
not 0 in shell."
+    ],
+    [
+        1913962,
+        "\"dnf needs-restarting -r\" work incorrectly inside systemd-nspawn 
containers"
+    ],
+    [
+        1772939,
+        "For \"needs-restarting\" command.  re-Include option (-s --services  
--  \"List the affected systemd services only.\")"
+    ],
+    [
+        1914251,
+        "\"dnf needs-restarting -r\" work incorrectly inside systemd-nspawn 
containers"
+    ],
+    [
+        1899852,
+        "repomanage is broken, reports not existing packages"
+    ],
+    [
+        1856818,
+        "[RFE] add gpgcheck back to reposync"
     ]
-]
\ No newline at end of file
+]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/libexec/dnf-utils.in 
new/dnf-plugins-core-4.0.19/libexec/dnf-utils.in
--- old/dnf-plugins-core-4.0.18/libexec/dnf-utils.in    2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/libexec/dnf-utils.in    2021-01-28 
18:02:06.000000000 +0100
@@ -37,6 +37,7 @@
            'yum-config-manager': ['config-manager'],
            'yum-debug-dump': ['debug-dump'],
            'yum-debug-restore': ['debug-restore'],
+           'yum-groups-manager': ['groups-manager'],
            'yumdownloader': ['download']
            }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/plugins/CMakeLists.txt 
new/dnf-plugins-core-4.0.19/plugins/CMakeLists.txt
--- old/dnf-plugins-core-4.0.18/plugins/CMakeLists.txt  2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/plugins/CMakeLists.txt  2021-01-28 
18:02:06.000000000 +0100
@@ -6,6 +6,7 @@
 INSTALL (FILES copr.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
 INSTALL (FILES download.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
 INSTALL (FILES generate_completion_cache.py DESTINATION 
${PYTHON_INSTALL_DIR}/dnf-plugins)
+INSTALL (FILES groups_manager.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
 INSTALL (FILES leaves.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
 if (${WITHOUT_LOCAL} STREQUAL "0")
 INSTALL (FILES local.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/plugins/copr.py 
new/dnf-plugins-core-4.0.19/plugins/copr.py
--- old/dnf-plugins-core-4.0.18/plugins/copr.py 2020-10-06 18:54:52.000000000 
+0200
+++ new/dnf-plugins-core-4.0.19/plugins/copr.py 2021-01-28 18:02:06.000000000 
+0100
@@ -50,7 +50,7 @@
             with open('/etc/os-release') as os_release_file:
                 os_release_data = {}
                 for line in os_release_file:
-                    os_release_key, os_release_value = line.rstrip.split('=')
+                    os_release_key, os_release_value = line.rstrip().split('=')
                     os_release_data[os_release_key] = 
os_release_value.strip('"')
                 return (os_release_data['NAME'], 
os_release_data['VERSION_ID'], None)
 
@@ -223,8 +223,15 @@
                   'copr command are required'))
         try:
             chroot = self.opts.arg[1]
+            if len(self.opts.arg) > 2:
+                raise dnf.exceptions.Error(_('Too many arguments.'))
+            self.chroot_parts = chroot.split("-")
+            if len(self.chroot_parts) < 3:
+                raise dnf.exceptions.Error(_('Bad format of optional chroot. 
The format is '
+                                             
'distribution-version-architecture.'))
         except IndexError:
             chroot = self._guess_chroot()
+            self.chroot_parts = chroot.split("-")
 
         # commands without defined copr_username/copr_projectname
         if subcommand == "search":
@@ -267,7 +274,7 @@
                                 copr_projectname])
             msg = "Do you really want to enable {0}?".format(project)
             self._ask_user(info, msg)
-            self._download_repo(project_name, repo_filename, chroot)
+            self._download_repo(project_name, repo_filename)
             logger.info(_("Repository successfully enabled."))
             self._runtime_deps_warning(copr_username, copr_projectname)
         elif subcommand == "disable":
@@ -453,11 +460,9 @@
             chroot = ("epel-%s-x86_64" % dist[1].split(".", 1)[0])
         return chroot
 
-    def _download_repo(self, project_name, repo_filename, chroot=None):
-        if chroot is None:
-            chroot = self._guess_chroot()
-        short_chroot = '-'.join(chroot.split('-')[:2])
-        arch = chroot.split('-')[2]
+    def _download_repo(self, project_name, repo_filename):
+        short_chroot = '-'.join(self.chroot_parts[:-1])
+        arch = self.chroot_parts[-1]
         api_path = 
"/coprs/{0}/repo/{1}/dnf.repo?arch={2}".format(project_name, short_chroot, arch)
 
         try:
@@ -660,7 +665,7 @@
                 f.close()
                 if (output2 and ("output" in output2)
                         and (output2["output"] == "ok")):
-                    self._download_repo(project_name, repo_filename, chroot)
+                    self._download_repo(project_name, repo_filename)
             except dnf.exceptions.Error:
                 # likely 404 and that repo does not exist
                 pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/plugins/groups_manager.py 
new/dnf-plugins-core-4.0.19/plugins/groups_manager.py
--- old/dnf-plugins-core-4.0.18/plugins/groups_manager.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/dnf-plugins-core-4.0.19/plugins/groups_manager.py       2021-01-28 
18:02:06.000000000 +0100
@@ -0,0 +1,314 @@
+# groups_manager.py
+# DNF plugin for managing comps groups metadata files
+#
+# Copyright (C) 2020 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+# Public License for more details.  You should have received a copy of the
+# GNU General Public License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import argparse
+import gzip
+import libcomps
+import os
+import re
+import shutil
+import tempfile
+
+from dnfpluginscore import _, logger
+import dnf
+import dnf.cli
+
+
+RE_GROUP_ID_VALID = '-a-z0-9_.:'
+RE_GROUP_ID = re.compile(r'^[{}]+$'.format(RE_GROUP_ID_VALID))
+RE_LANG = re.compile(r'^[-a-zA-Z0-9_.@]+$')
+COMPS_XML_OPTIONS = {
+    'default_explicit': True,
+    'uservisible_explicit': True,
+    'empty_groups': True}
+
+
+def group_id_type(value):
+    '''group id validator'''
+    if not RE_GROUP_ID.match(value):
+        raise argparse.ArgumentTypeError(_('Invalid group id'))
+    return value
+
+
+def translation_type(value):
+    '''translated texts validator'''
+    data = value.split(':', 2)
+    if len(data) != 2:
+        raise argparse.ArgumentTypeError(
+            _("Invalid translated data, should be in form 'lang:text'"))
+    lang, text = data
+    if not RE_LANG.match(lang):
+        raise argparse.ArgumentTypeError(_('Invalid/empty language for 
translated data'))
+    return lang, text
+
+
+def text_to_id(text):
+    '''generate group id based on its name'''
+    group_id = text.lower()
+    group_id = re.sub('[^{}]'.format(RE_GROUP_ID_VALID), '', group_id)
+    if not group_id:
+        raise dnf.cli.CliError(
+            _("Can't generate group id from '{}'. Please specify group id 
using --id.").format(
+                text))
+    return group_id
+
+
[email protected]_command
+class GroupsManagerCommand(dnf.cli.Command):
+    aliases = ('groups-manager',)
+    summary = _('create and edit groups metadata file')
+
+    def __init__(self, cli):
+        super(GroupsManagerCommand, self).__init__(cli)
+        self.comps = libcomps.Comps()
+
+    @staticmethod
+    def set_argparser(parser):
+        # input / output options
+        parser.add_argument('--load', action='append', default=[],
+                            metavar='COMPS.XML',
+                            help=_('load groups metadata from file'))
+        parser.add_argument('--save', action='append', default=[],
+                            metavar='COMPS.XML',
+                            help=_('save groups metadata to file'))
+        parser.add_argument('--merge', metavar='COMPS.XML',
+                            help=_('load and save groups metadata to file'))
+        parser.add_argument('--print', action='store_true', default=False,
+                            help=_('print the result metadata to stdout'))
+        # group options
+        parser.add_argument('--id', type=group_id_type,
+                            help=_('group id'))
+        parser.add_argument('-n', '--name', help=_('group name'))
+        parser.add_argument('--description',
+                            help=_('group description'))
+        parser.add_argument('--display-order', type=int,
+                            help=_('group display order'))
+        parser.add_argument('--translated-name', action='append', default=[],
+                            metavar='LANG:TEXT', type=translation_type,
+                            help=_('translated name for the group'))
+        parser.add_argument('--translated-description', action='append', 
default=[],
+                            metavar='LANG:TEXT', type=translation_type,
+                            help=_('translated description for the group'))
+        visible = parser.add_mutually_exclusive_group()
+        visible.add_argument('--user-visible', dest='user_visible', 
action='store_true',
+                             default=None,
+                             help=_('make the group user visible (default)'))
+        visible.add_argument('--not-user-visible', dest='user_visible', 
action='store_false',
+                             default=None,
+                             help=_('make the group user invisible'))
+
+        # package list options
+        section = parser.add_mutually_exclusive_group()
+        section.add_argument('--mandatory', action='store_true',
+                             help=_('add packages to the mandatory section'))
+        section.add_argument('--optional', action='store_true',
+                             help=_('add packages to the optional section'))
+        section.add_argument('--remove', action='store_true', default=False,
+                             help=_('remove packages from the group instead of 
adding them'))
+        parser.add_argument('--dependencies', action='store_true',
+                            help=_('include also direct dependencies for 
packages'))
+
+        parser.add_argument("packages", nargs='*', metavar='PACKAGE',
+                            help=_('package specification'))
+
+    def configure(self):
+        demands = self.cli.demands
+
+        if self.opts.packages:
+            demands.sack_activation = True
+            demands.available_repos = True
+            demands.load_system_repo = False
+
+        # handle --merge option (shortcut to --load and --save the same file)
+        if self.opts.merge:
+            self.opts.load.insert(0, self.opts.merge)
+            self.opts.save.append(self.opts.merge)
+
+        # check that group is specified when editing is attempted
+        if (self.opts.description
+                or self.opts.display_order
+                or self.opts.translated_name
+                or self.opts.translated_description
+                or self.opts.user_visible is not None
+                or self.opts.packages):
+            if not self.opts.id and not self.opts.name:
+                raise dnf.cli.CliError(
+                    _("Can't edit group without specifying it (use --id or 
--name)"))
+
+    def load_input_files(self):
+        """
+        Loads all input xml files.
+        Returns True if at least one file was successfuly loaded
+        """
+        for file_name in self.opts.load:
+            file_comps = libcomps.Comps()
+            try:
+                if file_name.endswith('.gz'):
+                    # libcomps does not support gzipped files - decompress to 
temporary
+                    # location
+                    with gzip.open(file_name) as gz_file:
+                        temp_file = tempfile.NamedTemporaryFile(delete=False)
+                        try:
+                            shutil.copyfileobj(gz_file, temp_file)
+                            # close temp_file to ensure the content is flushed 
to disk
+                            temp_file.close()
+                            file_comps.fromxml_f(temp_file.name)
+                        finally:
+                            os.unlink(temp_file.name)
+                else:
+                    file_comps.fromxml_f(file_name)
+            except (IOError, OSError, libcomps.ParserError) as err:
+                # gzip module raises OSError on reading from malformed gz file
+                # get_last_errors() output often contains duplicit lines, 
remove them
+                seen = set()
+                for error in file_comps.get_last_errors():
+                    if error in seen:
+                        continue
+                    logger.error(error.strip())
+                    seen.add(error)
+                raise dnf.exceptions.Error(
+                    _("Can't load file \"{}\": {}").format(file_name, err))
+            else:
+                self.comps += file_comps
+
+    def save_output_files(self):
+        for file_name in self.opts.save:
+            try:
+                # xml_f returns a list of errors / log entries
+                errors = self.comps.xml_f(file_name, 
xml_options=COMPS_XML_OPTIONS)
+            except libcomps.XMLGenError as err:
+                errors = [err]
+            if errors:
+                # xml_f() method could return more than one error. In this case
+                # raise the latest of them and log the others.
+                for err in errors[:-1]:
+                    logger.error(err.strip())
+                raise dnf.exceptions.Error(_("Can't save file \"{}\": 
{}").format(
+                    file_name, errors[-1].strip()))
+
+
+    def find_group(self, group_id, name):
+        '''
+        Try to find group according to command line parameters - first by id
+        then by name.
+        '''
+        group = None
+        if group_id:
+            for grp in self.comps.groups:
+                if grp.id == group_id:
+                    group = grp
+                    break
+        if group is None and name:
+            for grp in self.comps.groups:
+                if grp.name == name:
+                    group = grp
+                    break
+        return group
+
+    def edit_group(self, group):
+        '''
+        Set attributes and package lists for selected group
+        '''
+        def langlist_to_strdict(lst):
+            str_dict = libcomps.StrDict()
+            for lang, text in lst:
+                str_dict[lang] = text
+            return str_dict
+
+        # set group attributes
+        if self.opts.name:
+            group.name = self.opts.name
+        if self.opts.description:
+            group.desc = self.opts.description
+        if self.opts.display_order:
+            group.display_order = self.opts.display_order
+        if self.opts.user_visible is not None:
+            group.uservisible = self.opts.user_visible
+        if self.opts.translated_name:
+            group.name_by_lang = langlist_to_strdict(self.opts.translated_name)
+        if self.opts.translated_description:
+            group.desc_by_lang = 
langlist_to_strdict(self.opts.translated_description)
+
+        # edit packages list
+        if self.opts.packages:
+            # find packages according to specifications from command line
+            packages = set()
+            for pkg_spec in self.opts.packages:
+                q = 
self.base.sack.query().filterm(name__glob=pkg_spec).latest()
+                if not q:
+                    logger.warning(_("No match for argument: 
{}").format(pkg_spec))
+                    continue
+                packages.update(q)
+            if self.opts.dependencies:
+                # add packages that provide requirements
+                requirements = set()
+                for pkg in packages:
+                    requirements.update(pkg.requires)
+                
packages.update(self.base.sack.query().filterm(provides=requirements))
+
+            pkg_names = {pkg.name for pkg in packages}
+
+            if self.opts.remove:
+                for pkg_name in pkg_names:
+                    for pkg in group.packages_match(name=pkg_name,
+                                                    
type=libcomps.PACKAGE_TYPE_UNKNOWN):
+                        group.packages.remove(pkg)
+            else:
+                if self.opts.mandatory:
+                    pkg_type = libcomps.PACKAGE_TYPE_MANDATORY
+                elif self.opts.optional:
+                    pkg_type = libcomps.PACKAGE_TYPE_OPTIONAL
+                else:
+                    pkg_type = libcomps.PACKAGE_TYPE_DEFAULT
+                for pkg_name in sorted(pkg_names):
+                    if not group.packages_match(name=pkg_name, type=pkg_type):
+                        group.packages.append(libcomps.Package(name=pkg_name, 
type=pkg_type))
+
+    def run(self):
+        self.load_input_files()
+
+        if self.opts.id or self.opts.name:
+            # we are adding / editing a group
+            group = self.find_group(group_id=self.opts.id, name=self.opts.name)
+            if group is None:
+                # create a new group
+                if self.opts.remove:
+                    raise dnf.exceptions.Error(_("Can't remove packages from 
non-existent group"))
+                group = libcomps.Group()
+                if self.opts.id:
+                    group.id = self.opts.id
+                    group.name = self.opts.id
+                elif self.opts.name:
+                    group_id = text_to_id(self.opts.name)
+                    if self.find_group(group_id=group_id, name=None):
+                        raise dnf.cli.CliError(
+                            _("Group id '{}' generated from '{}' is duplicit. "
+                              "Please specify group id using --id.").format(
+                                  group_id, self.opts.name))
+                    group.id = group_id
+                self.comps.groups.append(group)
+            self.edit_group(group)
+
+        self.save_output_files()
+        if self.opts.print or (not self.opts.save):
+            print(self.comps.xml_str(xml_options=COMPS_XML_OPTIONS))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/plugins/needs_restarting.py 
new/dnf-plugins-core-4.0.19/plugins/needs_restarting.py
--- old/dnf-plugins-core-4.0.18/plugins/needs_restarting.py     2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/plugins/needs_restarting.py     2021-01-28 
18:02:06.000000000 +0100
@@ -29,6 +29,7 @@
 
 import dnf
 import dnf.cli
+import dbus
 import functools
 import os
 import re
@@ -126,6 +127,30 @@
     print('%d : %s' % (pid, command))
 
 
+def get_service_dbus(pid):
+    bus = dbus.SystemBus()
+    systemd_manager_object = bus.get_object(
+        'org.freedesktop.systemd1',
+        '/org/freedesktop/systemd1'
+    )
+    systemd_manager_interface = dbus.Interface(
+        systemd_manager_object,
+        'org.freedesktop.systemd1.Manager'
+    )
+    service_proxy = bus.get_object(
+        'org.freedesktop.systemd1',
+        systemd_manager_interface.GetUnitByPID(pid)
+    )
+    service_properties = dbus.Interface(
+        service_proxy, dbus_interface="org.freedesktop.DBus.Properties")
+    name = service_properties.Get(
+        "org.freedesktop.systemd1.Unit",
+        'Id'
+    )
+    if name.endswith(".service"):
+        return name
+    return
+
 def smap2opened_file(pid, line):
     slash = line.find('/')
     if slash < 0:
@@ -174,11 +199,7 @@
 
     @staticmethod
     def get_boot_time():
-        with open('/proc/stat') as stat_file:
-            for line in stat_file.readlines():
-                if not line.startswith('btime '):
-                    continue
-                return int(line[len('btime '):].strip())
+        return int(os.stat('/proc/1/cmdline').st_mtime)
 
     @staticmethod
     def get_sc_clk_tck():
@@ -205,6 +226,8 @@
         parser.add_argument('-r', '--reboothint', action='store_true',
                             help=_("only report whether a reboot is required "
                                    "(exit code 1) or not (exit code 0)"))
+        parser.add_argument('-s', '--services', action='store_true',
+                            help=_("only report affected systemd services"))
 
     def configure(self):
         demands = self.cli.demands
@@ -251,5 +274,11 @@
             if pkg.installtime > process_start(ofile.pid):
                 stale_pids.add(ofile.pid)
 
+        if self.opts.services:
+            names = set([get_service_dbus(pid) for pid in sorted(stale_pids)])
+            for name in names:
+                if name is not None:
+                    print(name)
+            return 0
         for pid in sorted(stale_pids):
             print_cmd(pid)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/dnf-plugins-core-4.0.18/plugins/post-transaction-actions.py 
new/dnf-plugins-core-4.0.19/plugins/post-transaction-actions.py
--- old/dnf-plugins-core-4.0.18/plugins/post-transaction-actions.py     
2020-10-06 18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/plugins/post-transaction-actions.py     
2021-01-28 18:02:06.000000000 +0100
@@ -117,7 +117,7 @@
                 out_ts_items.append(ts_item)
             all_ts_items.append(ts_item)
 
-        commands_to_run = set()
+        commands_to_run = []
         for (a_key, a_state, a_command) in action_tuples:
             if a_state == "in":
                 ts_items = in_ts_items
@@ -141,7 +141,11 @@
                                 if tsi.pkg == pkg and tsi.pkg.reponame == 
pkg.reponame]
                 ts_item = ts_item_list[0]
                 command = self._replace_vars(ts_item, a_command)
-                commands_to_run.add(command)
+                commands_to_run.append(command)
+
+        # de-dup the list
+        seen = set()
+        commands_to_run = [x for x in commands_to_run if x not in seen and not 
seen.add(x)]
 
         for command in commands_to_run:
             try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/plugins/repomanage.py 
new/dnf-plugins-core-4.0.19/plugins/repomanage.py
--- old/dnf-plugins-core-4.0.18/plugins/repomanage.py   2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/plugins/repomanage.py   2021-01-28 
18:02:06.000000000 +0100
@@ -71,8 +71,11 @@
             raise dnf.exceptions.Error(_("No files to process"))
 
         try:
-            this_repo = self.base.repos.add_new_repo("repomanage_repo", 
self.base.conf, baseurl=[self.opts.path])
-            self.base._add_repo_to_sack(this_repo)
+            repo_conf = self.base.repos.add_new_repo("repomanage_repo", 
self.base.conf, baseurl=[self.opts.path])
+            # Always expire the repo, otherwise repomanage could use cached 
metadata and give identical results
+            # for multiple runs even if the actual repo changed in the meantime
+            repo_conf._repo.expire()
+            self.base._add_repo_to_sack(repo_conf)
             if dnf.base.WITH_MODULES:
                 self.base._setup_modular_excludes()
 
@@ -130,7 +133,7 @@
 
             # modular packages
             for streams_by_version in module_dict.values():
-                sorted_stream_versions = sorted(streams_by_version.keys(), 
reverse=True)
+                sorted_stream_versions = sorted(streams_by_version.keys())
 
                 new_sorted_stream_versions = sorted_stream_versions[-keepnum:]
 
@@ -153,7 +156,7 @@
 
             # modular packages
             for streams_by_version in module_dict.values():
-                sorted_stream_versions = sorted(streams_by_version.keys(), 
reverse=True)
+                sorted_stream_versions = sorted(streams_by_version.keys())
 
                 old_sorted_stream_versions = sorted_stream_versions[:-keepnum]
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/plugins/reposync.py 
new/dnf-plugins-core-4.0.19/plugins/reposync.py
--- old/dnf-plugins-core-4.0.18/plugins/reposync.py     2020-10-06 
18:54:52.000000000 +0200
+++ new/dnf-plugins-core-4.0.19/plugins/reposync.py     2021-01-28 
18:02:06.000000000 +0100
@@ -24,6 +24,7 @@
 import hawkey
 import os
 import shutil
+import types
 
 from dnfpluginscore import _, logger
 from dnf.cli.option_parser import OptionParser
@@ -63,24 +64,27 @@
                             help=_('download only packages for this ARCH'))
         parser.add_argument('--delete', default=False, action='store_true',
                             help=_('delete local packages no longer present in 
repository'))
-        parser.add_argument('-m', '--downloadcomps', default=False, 
action='store_true',
-                            help=_('also download and uncompress comps.xml'))
         parser.add_argument('--download-metadata', default=False, 
action='store_true',
                             help=_('download all the metadata.'))
+        parser.add_argument('-g', '--gpgcheck', default=False, 
action='store_true',
+                            help=_('Remove packages that fail GPG signature 
checking '
+                                   'after downloading'))
+        parser.add_argument('-m', '--downloadcomps', default=False, 
action='store_true',
+                            help=_('also download and uncompress comps.xml'))
+        parser.add_argument('--metadata-path',
+                            help=_('where to store downloaded repository 
metadata. '
+                                   'Defaults to the value of 
--download-path.'))
         parser.add_argument('-n', '--newest-only', default=False, 
action='store_true',
                             help=_('download only newest packages per-repo'))
-        parser.add_argument('-p', '--download-path', default='./',
-                            help=_('where to store downloaded repositories'))
         parser.add_argument('--norepopath', default=False, action='store_true',
                             help=_("Don't add the reponame to the download 
path."))
-        parser.add_argument('--metadata-path',
-                            help=_('where to store downloaded repository 
metadata. '
-                                   'Defaults to the value of 
--download-path.'))
-        parser.add_argument('--source', default=False, action='store_true',
-                            help=_('operate on source packages'))
+        parser.add_argument('-p', '--download-path', default='./',
+                            help=_('where to store downloaded repositories'))
         parser.add_argument('--remote-time', default=False, 
action='store_true',
                             help=_('try to set local timestamps of local files 
by '
                                    'the one on the server'))
+        parser.add_argument('--source', default=False, action='store_true',
+                            help=_('operate on source packages'))
         parser.add_argument('-u', '--urls', default=False, action='store_true',
                             help=_("Just list urls of what would be 
downloaded, "
                                    "don't download"))
@@ -114,6 +118,7 @@
 
     def run(self):
         self.base.conf.keepcache = True
+        gpgcheck_ok = True
         for repo in self.base.repos.iter_enabled():
             if self.opts.remote_time:
                 repo._repo.setPreserveRemoteTime(True)
@@ -150,8 +155,24 @@
                 self.print_urls(pkglist)
             else:
                 self.download_packages(pkglist)
+                if self.opts.gpgcheck:
+                    for pkg in pkglist:
+                        local_path = self.pkg_download_path(pkg)
+                        # base.package_signature_check uses pkg.localPkg() to 
determine
+                        # the location of the package rpm file on the disk.
+                        # Set it to the correct download path.
+                        pkg.localPkg  = types.MethodType(
+                            lambda s, local_path=local_path: local_path, pkg)
+                        result, error = self.base.package_signature_check(pkg)
+                        if result != 0:
+                            logger.warning(_("Removing {}: {}").format(
+                                os.path.basename(local_path), error))
+                            os.unlink(local_path)
+                            gpgcheck_ok = False
             if self.opts.delete:
                 self.delete_old_local_packages(repo, pkglist)
+        if not gpgcheck_ok:
+            raise dnf.exceptions.Error(_("GPG signature check failed."))
 
     def repo_target(self, repo):
         return _pkgdir(self.opts.destdir or self.opts.download_path,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dnf-plugins-core-4.0.18/tests/CMakeLists.txt 
new/dnf-plugins-core-4.0.19/tests/CMakeLists.txt
--- old/dnf-plugins-core-4.0.18/tests/CMakeLists.txt    1970-01-01 
01:00:00.000000000 +0100
+++ new/dnf-plugins-core-4.0.19/tests/CMakeLists.txt    2021-01-28 
18:02:06.000000000 +0100
@@ -0,0 +1,4 @@
+ADD_TEST(test ${PYTHON_EXECUTABLE} -m nose -s ${CMAKE_CURRENT_SOURCE_DIR})
+
+# ASAN_OPTIONS is for libdnf built with sanitizers, has no effect otherwise.
+SET_PROPERTY(TEST test PROPERTY ENVIRONMENT 
"PYTHONPATH=${CMAKE_SOURCE_DIR}/plugins;ASAN_OPTIONS=verify_asan_link_order=0")

Reply via email to