public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/rpkg] 1.70-1: A few patches:
@ 2026-08-10 21:44
0 siblings, 0 replies; 6+ messages in thread
From: @ 2026-08-10 21:44 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/rpkg
Branch : 1.70-1
Commit : e78ad805c297ae46eb38e788dba0c62509ac5ba8
Author : Ondřej Nosek <onosek@redhat.com>
Date : 2023-04-28T10:42:36+00:00
Stats : +237/-1 in 3 file(s)
URL : https://src.fedoraproject.org/rpms/rpkg/c/e78ad805c297ae46eb38e788dba0c62509ac5ba8?branch=1.70-1
Log:
A few patches:
- Patch: Do not require 'sources' file for all namespaces
- Use release's rpmdefines in unused sources check
Signed-off-by: Ondřej Nosek <onosek@redhat.com>
---
diff --git a/0020-Use-release-s-rpmdefines-in-unused-sources-check.patch b/0020-Use-release-s-rpmdefines-in-unused-sources-check.patch
new file mode 100644
index 0000000..6be7cee
--- /dev/null
+++ b/0020-Use-release-s-rpmdefines-in-unused-sources-check.patch
@@ -0,0 +1,170 @@
+From 8667d5379161183b306bdd4a6733c666cd2ef310 Mon Sep 17 00:00:00 2001
+From: Otto Liljalaakso <otto.liljalaakso@iki.fi>
+Date: Sun, 2 Apr 2023 17:21:00 +0300
+Subject: [PATCH 1/2] Use release's rpmdefines in unused sources check
+
+Conditional Source: tags are problematic and, in fact, forbidden in at
+least Fedora. However, there are packages that conditionalize packages
+based on macros such as %{rhel} or %{fedora}. 'x-pkg sources' did not
+handle such packages correctly, because when the specfile was parsed
+to check for unused sources, values for those macros were not set. This
+was different from other commands which set such macros based on the
+value of --release parameter or Git branch name.
+
+Improve support for conditional Source: tags by using the standard set
+of rpmdefines when the specfile is parsed in 'fedpkg sources'.
+
+Fixes: #671
+JIRA: RHELCMP-11465
+Merges: https://pagure.io/rpkg/pull-request/678
+
+Signed-off-by: Otto Liljalaakso <otto.liljalaakso@iki.fi>
+---
+ pyrpkg/__init__.py | 21 +++++++++++++++------
+ pyrpkg/spec.py | 12 +++++++-----
+ tests/test_cli.py | 21 ++++++++++++++++++++-
+ tests/test_spec.py | 8 ++++++--
+ 4 files changed, 48 insertions(+), 14 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index 3f934d3..817ef33 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -2261,13 +2261,22 @@ class Commands(object):
+ sourcesf = SourcesFile(self.sources_filename, self.source_entry_type)
+
+ try:
+- specf = SpecFile(os.path.join(self.layout.specdir, self.spec),
+- self.layout.sourcedir)
+- spec_parsed = True
+- except Exception:
+- self.log.warning("Parsing specfile for used sources failed. "
+- "Falling back to downloading all sources.")
++ # Try resolving rpmdefines separately. This produces a clear error
++ # message in the common failure case of custom branch name.
++ self.rpmdefines
++ except Exception as err:
++ self.log.warning("Parsing specfile for used sources failed: %s" % err)
++ self.log.warning("Falling back to downloading all sources.")
+ spec_parsed = False
++ else:
++ try:
++ specf = SpecFile(os.path.join(self.layout.specdir, self.spec),
++ self.rpmdefines)
++ spec_parsed = True
++ except Exception:
++ self.log.warning("Parsing specfile for used sources failed. "
++ "Falling back to downloading all sources.")
++ spec_parsed = False
+
+ args = dict()
+ if self.lookaside_request_params:
+diff --git a/pyrpkg/spec.py b/pyrpkg/spec.py
+index d72f1fb..5400de3 100644
+--- a/pyrpkg/spec.py
++++ b/pyrpkg/spec.py
+@@ -18,16 +18,16 @@ class SpecFile(object):
+ r'^((source[0-9]*|patch[0-9]*)\s*:\s*(?P<val>.*))\s*$',
+ re.IGNORECASE)
+
+- def __init__(self, spec, sourcedir):
++ def __init__(self, spec, rpmdefines):
+ self.spec = spec
+- self.sourcedir = sourcedir
++ self.rpmdefines = rpmdefines
+ self.sources = []
+
+ self.parse()
+
+ def parse(self):
+ """Call rpmspec and find source tags from the result."""
+- stdout = run(self.spec, self.sourcedir)
++ stdout = run(self.spec, self.rpmdefines)
+ for line in stdout.splitlines():
+ m = self.sourcefile_expression.match(line)
+ if not m:
+@@ -38,8 +38,10 @@ class SpecFile(object):
+ self.sources.append(val)
+
+
+-def run(spec, sourcedir):
+- cmdline = ['rpmspec', '--define', "_sourcedir %s" % sourcedir, '-P', spec]
++def run(spec, rpmdefines):
++ cmdline = ['rpmspec']
++ cmdline.extend(rpmdefines)
++ cmdline.extend(['-P', spec])
+ try:
+ process = subprocess.Popen(cmdline,
+ stdout=subprocess.PIPE,
+diff --git a/tests/test_cli.py b/tests/test_cli.py
+index 02620ef..58df047 100644
+--- a/tests/test_cli.py
++++ b/tests/test_cli.py
+@@ -1607,6 +1607,25 @@ class TestSources(LookasideCacheMock, CliTestCase):
+ def test_unused_sources_are_not_downloaded(self):
+ self._upload_unused()
+
++ cli_cmd = ['rpkg', '--path', self.cloned_repo_path, 'sources']
++ with patch('sys.argv', new=cli_cmd):
++ with patch('pyrpkg.Commands.rpmdefines',
++ new=['--define', '_sourcedir %s' % self.cloned_repo_path]):
++ cli = self.new_cli()
++ with patch('pyrpkg.lookaside.CGILookasideCache.download',
++ new=self.lookasidecache_download):
++ cli.sources()
++
++ path = os.path.join(self.cloned_repo_path, 'unused.patch')
++ self.assertFalse(os.path.exists(path))
++
++ @patch('pyrpkg.Commands.load_rpmdefines')
++ def test_download_sources_including_unused(self, rpmdefines):
++ self._upload_unused()
++ # SpecFile parsing executes 'rpmspec', that needs '--define' arguments from rpmdefines
++ # when rpmdefines raises eception, SpecFile parsing fails --> all sources are downloaded.
++ rpmdefines.side_effect = rpkgError
++
+ cli_cmd = ['rpkg', '--path', self.cloned_repo_path, 'sources']
+ with patch('sys.argv', new=cli_cmd):
+ cli = self.new_cli()
+@@ -1615,7 +1634,7 @@ class TestSources(LookasideCacheMock, CliTestCase):
+ cli.sources()
+
+ path = os.path.join(self.cloned_repo_path, 'unused.patch')
+- self.assertFalse(os.path.exists(path))
++ self.assertTrue(os.path.exists(path))
+
+ def test_force_option_downloads_unused_sources(self):
+ self._upload_unused()
+diff --git a/tests/test_spec.py b/tests/test_spec.py
+index eefc475..0c7907a 100644
+--- a/tests/test_spec.py
++++ b/tests/test_spec.py
+@@ -10,6 +10,10 @@ from pyrpkg.errors import rpkgError
+ class SpecFileTestCase(unittest.TestCase):
+ def setUp(self):
+ self.workdir = tempfile.mkdtemp(prefix='rpkg-tests.')
++ self.rpmdefines = ["--define", "_sourcedir %s" % self.workdir,
++ "--define", "_specdir %s" % self.workdir,
++ "--define", "_builddir %s" % self.workdir,
++ "--eval", "%%undefine rhel"]
+ self.specfile = os.path.join(self.workdir, self._testMethodName)
+
+ # Write common header
+@@ -43,7 +47,7 @@ class SpecFileTestCase(unittest.TestCase):
+ "PAtch999: https://remote.patch-sourcce.org/another-patch.bz2\n")
+ spec_fd.close()
+
+- s = spec.SpecFile(self.specfile, self.workdir)
++ s = spec.SpecFile(self.specfile, self.rpmdefines)
+ actual = s.sources
+ expected = [
+ "tarball.tar.gz",
+@@ -65,4 +69,4 @@ class SpecFileTestCase(unittest.TestCase):
+ self.assertRaises(rpkgError,
+ spec.SpecFile,
+ self.specfile,
+- self.workdir)
++ self.rpmdefines)
+--
+2.40.0
+
diff --git a/0021-Do-not-require-sources-file-for-all-namespaces.patch b/0021-Do-not-require-sources-file-for-all-namespaces.patch
new file mode 100644
index 0000000..a4c71aa
--- /dev/null
+++ b/0021-Do-not-require-sources-file-for-all-namespaces.patch
@@ -0,0 +1,60 @@
+From 079a64dde258f45e26fe35de86b1a0915f4973cd Mon Sep 17 00:00:00 2001
+From: Ondrej Nosek <onosek@redhat.com>
+Date: Thu, 27 Apr 2023 23:05:48 +0200
+Subject: [PATCH 2/2] Do not require 'sources' file for all namespaces
+
+Requirement for 'sources' file for all layouts except the RetiredLayout
+(and thus all namespaces) was too restrictive and unexpected.
+Partially reverts the commit 1108810bdefd0d880517b274acd6a3bd0d4156e0.
+
+Fixes: #684
+JIRA: RHELCMP-11529
+
+Signed-off-by: Ondrej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 2 --
+ pyrpkg/cli.py | 1 -
+ tests/test_cli.py | 2 +-
+ 3 files changed, 1 insertion(+), 4 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index 817ef33..11b8dae 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -1168,8 +1168,6 @@ class Commands(object):
+
+ @property
+ def sources_filename(self):
+- if self.layout is None or isinstance(self.layout, layout.IncompleteLayout):
+- raise rpkgError('Spec file is not available')
+ if isinstance(self.layout, layout.RetiredLayout):
+ raise rpkgError('This package or module is retired. The action has stopped.')
+ return os.path.join(
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index a1f3f44..dc1eb4e 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -2375,7 +2375,6 @@ class cliClient(object):
+
+ def import_srpm(self):
+ uploadfiles = self.cmd.import_srpm(self.args.srpm)
+- self.load_cmd() # to reload layouts - because a specfile could appear during import
+ if uploadfiles:
+ try:
+ self.cmd.upload(uploadfiles, replace=True, offline=self.args.offline)
+diff --git a/tests/test_cli.py b/tests/test_cli.py
+index 58df047..6e4ec6a 100644
+--- a/tests/test_cli.py
++++ b/tests/test_cli.py
+@@ -1610,7 +1610,7 @@ class TestSources(LookasideCacheMock, CliTestCase):
+ cli_cmd = ['rpkg', '--path', self.cloned_repo_path, 'sources']
+ with patch('sys.argv', new=cli_cmd):
+ with patch('pyrpkg.Commands.rpmdefines',
+- new=['--define', '_sourcedir %s' % self.cloned_repo_path]):
++ new=['--define', '_sourcedir %s' % self.cloned_repo_path]):
+ cli = self.new_cli()
+ with patch('pyrpkg.lookaside.CGILookasideCache.download',
+ new=self.lookasidecache_download):
+--
+2.40.0
+
diff --git a/rpkg.spec b/rpkg.spec
index 60d99d1..17e44ee 100644
--- a/rpkg.spec
+++ b/rpkg.spec
@@ -1,6 +1,6 @@
Name: rpkg
Version: 1.66
-Release: 6%{?dist}
+Release: 7%{?dist}
Summary: Python library for interacting with rpm+git
License: GPLv2+ and LGPLv2
@@ -53,6 +53,8 @@ Patch16: 0016-Check-remote-file-with-correct-hash.patch
Patch17: 0017-Allow-empty-commits-when-uses_rpmautospec.patch
Patch18: 0018-Config-file-option-to-skip-the-hook-script-creation.patch
Patch19: 0019-Pre-push-hook-won-t-check-private-branches.patch
+Patch20: 0020-Use-release-s-rpmdefines-in-unused-sources-check.patch
+Patch21: 0021-Do-not-require-sources-file-for-all-namespaces.patch
%description
Python library for interacting with rpm+git
@@ -269,6 +271,10 @@ example_cli_dir=$RPM_BUILD_ROOT%{_datadir}/%{name}/examples/cli
%changelog
+* Fri Apr 28 2023 Ondřej Nosek <onosek@redhat.com> - 1.66-7
+- Patch: Do not require 'sources' file for all namespaces
+- Use release's rpmdefines in unused sources check
+
* Tue Apr 18 2023 Ondřej Nosek <onosek@redhat.com> - 1.66-6
- Patch: Pre-push hook won't check private branches
- Patch: Config file option to skip the hook script creation
^ permalink raw reply related [flat|nested] 6+ messages in thread* [rpms/rpkg] 1.70-1: A few patches:
@ 2026-08-10 21:44
0 siblings, 0 replies; 6+ messages in thread
From: @ 2026-08-10 21:44 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/rpkg
Branch : 1.70-1
Commit : d35e81155f584afe442b3bed369915257a4f485b
Author : Ondřej Nosek <onosek@redhat.com>
Date : 2023-08-20T20:58:32+00:00
Stats : +111/-1 in 3 file(s)
URL : https://src.fedoraproject.org/rpms/rpkg/c/d35e81155f584afe442b3bed369915257a4f485b?branch=1.70-1
Log:
A few patches:
- Patch: Support for checking exploded sources before push
- Patch: Split git credential data on first = only
Signed-off-by: Ondřej Nosek <onosek@redhat.com>
---
diff --git a/0023-Split-git-credential-data-on-first-only.patch b/0023-Split-git-credential-data-on-first-only.patch
new file mode 100644
index 0000000..43416b7
--- /dev/null
+++ b/0023-Split-git-credential-data-on-first-only.patch
@@ -0,0 +1,33 @@
+From 75d42bad79b54654fca9770a2857e79e82d83c3e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Lubom=C3=ADr=20Sedl=C3=A1=C5=99?= <lsedlar@redhat.com>
+Date: Wed, 21 Jun 2023 10:42:45 +0200
+Subject: [PATCH 1/3] Split git credential data on first = only
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The value itself can contain a = character, but we don't really care
+about that. We can treat the value of the key as opaque.
+
+Fixes: https://pagure.io/rpkg/issue/694
+Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
+---
+ pyrpkg/cli.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index 020a247..1bd7979 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -824,7 +824,7 @@ class cliClient(object):
+ """Parses the git-credential-helper IO format input."""
+ inp = {}
+ for line in sys.stdin:
+- vals = line.split('=', 2)
++ vals = line.split('=', 1)
+ if len(vals) != 2:
+ print('Invalid input: %s' % line, file=sys.stderr)
+ return False
+--
+2.41.0
+
diff --git a/0024-Support-for-checking-exploded-sources-before-push.patch b/0024-Support-for-checking-exploded-sources-before-push.patch
new file mode 100644
index 0000000..e8f14de
--- /dev/null
+++ b/0024-Support-for-checking-exploded-sources-before-push.patch
@@ -0,0 +1,71 @@
+From 87d4995b40fbcddac88fb21191eb2d5d1f248550 Mon Sep 17 00:00:00 2001
+From: Ondrej Nosek <onosek@redhat.com>
+Date: Tue, 11 Jul 2023 17:00:48 +0200
+Subject: [PATCH 2/3] Support for checking exploded sources before push
+
+pre-push-check now includes test whether source files listed
+in a specfile come from additional sources.
+This functionality is relevant only for some x-pkg tools, others
+should not be affected.
+
+JIRA: RHELCMP-11777
+
+Signed-off-by: Ondrej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 12 ++++++++++--
+ tests/commands/test_pre_push_check.py | 4 ++--
+ 2 files changed, 12 insertions(+), 4 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index b45ad8f..bc669b9 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -4468,6 +4468,10 @@ class Commands(object):
+
+ return self._repo_name, version, release
+
++ # Works as virtual method. Other x-pkg tools can add their specific sources
++ def additional_source_entries(self):
++ return {}
++
+ def pre_push_check(self, ref):
+ show_hint = ('Hint: this check (.git/hooks/pre-push script) can be bypassed by adding '
+ 'the argument \'--no-verify\' argument to the push command.')
+@@ -4561,13 +4565,17 @@ class Commands(object):
+ # list of all files (their relative paths) in the commit
+ repo_entries = set(item.path for item in commit.tree.traverse() if item.type != "tree")
+
++ # other x-pkg tools can add their specific sources
++ additional_entries = set(self.additional_source_entries())
++
+ # check whether every source file is either listed in the 'sources' file or tracked in git
+ for source_file in source_files:
+ listed = source_file in sourcesf_entries
+ tracked = source_file in repo_entries
+- if not (listed or tracked):
++ listed_additional = source_file in additional_entries
++ if not (listed or tracked or listed_additional):
+ self.log.error('Source file \'{0}\' was neither listed in the \'sources\' file '
+- 'nor tracked in git. '
++ 'nor tracked in git nor listed in additional sources. '
+ 'Push operation was cancelled'.format(source_file))
+ self.log.warning(show_hint)
+ sys.exit(4)
+diff --git a/tests/commands/test_pre_push_check.py b/tests/commands/test_pre_push_check.py
+index ee151c1..79165ec 100644
+--- a/tests/commands/test_pre_push_check.py
++++ b/tests/commands/test_pre_push_check.py
+@@ -90,8 +90,8 @@ Patch3: d.patch
+
+ self.assertEqual(exc.exception.code, 4)
+ log_error.assert_called_once_with("Source file 'b.patch' was neither listed in the "
+- "'sources' file nor tracked in git. Push operation "
+- "was cancelled")
++ "'sources' file nor tracked in git nor listed "
++ "in additional sources. Push operation was cancelled")
+
+ # Verify added files are committed but not pushed to origin
+ local_repo = git.Repo(self.cloned_dir)
+--
+2.41.0
+
diff --git a/rpkg.spec b/rpkg.spec
index 8d1cb8f..346816d 100644
--- a/rpkg.spec
+++ b/rpkg.spec
@@ -1,6 +1,6 @@
Name: rpkg
Version: 1.66
-Release: 10%{?dist}
+Release: 11%{?dist}
Summary: Python library for interacting with rpm+git
License: GPLv2+ and LGPLv2
@@ -56,6 +56,8 @@ Patch19: 0019-Pre-push-hook-won-t-check-private-branches.patch
Patch20: 0020-Use-release-s-rpmdefines-in-unused-sources-check.patch
Patch21: 0021-Do-not-require-sources-file-for-all-namespaces.patch
Patch22: 0022-commit-command-fails-on-containers-namespace.patch
+Patch23: 0023-Split-git-credential-data-on-first-only.patch
+Patch24: 0024-Support-for-checking-exploded-sources-before-push.patch
%description
Python library for interacting with rpm+git
@@ -272,6 +274,10 @@ example_cli_dir=$RPM_BUILD_ROOT%{_datadir}/%{name}/examples/cli
%changelog
+* Sun Aug 20 2023 Ondřej Nosek <onosek@redhat.com> - 1.66-11
+- Patch: Support for checking exploded sources before push
+- Patch: Split git credential data on first = only
+
* Fri Jul 21 2023 Fedora Release Engineering <releng@fedoraproject.org> - 1.66-10
- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild
^ permalink raw reply related [flat|nested] 6+ messages in thread* [rpms/rpkg] 1.70-1: A few patches:
@ 2026-08-10 21:44
0 siblings, 0 replies; 6+ messages in thread
From: @ 2026-08-10 21:44 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/rpkg
Branch : 1.70-1
Commit : b48b2893740995636fe9e2496130e5bb5f4ab749
Author : Ondřej Nosek <onosek@redhat.com>
Date : 2024-01-10T03:03:43+00:00
Stats : +1560/-1197 in 25 file(s)
URL : https://src.fedoraproject.org/rpms/rpkg/c/b48b2893740995636fe9e2496130e5bb5f4ab749?branch=1.70-1
Log:
A few patches:
- Patch: Add option to mockbuild use default resultdir of mock (v3)
- Patch: mockbuild`: new argument --extra-pkgs
- Patch: `copr-build` passes extra_args to copr-cli command
Signed-off-by: Ondřej Nosek <onosek@redhat.com>
---
diff --git a/0022-commit-command-fails-on-containers-namespace.patch b/0022-commit-command-fails-on-containers-namespace.patch
deleted file mode 100644
index a284abc..0000000
--- a/0022-commit-command-fails-on-containers-namespace.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-From 7ade8c1f38efaa8817bd10df6b0928ef70822f6e Mon Sep 17 00:00:00 2001
-From: Ondrej Nosek <onosek@redhat.com>
-Date: Wed, 17 May 2023 00:32:47 +0200
-Subject: [PATCH] `commit` command fails on 'containers' namespace
-
-Commit failed when 'uses_rpmautospec' tried to search for a specfile.
-There is no specfile in 'containers' namespace repository.
-
-JIRA: RHELCMP-11734
-
-Signed-off-by: Ondrej Nosek <onosek@redhat.com>
----
- pyrpkg/__init__.py | 11 +++++++++--
- 1 file changed, 9 insertions(+), 2 deletions(-)
-
-diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
-index f14b055..b45ad8f 100644
---- a/pyrpkg/__init__.py
-+++ b/pyrpkg/__init__.py
-@@ -1872,8 +1872,15 @@ class Commands(object):
- # construct the git command
- # We do this via subprocess because the git module is terrible.
- cmd = ['git', 'commit']
-- if not self.is_retired() and self.uses_rpmautospec:
-- cmd.append('--allow-empty')
-+ if not self.is_retired():
-+ try:
-+ # raises exception when a specfile is missing
-+ # (for example when processing "containers" namespace repository)
-+ uses_rpmautospec = self.uses_rpmautospec
-+ except Exception:
-+ uses_rpmautospec = False
-+ if uses_rpmautospec:
-+ cmd.append('--allow-empty')
- if signoff:
- cmd.append('-s')
- if self.quiet:
---
-2.40.1
-
diff --git a/0022-copr-build-passes-extra_args-to-copr-cli-command.patch b/0022-copr-build-passes-extra_args-to-copr-cli-command.patch
new file mode 100644
index 0000000..966f0ef
--- /dev/null
+++ b/0022-copr-build-passes-extra_args-to-copr-cli-command.patch
@@ -0,0 +1,113 @@
+From ad67fa9069befef3e4ba5180eea6bf56b658e664 Mon Sep 17 00:00:00 2001
+From: Ondrej Nosek <onosek@redhat.com>
+Date: Wed, 3 May 2023 00:41:34 +0200
+Subject: [PATCH 01/17] `copr-build` passes extra_args to copr-cli command
+
+The right target for passing extra_args (arguments that are placed
+after '--' on the command line) is the command copr-cli instead
+of rpmbuild command.
+
+Fixes: https://pagure.io/fedpkg/issue/510
+JIRA: RHELCMP-11429
+
+Signed-off-by: Ondrej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 5 ++++-
+ pyrpkg/cli.py | 11 ++++++++++-
+ tests/test_cli.py | 24 ++++++++++++++++++++++++
+ 3 files changed, 38 insertions(+), 2 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index 11b8dae..f14b055 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -3576,13 +3576,16 @@ class Commands(object):
+ else:
+ self.log.info('Nothing to be done')
+
+- def copr_build(self, project, srpm_name, nowait, config_file):
++ def copr_build(self, project, srpm_name, nowait, config_file, extra_args=None):
+ cmd = ['copr-cli']
+ if config_file:
+ cmd.extend(['--config', config_file])
+ cmd.append('build')
+ if nowait:
+ cmd.append('--nowait')
++ if extra_args:
++ cmd.extend(extra_args)
++ self.log.debug("Extra args '{0}' are passed to copr-cli command".format(extra_args))
+ cmd.extend([project, srpm_name])
+ self._run_command(cmd)
+
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index dc1eb4e..020a247 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -1561,6 +1561,10 @@ class cliClient(object):
+ help="Don't wait on build")
+ copr_parser.add_argument(
+ 'project', nargs=1, help='Name of the project in format USER/PROJECT')
++ copr_parser.add_argument(
++ "extra_args", default=None, nargs=argparse.REMAINDER,
++ help="Custom arguments that are passed to the 'copr-cli'. "
++ "Use '--' to separate them from other arguments.")
+ copr_parser.set_defaults(command=self.copr_build)
+
+ def register_switch_branch(self):
+@@ -2354,12 +2358,17 @@ class cliClient(object):
+ def copr_build(self):
+ self.log.debug('Generating an srpm')
+ self.args.hash = None
++ # do not pass 'extra_args' to 'rpmbuild' command in 'srpm' method; Pass it to copr-cli.
++ extra_args_backup = self.extra_args
++ self.extra_args = None
+ self.srpm()
++ self.extra_args = extra_args_backup
+ srpm_name = '%s.src.rpm' % self.cmd.nvr
+ self.cmd.copr_build(self.args.project[0],
+ srpm_name,
+ self.args.nowait,
+- self.args.copr_config)
++ self.args.copr_config,
++ extra_args=self.extra_args)
+
+ def diff(self):
+ self.cmd.diff(self.args.cached, self.args.files)
+diff --git a/tests/test_cli.py b/tests/test_cli.py
+index 6e4ec6a..f2e68df 100644
+--- a/tests/test_cli.py
++++ b/tests/test_cli.py
+@@ -636,6 +636,30 @@ class TestClone(CliTestCase):
+ output = sys.stderr.getvalue().strip()
+ self.assertEqual('', output)
+
++ @patch('sys.stderr', new=StringIO())
++ @patch('pyrpkg.Commands._clone_config', new_callable=Mock())
++ @patch('pyrpkg.Commands._run_command')
++ def test_extra_args_copr(self, _run_command, _clone_config):
++ # copr-build is the command that has two subcommands (rpmbuild and copr-cli)
++ # that might accept the extra args. This tests requies extra_args at copr-cli.
++ cli_cmd = ['rpkg', '--user', 'dude', '--path', self.cloned_repo_path,
++ '--release', 'rhel-6', 'copr-build', 'COPR-REPO',
++ '--', '--after-build-id', 'ID']
++
++ with patch('sys.argv', new=cli_cmd):
++ cli = self.new_cli()
++ cli.copr_build()
++
++ expected_cmd = ['copr-cli', 'build', '--after-build-id', 'ID', 'COPR-REPO']
++ self.assertEqual(2, _run_command.call_count)
++ copr_cli_call = _run_command.mock_calls[1]
++ if 'args' in dir(copr_cli_call): # doesn't work in <=py36
++ # strip the last argument - it contains dynamically generated src.rpm filename
++ self.assertEqual(expected_cmd, copr_cli_call.args[0][:-1])
++
++ output = sys.stderr.getvalue().strip()
++ self.assertEqual('', output)
++
+ @patch('sys.stderr', new=StringIO())
+ @patch('pyrpkg.Commands._clone_config', new_callable=Mock())
+ @patch('pyrpkg.Commands._run_command')
+--
+2.43.0
+
diff --git a/0023-Split-git-credential-data-on-first-only.patch b/0023-Split-git-credential-data-on-first-only.patch
deleted file mode 100644
index 43416b7..0000000
--- a/0023-Split-git-credential-data-on-first-only.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-From 75d42bad79b54654fca9770a2857e79e82d83c3e Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Lubom=C3=ADr=20Sedl=C3=A1=C5=99?= <lsedlar@redhat.com>
-Date: Wed, 21 Jun 2023 10:42:45 +0200
-Subject: [PATCH 1/3] Split git credential data on first = only
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-The value itself can contain a = character, but we don't really care
-about that. We can treat the value of the key as opaque.
-
-Fixes: https://pagure.io/rpkg/issue/694
-Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
----
- pyrpkg/cli.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
-index 020a247..1bd7979 100644
---- a/pyrpkg/cli.py
-+++ b/pyrpkg/cli.py
-@@ -824,7 +824,7 @@ class cliClient(object):
- """Parses the git-credential-helper IO format input."""
- inp = {}
- for line in sys.stdin:
-- vals = line.split('=', 2)
-+ vals = line.split('=', 1)
- if len(vals) != 2:
- print('Invalid input: %s' % line, file=sys.stderr)
- return False
---
-2.41.0
-
diff --git a/0023-commit-command-fails-on-containers-namespace.patch b/0023-commit-command-fails-on-containers-namespace.patch
new file mode 100644
index 0000000..21ae8e3
--- /dev/null
+++ b/0023-commit-command-fails-on-containers-namespace.patch
@@ -0,0 +1,40 @@
+From 7ade8c1f38efaa8817bd10df6b0928ef70822f6e Mon Sep 17 00:00:00 2001
+From: Ondrej Nosek <onosek@redhat.com>
+Date: Wed, 17 May 2023 00:32:47 +0200
+Subject: [PATCH 02/17] `commit` command fails on 'containers' namespace
+
+Commit failed when 'uses_rpmautospec' tried to search for a specfile.
+There is no specfile in 'containers' namespace repository.
+
+JIRA: RHELCMP-11734
+
+Signed-off-by: Ondrej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index f14b055..b45ad8f 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -1872,8 +1872,15 @@ class Commands(object):
+ # construct the git command
+ # We do this via subprocess because the git module is terrible.
+ cmd = ['git', 'commit']
+- if not self.is_retired() and self.uses_rpmautospec:
+- cmd.append('--allow-empty')
++ if not self.is_retired():
++ try:
++ # raises exception when a specfile is missing
++ # (for example when processing "containers" namespace repository)
++ uses_rpmautospec = self.uses_rpmautospec
++ except Exception:
++ uses_rpmautospec = False
++ if uses_rpmautospec:
++ cmd.append('--allow-empty')
+ if signoff:
+ cmd.append('-s')
+ if self.quiet:
+--
+2.43.0
+
diff --git a/0024-Split-git-credential-data-on-first-only.patch b/0024-Split-git-credential-data-on-first-only.patch
new file mode 100644
index 0000000..8f7ab6b
--- /dev/null
+++ b/0024-Split-git-credential-data-on-first-only.patch
@@ -0,0 +1,33 @@
+From 75d42bad79b54654fca9770a2857e79e82d83c3e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Lubom=C3=ADr=20Sedl=C3=A1=C5=99?= <lsedlar@redhat.com>
+Date: Wed, 21 Jun 2023 10:42:45 +0200
+Subject: [PATCH 03/17] Split git credential data on first = only
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The value itself can contain a = character, but we don't really care
+about that. We can treat the value of the key as opaque.
+
+Fixes: https://pagure.io/rpkg/issue/694
+Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
+---
+ pyrpkg/cli.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index 020a247..1bd7979 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -824,7 +824,7 @@ class cliClient(object):
+ """Parses the git-credential-helper IO format input."""
+ inp = {}
+ for line in sys.stdin:
+- vals = line.split('=', 2)
++ vals = line.split('=', 1)
+ if len(vals) != 2:
+ print('Invalid input: %s' % line, file=sys.stderr)
+ return False
+--
+2.43.0
+
diff --git a/0024-Support-for-checking-exploded-sources-before-push.patch b/0024-Support-for-checking-exploded-sources-before-push.patch
deleted file mode 100644
index e8f14de..0000000
--- a/0024-Support-for-checking-exploded-sources-before-push.patch
+++ /dev/null
@@ -1,71 +0,0 @@
-From 87d4995b40fbcddac88fb21191eb2d5d1f248550 Mon Sep 17 00:00:00 2001
-From: Ondrej Nosek <onosek@redhat.com>
-Date: Tue, 11 Jul 2023 17:00:48 +0200
-Subject: [PATCH 2/3] Support for checking exploded sources before push
-
-pre-push-check now includes test whether source files listed
-in a specfile come from additional sources.
-This functionality is relevant only for some x-pkg tools, others
-should not be affected.
-
-JIRA: RHELCMP-11777
-
-Signed-off-by: Ondrej Nosek <onosek@redhat.com>
----
- pyrpkg/__init__.py | 12 ++++++++++--
- tests/commands/test_pre_push_check.py | 4 ++--
- 2 files changed, 12 insertions(+), 4 deletions(-)
-
-diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
-index b45ad8f..bc669b9 100644
---- a/pyrpkg/__init__.py
-+++ b/pyrpkg/__init__.py
-@@ -4468,6 +4468,10 @@ class Commands(object):
-
- return self._repo_name, version, release
-
-+ # Works as virtual method. Other x-pkg tools can add their specific sources
-+ def additional_source_entries(self):
-+ return {}
-+
- def pre_push_check(self, ref):
- show_hint = ('Hint: this check (.git/hooks/pre-push script) can be bypassed by adding '
- 'the argument \'--no-verify\' argument to the push command.')
-@@ -4561,13 +4565,17 @@ class Commands(object):
- # list of all files (their relative paths) in the commit
- repo_entries = set(item.path for item in commit.tree.traverse() if item.type != "tree")
-
-+ # other x-pkg tools can add their specific sources
-+ additional_entries = set(self.additional_source_entries())
-+
- # check whether every source file is either listed in the 'sources' file or tracked in git
- for source_file in source_files:
- listed = source_file in sourcesf_entries
- tracked = source_file in repo_entries
-- if not (listed or tracked):
-+ listed_additional = source_file in additional_entries
-+ if not (listed or tracked or listed_additional):
- self.log.error('Source file \'{0}\' was neither listed in the \'sources\' file '
-- 'nor tracked in git. '
-+ 'nor tracked in git nor listed in additional sources. '
- 'Push operation was cancelled'.format(source_file))
- self.log.warning(show_hint)
- sys.exit(4)
-diff --git a/tests/commands/test_pre_push_check.py b/tests/commands/test_pre_push_check.py
-index ee151c1..79165ec 100644
---- a/tests/commands/test_pre_push_check.py
-+++ b/tests/commands/test_pre_push_check.py
-@@ -90,8 +90,8 @@ Patch3: d.patch
-
- self.assertEqual(exc.exception.code, 4)
- log_error.assert_called_once_with("Source file 'b.patch' was neither listed in the "
-- "'sources' file nor tracked in git. Push operation "
-- "was cancelled")
-+ "'sources' file nor tracked in git nor listed "
-+ "in additional sources. Push operation was cancelled")
-
- # Verify added files are committed but not pushed to origin
- local_repo = git.Repo(self.cloned_dir)
---
-2.41.0
-
diff --git a/0025-Fix-flake8-complaints.patch b/0025-Fix-flake8-complaints.patch
deleted file mode 100644
index 0e4e657..0000000
--- a/0025-Fix-flake8-complaints.patch
+++ /dev/null
@@ -1,42 +0,0 @@
-From 5c915a549ad2d10a3eb36c56801574d81a601670 Mon Sep 17 00:00:00 2001
-From: Ondrej Nosek <onosek@redhat.com>
-Date: Tue, 1 Aug 2023 23:26:43 +0200
-Subject: [PATCH 1/4] Fix flake8 complaints
-
-E721 do not compare types, for exact checks use `is` / `is not`, for
-instance checks use `isinstance()`
-Conditions in the method `_list_branches` were switched because:
-`issubclass(git.RemoteReference, git.Head)`
-
-Signed-off-by: Ondrej Nosek <onosek@redhat.com>
----
- pyrpkg/__init__.py | 8 ++++----
- 1 file changed, 4 insertions(+), 4 deletions(-)
-
-diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
-index bc669b9..f69f2ce 100644
---- a/pyrpkg/__init__.py
-+++ b/pyrpkg/__init__.py
-@@ -1411,15 +1411,15 @@ class Commands(object):
- remotes = []
- locals = []
- for ref in refs:
-- if type(ref) == git.Head:
-- self.log.debug('Found local branch %s', ref.name)
-- locals.append(ref.name)
-- elif type(ref) == git.RemoteReference:
-+ if isinstance(ref, git.RemoteReference):
- if ref.remote_head == 'HEAD':
- self.log.debug('Skipping remote branch alias HEAD')
- continue # Not useful in this context
- self.log.debug('Found remote branch %s', ref.name)
- remotes.append(ref.name)
-+ elif isinstance(ref, git.Head):
-+ self.log.debug('Found local branch %s', ref.name)
-+ locals.append(ref.name)
- return (locals, remotes)
-
- def _srpmdetails(self, srpm):
---
-2.41.0
-
diff --git a/0025-Support-for-checking-exploded-sources-before-push.patch b/0025-Support-for-checking-exploded-sources-before-push.patch
new file mode 100644
index 0000000..cd1e34a
--- /dev/null
+++ b/0025-Support-for-checking-exploded-sources-before-push.patch
@@ -0,0 +1,71 @@
+From 87d4995b40fbcddac88fb21191eb2d5d1f248550 Mon Sep 17 00:00:00 2001
+From: Ondrej Nosek <onosek@redhat.com>
+Date: Tue, 11 Jul 2023 17:00:48 +0200
+Subject: [PATCH 04/17] Support for checking exploded sources before push
+
+pre-push-check now includes test whether source files listed
+in a specfile come from additional sources.
+This functionality is relevant only for some x-pkg tools, others
+should not be affected.
+
+JIRA: RHELCMP-11777
+
+Signed-off-by: Ondrej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 12 ++++++++++--
+ tests/commands/test_pre_push_check.py | 4 ++--
+ 2 files changed, 12 insertions(+), 4 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index b45ad8f..bc669b9 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -4468,6 +4468,10 @@ class Commands(object):
+
+ return self._repo_name, version, release
+
++ # Works as virtual method. Other x-pkg tools can add their specific sources
++ def additional_source_entries(self):
++ return {}
++
+ def pre_push_check(self, ref):
+ show_hint = ('Hint: this check (.git/hooks/pre-push script) can be bypassed by adding '
+ 'the argument \'--no-verify\' argument to the push command.')
+@@ -4561,13 +4565,17 @@ class Commands(object):
+ # list of all files (their relative paths) in the commit
+ repo_entries = set(item.path for item in commit.tree.traverse() if item.type != "tree")
+
++ # other x-pkg tools can add their specific sources
++ additional_entries = set(self.additional_source_entries())
++
+ # check whether every source file is either listed in the 'sources' file or tracked in git
+ for source_file in source_files:
+ listed = source_file in sourcesf_entries
+ tracked = source_file in repo_entries
+- if not (listed or tracked):
++ listed_additional = source_file in additional_entries
++ if not (listed or tracked or listed_additional):
+ self.log.error('Source file \'{0}\' was neither listed in the \'sources\' file '
+- 'nor tracked in git. '
++ 'nor tracked in git nor listed in additional sources. '
+ 'Push operation was cancelled'.format(source_file))
+ self.log.warning(show_hint)
+ sys.exit(4)
+diff --git a/tests/commands/test_pre_push_check.py b/tests/commands/test_pre_push_check.py
+index ee151c1..79165ec 100644
+--- a/tests/commands/test_pre_push_check.py
++++ b/tests/commands/test_pre_push_check.py
+@@ -90,8 +90,8 @@ Patch3: d.patch
+
+ self.assertEqual(exc.exception.code, 4)
+ log_error.assert_called_once_with("Source file 'b.patch' was neither listed in the "
+- "'sources' file nor tracked in git. Push operation "
+- "was cancelled")
++ "'sources' file nor tracked in git nor listed "
++ "in additional sources. Push operation was cancelled")
+
+ # Verify added files are committed but not pushed to origin
+ local_repo = git.Repo(self.cloned_dir)
+--
+2.43.0
+
diff --git a/0026-Fix-flake8-complaints.patch b/0026-Fix-flake8-complaints.patch
new file mode 100644
index 0000000..4955a49
--- /dev/null
+++ b/0026-Fix-flake8-complaints.patch
@@ -0,0 +1,42 @@
+From 5c915a549ad2d10a3eb36c56801574d81a601670 Mon Sep 17 00:00:00 2001
+From: Ondrej Nosek <onosek@redhat.com>
+Date: Tue, 1 Aug 2023 23:26:43 +0200
+Subject: [PATCH 05/17] Fix flake8 complaints
+
+E721 do not compare types, for exact checks use `is` / `is not`, for
+instance checks use `isinstance()`
+Conditions in the method `_list_branches` were switched because:
+`issubclass(git.RemoteReference, git.Head)`
+
+Signed-off-by: Ondrej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index bc669b9..f69f2ce 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -1411,15 +1411,15 @@ class Commands(object):
+ remotes = []
+ locals = []
+ for ref in refs:
+- if type(ref) == git.Head:
+- self.log.debug('Found local branch %s', ref.name)
+- locals.append(ref.name)
+- elif type(ref) == git.RemoteReference:
++ if isinstance(ref, git.RemoteReference):
+ if ref.remote_head == 'HEAD':
+ self.log.debug('Skipping remote branch alias HEAD')
+ continue # Not useful in this context
+ self.log.debug('Found remote branch %s', ref.name)
+ remotes.append(ref.name)
++ elif isinstance(ref, git.Head):
++ self.log.debug('Found local branch %s', ref.name)
++ locals.append(ref.name)
+ return (locals, remotes)
+
+ def _srpmdetails(self, srpm):
+--
+2.43.0
+
diff --git a/0026-Prepare-the-lookaside-cache-code-for-retries.patch b/0026-Prepare-the-lookaside-cache-code-for-retries.patch
deleted file mode 100644
index 733a980..0000000
--- a/0026-Prepare-the-lookaside-cache-code-for-retries.patch
+++ /dev/null
@@ -1,148 +0,0 @@
-From 1a0601d29794cec1f735a10208364d11958c41ec Mon Sep 17 00:00:00 2001
-From: Ondrej Nosek <onosek@redhat.com>
-Date: Wed, 26 Jul 2023 01:30:12 +0200
-Subject: [PATCH 2/4] Prepare the lookaside cache code for retries
-
-These changes should not have an impact on the original functionality.
-
-JIRA: RHELCMP-11210
-
-Signed-off-by: Ondrej Nosek <onosek@redhat.com>
----
- pyrpkg/lookaside.py | 96 ++++++++++++++++++++++-----------------------
- 1 file changed, 48 insertions(+), 48 deletions(-)
-
-diff --git a/pyrpkg/lookaside.py b/pyrpkg/lookaside.py
-index 3efcd88..f94ffdb 100644
---- a/pyrpkg/lookaside.py
-+++ b/pyrpkg/lookaside.py
-@@ -163,17 +163,17 @@ class CGILookasideCache(object):
- url = url.encode('utf-8')
- self.log.debug("Full url: %s", url)
-
-+ c = pycurl.Curl()
-+ c.setopt(pycurl.URL, url)
-+ c.setopt(pycurl.HTTPHEADER, ['Pragma:'])
-+ c.setopt(pycurl.NOPROGRESS, False)
-+ c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
-+ c.setopt(pycurl.OPT_FILETIME, True)
-+ c.setopt(pycurl.LOW_SPEED_LIMIT, 1000)
-+ c.setopt(pycurl.LOW_SPEED_TIME, 300)
-+ c.setopt(pycurl.FOLLOWLOCATION, 1)
- with open(outfile, 'wb') as f:
-- c = pycurl.Curl()
-- c.setopt(pycurl.URL, url)
-- c.setopt(pycurl.HTTPHEADER, ['Pragma:'])
-- c.setopt(pycurl.NOPROGRESS, False)
-- c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
-- c.setopt(pycurl.OPT_FILETIME, True)
- c.setopt(pycurl.WRITEDATA, f)
-- c.setopt(pycurl.LOW_SPEED_LIMIT, 1000)
-- c.setopt(pycurl.LOW_SPEED_TIME, 300)
-- c.setopt(pycurl.FOLLOWLOCATION, 1)
- try:
- c.perform()
- tstamp = c.getinfo(pycurl.INFO_FILETIME)
-@@ -254,29 +254,29 @@ class CGILookasideCache(object):
- ('%ssum' % self.hashtype, hash),
- ('filename', filename)]
-
-- with io.BytesIO() as buf:
-- c = pycurl.Curl()
-- c.setopt(pycurl.URL, self.upload_url)
-- c.setopt(pycurl.WRITEFUNCTION, buf.write)
-- c.setopt(pycurl.HTTPPOST, post_data)
-- c.setopt(pycurl.FOLLOWLOCATION, 1)
-+ c = pycurl.Curl()
-+ c.setopt(pycurl.URL, self.upload_url)
-+ c.setopt(pycurl.HTTPPOST, post_data)
-+ c.setopt(pycurl.FOLLOWLOCATION, 1)
-
-- if self.client_cert is not None:
-- if os.path.exists(self.client_cert):
-- c.setopt(pycurl.SSLCERT, self.client_cert)
-- else:
-- self.log.warning("Missing certificate: %s"
-- % self.client_cert)
-+ if self.client_cert is not None:
-+ if os.path.exists(self.client_cert):
-+ c.setopt(pycurl.SSLCERT, self.client_cert)
-+ else:
-+ self.log.warning("Missing certificate: %s"
-+ % self.client_cert)
-
-- if self.ca_cert is not None:
-- if os.path.exists(self.ca_cert):
-- c.setopt(pycurl.CAINFO, self.ca_cert)
-- else:
-- self.log.warning("Missing certificate: %s", self.ca_cert)
-+ if self.ca_cert is not None:
-+ if os.path.exists(self.ca_cert):
-+ c.setopt(pycurl.CAINFO, self.ca_cert)
-+ else:
-+ self.log.warning("Missing certificate: %s", self.ca_cert)
-
-- c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
-- c.setopt(pycurl.USERPWD, ':')
-+ c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
-+ c.setopt(pycurl.USERPWD, ':')
-
-+ with io.BytesIO() as buf:
-+ c.setopt(pycurl.WRITEFUNCTION, buf.write)
- try:
- c.perform()
- status = c.getinfo(pycurl.RESPONSE_CODE)
-@@ -341,30 +341,30 @@ class CGILookasideCache(object):
- ('mtime', str(int(os.stat(filepath).st_mtime))),
- ]
-
-- with io.BytesIO() as buf:
-- c = pycurl.Curl()
-- c.setopt(pycurl.URL, self.upload_url)
-- c.setopt(pycurl.NOPROGRESS, False)
-- c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
-- c.setopt(pycurl.WRITEFUNCTION, buf.write)
-- c.setopt(pycurl.HTTPPOST, post_data)
-- c.setopt(pycurl.FOLLOWLOCATION, 1)
-+ c = pycurl.Curl()
-+ c.setopt(pycurl.URL, self.upload_url)
-+ c.setopt(pycurl.NOPROGRESS, False)
-+ c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
-+ c.setopt(pycurl.HTTPPOST, post_data)
-+ c.setopt(pycurl.FOLLOWLOCATION, 1)
-
-- if self.client_cert is not None:
-- if os.path.exists(self.client_cert):
-- c.setopt(pycurl.SSLCERT, self.client_cert)
-- else:
-- self.log.warning("Missing certificate: %s", self.client_cert)
-+ if self.client_cert is not None:
-+ if os.path.exists(self.client_cert):
-+ c.setopt(pycurl.SSLCERT, self.client_cert)
-+ else:
-+ self.log.warning("Missing certificate: %s", self.client_cert)
-
-- if self.ca_cert is not None:
-- if os.path.exists(self.ca_cert):
-- c.setopt(pycurl.CAINFO, self.ca_cert)
-- else:
-- self.log.warning("Missing certificate: %s", self.ca_cert)
-+ if self.ca_cert is not None:
-+ if os.path.exists(self.ca_cert):
-+ c.setopt(pycurl.CAINFO, self.ca_cert)
-+ else:
-+ self.log.warning("Missing certificate: %s", self.ca_cert)
-
-- c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
-- c.setopt(pycurl.USERPWD, ':')
-+ c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
-+ c.setopt(pycurl.USERPWD, ':')
-
-+ with io.BytesIO() as buf:
-+ c.setopt(pycurl.WRITEFUNCTION, buf.write)
- try:
- c.perform()
- status = c.getinfo(pycurl.RESPONSE_CODE)
---
-2.41.0
-
diff --git a/0027-Lookaside-cache-operations-retries.patch b/0027-Lookaside-cache-operations-retries.patch
deleted file mode 100644
index fe3a171..0000000
--- a/0027-Lookaside-cache-operations-retries.patch
+++ /dev/null
@@ -1,278 +0,0 @@
-From 3a96293d2479a75348f424806028c9b640aff31c Mon Sep 17 00:00:00 2001
-From: Ondrej Nosek <onosek@redhat.com>
-Date: Tue, 22 Aug 2023 14:48:02 +0200
-Subject: [PATCH 3/4] Lookaside cache operations retries
-
-Both upload and download network operations might fail
-and in this case, a retry mechanism was implemented.
-In case of failure, there is a delay and another attempt(s).
-Delays are increasing with every attempt.
-
-JIRA: RHELCMP-11210
-
-Signed-off-by: Ondrej Nosek <onosek@redhat.com>
----
- pyrpkg/lookaside.py | 129 ++++++++++++++++++++++++++--------------
- tests/test_lookaside.py | 12 ++--
- 2 files changed, 89 insertions(+), 52 deletions(-)
-
-diff --git a/pyrpkg/lookaside.py b/pyrpkg/lookaside.py
-index f94ffdb..01eee4a 100644
---- a/pyrpkg/lookaside.py
-+++ b/pyrpkg/lookaside.py
-@@ -14,11 +14,13 @@ way it is done by Fedora, RHEL, and other distributions maintainers.
- """
-
-
-+import functools
- import hashlib
- import io
- import logging
- import os
- import sys
-+import time
-
- import pycurl
- import six
-@@ -31,7 +33,7 @@ from .errors import (AlreadyUploadedError, DownloadError, InvalidHashType,
- class CGILookasideCache(object):
- """A class to interact with a CGI-based lookaside cache"""
- def __init__(self, hashtype, download_url, upload_url,
-- client_cert=None, ca_cert=None):
-+ client_cert=None, ca_cert=None, attempts=None, delay=None):
- """Constructor
-
- :param str hashtype: The hash algorithm to use for uploads. (e.g 'md5')
-@@ -45,12 +47,18 @@ class CGILookasideCache(object):
- use for HTTPS connexions. (e.g if the server certificate is
- self-signed. It defaults to None, in which case the system CA
- bundle is used.
-+ :param int attempts: repeat network operations after failure. The param
-+ says how many tries to do. None = single attempt / no-retrying
-+ :param int delay: Initial delay between network operation attempts.
-+ Each attempt doubles the previous delay value. In seconds.
- """
- self.hashtype = hashtype
- self.download_url = download_url
- self.upload_url = upload_url
- self.client_cert = client_cert
- self.ca_cert = ca_cert
-+ self.attempts = attempts if attempts is not None and attempts > 1 else 1
-+ self.delay_between_attempts = delay if delay is not None and delay >= 0 else 15
-
- self.log = logging.getLogger(__name__)
-
-@@ -170,20 +178,13 @@ class CGILookasideCache(object):
- c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
- c.setopt(pycurl.OPT_FILETIME, True)
- c.setopt(pycurl.LOW_SPEED_LIMIT, 1000)
-- c.setopt(pycurl.LOW_SPEED_TIME, 300)
-+ c.setopt(pycurl.LOW_SPEED_TIME, 60)
- c.setopt(pycurl.FOLLOWLOCATION, 1)
-- with open(outfile, 'wb') as f:
-- c.setopt(pycurl.WRITEDATA, f)
-- try:
-- c.perform()
-- tstamp = c.getinfo(pycurl.INFO_FILETIME)
-- status = c.getinfo(pycurl.RESPONSE_CODE)
--
-- except Exception as e:
-- raise DownloadError(e)
-
-- finally:
-- c.close()
-+ # call retry method directly instead of @retry decorator - this approach allows passing
-+ # object's internal variables into the retry method
-+ status, tstamp = self.retry(raises=DownloadError)(self.retry_download)(c, outfile)
-+ c.close()
-
- # Get back a new line, after displaying the download progress
- if sys.stdout.isatty():
-@@ -220,13 +221,8 @@ class CGILookasideCache(object):
- c.setopt(pycurl.NOBODY, True)
- c.setopt(pycurl.FOLLOWLOCATION, 1)
-
-- try:
-- c.perform()
-- status = c.getinfo(pycurl.RESPONSE_CODE)
-- except Exception as e:
-- raise DownloadError(e)
-- finally:
-- c.close()
-+ status = self.retry(raises=DownloadError)(self.retry_remote_file_exists_head)(c)
-+ c.close()
-
- if status != 200:
- self.log.debug('Unavailable file \'%s\' at %s' % (filename, url))
-@@ -275,19 +271,8 @@ class CGILookasideCache(object):
- c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
- c.setopt(pycurl.USERPWD, ':')
-
-- with io.BytesIO() as buf:
-- c.setopt(pycurl.WRITEFUNCTION, buf.write)
-- try:
-- c.perform()
-- status = c.getinfo(pycurl.RESPONSE_CODE)
--
-- except Exception as e:
-- raise UploadError(e)
--
-- finally:
-- c.close()
--
-- output = buf.getvalue().strip()
-+ status, output = self.retry(raises=UploadError)(self.retry_remote_file_exists)(c)
-+ c.close()
-
- if status != 200:
- self.raise_upload_error(status)
-@@ -363,19 +348,8 @@ class CGILookasideCache(object):
- c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
- c.setopt(pycurl.USERPWD, ':')
-
-- with io.BytesIO() as buf:
-- c.setopt(pycurl.WRITEFUNCTION, buf.write)
-- try:
-- c.perform()
-- status = c.getinfo(pycurl.RESPONSE_CODE)
--
-- except Exception as e:
-- raise UploadError(e)
--
-- finally:
-- c.close()
--
-- output = buf.getvalue().strip()
-+ status, output = self.retry(raises=UploadError)(self.retry_upload)(c)
-+ c.close()
-
- # Get back a new line, after displaying the download progress
- if sys.stdout.isatty():
-@@ -387,3 +361,66 @@ class CGILookasideCache(object):
-
- if output:
- self.log.debug(output)
-+
-+ def retry_download(self, curl, outfile):
-+ with open(outfile, 'wb') as f:
-+ curl.setopt(pycurl.WRITEDATA, f)
-+ curl.perform()
-+ tstamp = curl.getinfo(pycurl.INFO_FILETIME)
-+ status = curl.getinfo(pycurl.RESPONSE_CODE)
-+ return status, tstamp
-+
-+ def retry_remote_file_exists_head(self, curl):
-+ curl.perform()
-+ status = curl.getinfo(pycurl.RESPONSE_CODE)
-+ return status
-+
-+ def retry_remote_file_exists(self, curl):
-+ with io.BytesIO() as buf:
-+ curl.setopt(pycurl.WRITEFUNCTION, buf.write)
-+ curl.perform()
-+ status = curl.getinfo(pycurl.RESPONSE_CODE)
-+ output = buf.getvalue().strip()
-+ return status, output
-+
-+ def retry_upload(self, curl):
-+ with io.BytesIO() as buf:
-+ curl.setopt(pycurl.WRITEFUNCTION, buf.write)
-+ curl.perform()
-+ status = curl.getinfo(pycurl.RESPONSE_CODE)
-+ output = buf.getvalue().strip()
-+ return status, output
-+
-+ def retry(self, attempts=None, delay_between_attempts=None, wait_on=pycurl.error, raises=None):
-+ """A decorator that allows to retry a section of code until success or counter elapses
-+ """
-+
-+ def wrapper(function):
-+ @functools.wraps(function)
-+ def inner(*args, **kwargs):
-+
-+ attempts_all = attempts or self.attempts
-+ attempts_left = attempts_all
-+ delay = delay_between_attempts or self.delay_between_attempts
-+ while attempts_left > 0:
-+ try:
-+ return function(*args, **kwargs)
-+ except wait_on as e:
-+ self.log.warn("Network error: %s" % (e))
-+ attempts_left -= 1
-+ self.log.debug("Attempt %d/%d has failed."
-+ % (attempts_all - attempts_left, attempts_all))
-+ if attempts_left:
-+ self.log.info("The operation will be retried in %ds." % (delay))
-+ time.sleep(delay)
-+ delay *= 2
-+ self.log.info("Retrying ...")
-+ else:
-+ if raises is None:
-+ raise # This re-raises the last exception.
-+ else:
-+ raise raises(e)
-+
-+ return inner
-+
-+ return wrapper
-diff --git a/tests/test_lookaside.py b/tests/test_lookaside.py
-index 35d3499..2fe1bdb 100644
---- a/tests/test_lookaside.py
-+++ b/tests/test_lookaside.py
-@@ -175,7 +175,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
- return 200 if info == pycurl.RESPONSE_CODE else 0
-
- def mock_perform():
-- with open(self.filename) as f:
-+ with open(self.filename, "rb") as f:
- curlopts[pycurl.WRITEDATA].write(f.read())
-
- def mock_setopt(opt, value):
-@@ -200,7 +200,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
- @mock.patch('pyrpkg.lookaside.pycurl.Curl')
- def test_download_failed(self, mock_curl):
- curl = mock_curl.return_value
-- curl.perform.side_effect = Exception(
-+ curl.perform.side_effect = pycurl.error(
- 'Could not resolve host: example.com')
-
- with open(self.filename, 'wb') as f:
-@@ -219,7 +219,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
- return 500 if info == pycurl.RESPONSE_CODE else 0
-
- def mock_perform():
-- with open(self.filename) as f:
-+ with open(self.filename, "rb") as f:
- curlopts[pycurl.WRITEDATA].write(f.read())
-
- def mock_setopt(opt, value):
-@@ -424,7 +424,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
- @mock.patch('pyrpkg.lookaside.pycurl.Curl')
- def test_remote_file_exists_check_failed(self, mock_curl):
- curl = mock_curl.return_value
-- curl.perform.side_effect = Exception(
-+ curl.perform.side_effect = pycurl.error(
- 'Could not resolve host: example.com')
-
- lc = CGILookasideCache('_', '_', '_')
-@@ -452,7 +452,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
- @mock.patch('pyrpkg.lookaside.pycurl.Curl')
- def test_remote_file_exists_check_unexpected_error(self, mock_curl):
- def mock_perform():
-- curlopts[pycurl.WRITEFUNCTION]('Something unexpected')
-+ curlopts[pycurl.WRITEFUNCTION](b'Something unexpected')
-
- def mock_setopt(opt, value):
- curlopts[opt] = value
-@@ -590,7 +590,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
- @mock.patch('pyrpkg.lookaside.pycurl.Curl')
- def test_upload_failed(self, mock_curl):
- curl = mock_curl.return_value
-- curl.perform.side_effect = Exception(
-+ curl.perform.side_effect = pycurl.error(
- 'Could not resolve host: example.com')
-
- lc = CGILookasideCache('_', '_', '_')
---
-2.41.0
-
diff --git a/0027-Prepare-the-lookaside-cache-code-for-retries.patch b/0027-Prepare-the-lookaside-cache-code-for-retries.patch
new file mode 100644
index 0000000..48a5962
--- /dev/null
+++ b/0027-Prepare-the-lookaside-cache-code-for-retries.patch
@@ -0,0 +1,148 @@
+From 1a0601d29794cec1f735a10208364d11958c41ec Mon Sep 17 00:00:00 2001
+From: Ondrej Nosek <onosek@redhat.com>
+Date: Wed, 26 Jul 2023 01:30:12 +0200
+Subject: [PATCH 06/17] Prepare the lookaside cache code for retries
+
+These changes should not have an impact on the original functionality.
+
+JIRA: RHELCMP-11210
+
+Signed-off-by: Ondrej Nosek <onosek@redhat.com>
+---
+ pyrpkg/lookaside.py | 96 ++++++++++++++++++++++-----------------------
+ 1 file changed, 48 insertions(+), 48 deletions(-)
+
+diff --git a/pyrpkg/lookaside.py b/pyrpkg/lookaside.py
+index 3efcd88..f94ffdb 100644
+--- a/pyrpkg/lookaside.py
++++ b/pyrpkg/lookaside.py
+@@ -163,17 +163,17 @@ class CGILookasideCache(object):
+ url = url.encode('utf-8')
+ self.log.debug("Full url: %s", url)
+
++ c = pycurl.Curl()
++ c.setopt(pycurl.URL, url)
++ c.setopt(pycurl.HTTPHEADER, ['Pragma:'])
++ c.setopt(pycurl.NOPROGRESS, False)
++ c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
++ c.setopt(pycurl.OPT_FILETIME, True)
++ c.setopt(pycurl.LOW_SPEED_LIMIT, 1000)
++ c.setopt(pycurl.LOW_SPEED_TIME, 300)
++ c.setopt(pycurl.FOLLOWLOCATION, 1)
+ with open(outfile, 'wb') as f:
+- c = pycurl.Curl()
+- c.setopt(pycurl.URL, url)
+- c.setopt(pycurl.HTTPHEADER, ['Pragma:'])
+- c.setopt(pycurl.NOPROGRESS, False)
+- c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
+- c.setopt(pycurl.OPT_FILETIME, True)
+ c.setopt(pycurl.WRITEDATA, f)
+- c.setopt(pycurl.LOW_SPEED_LIMIT, 1000)
+- c.setopt(pycurl.LOW_SPEED_TIME, 300)
+- c.setopt(pycurl.FOLLOWLOCATION, 1)
+ try:
+ c.perform()
+ tstamp = c.getinfo(pycurl.INFO_FILETIME)
+@@ -254,29 +254,29 @@ class CGILookasideCache(object):
+ ('%ssum' % self.hashtype, hash),
+ ('filename', filename)]
+
+- with io.BytesIO() as buf:
+- c = pycurl.Curl()
+- c.setopt(pycurl.URL, self.upload_url)
+- c.setopt(pycurl.WRITEFUNCTION, buf.write)
+- c.setopt(pycurl.HTTPPOST, post_data)
+- c.setopt(pycurl.FOLLOWLOCATION, 1)
++ c = pycurl.Curl()
++ c.setopt(pycurl.URL, self.upload_url)
++ c.setopt(pycurl.HTTPPOST, post_data)
++ c.setopt(pycurl.FOLLOWLOCATION, 1)
+
+- if self.client_cert is not None:
+- if os.path.exists(self.client_cert):
+- c.setopt(pycurl.SSLCERT, self.client_cert)
+- else:
+- self.log.warning("Missing certificate: %s"
+- % self.client_cert)
++ if self.client_cert is not None:
++ if os.path.exists(self.client_cert):
++ c.setopt(pycurl.SSLCERT, self.client_cert)
++ else:
++ self.log.warning("Missing certificate: %s"
++ % self.client_cert)
+
+- if self.ca_cert is not None:
+- if os.path.exists(self.ca_cert):
+- c.setopt(pycurl.CAINFO, self.ca_cert)
+- else:
+- self.log.warning("Missing certificate: %s", self.ca_cert)
++ if self.ca_cert is not None:
++ if os.path.exists(self.ca_cert):
++ c.setopt(pycurl.CAINFO, self.ca_cert)
++ else:
++ self.log.warning("Missing certificate: %s", self.ca_cert)
+
+- c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
+- c.setopt(pycurl.USERPWD, ':')
++ c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
++ c.setopt(pycurl.USERPWD, ':')
+
++ with io.BytesIO() as buf:
++ c.setopt(pycurl.WRITEFUNCTION, buf.write)
+ try:
+ c.perform()
+ status = c.getinfo(pycurl.RESPONSE_CODE)
+@@ -341,30 +341,30 @@ class CGILookasideCache(object):
+ ('mtime', str(int(os.stat(filepath).st_mtime))),
+ ]
+
+- with io.BytesIO() as buf:
+- c = pycurl.Curl()
+- c.setopt(pycurl.URL, self.upload_url)
+- c.setopt(pycurl.NOPROGRESS, False)
+- c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
+- c.setopt(pycurl.WRITEFUNCTION, buf.write)
+- c.setopt(pycurl.HTTPPOST, post_data)
+- c.setopt(pycurl.FOLLOWLOCATION, 1)
++ c = pycurl.Curl()
++ c.setopt(pycurl.URL, self.upload_url)
++ c.setopt(pycurl.NOPROGRESS, False)
++ c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
++ c.setopt(pycurl.HTTPPOST, post_data)
++ c.setopt(pycurl.FOLLOWLOCATION, 1)
+
+- if self.client_cert is not None:
+- if os.path.exists(self.client_cert):
+- c.setopt(pycurl.SSLCERT, self.client_cert)
+- else:
+- self.log.warning("Missing certificate: %s", self.client_cert)
++ if self.client_cert is not None:
++ if os.path.exists(self.client_cert):
++ c.setopt(pycurl.SSLCERT, self.client_cert)
++ else:
++ self.log.warning("Missing certificate: %s", self.client_cert)
+
+- if self.ca_cert is not None:
+- if os.path.exists(self.ca_cert):
+- c.setopt(pycurl.CAINFO, self.ca_cert)
+- else:
+- self.log.warning("Missing certificate: %s", self.ca_cert)
++ if self.ca_cert is not None:
++ if os.path.exists(self.ca_cert):
++ c.setopt(pycurl.CAINFO, self.ca_cert)
++ else:
++ self.log.warning("Missing certificate: %s", self.ca_cert)
+
+- c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
+- c.setopt(pycurl.USERPWD, ':')
++ c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
++ c.setopt(pycurl.USERPWD, ':')
+
++ with io.BytesIO() as buf:
++ c.setopt(pycurl.WRITEFUNCTION, buf.write)
+ try:
+ c.perform()
+ status = c.getinfo(pycurl.RESPONSE_CODE)
+--
+2.43.0
+
diff --git a/0028-Lookaside-cache-operations-retries.patch b/0028-Lookaside-cache-operations-retries.patch
new file mode 100644
index 0000000..82c98cd
--- /dev/null
+++ b/0028-Lookaside-cache-operations-retries.patch
@@ -0,0 +1,278 @@
+From 3a96293d2479a75348f424806028c9b640aff31c Mon Sep 17 00:00:00 2001
+From: Ondrej Nosek <onosek@redhat.com>
+Date: Tue, 22 Aug 2023 14:48:02 +0200
+Subject: [PATCH 07/17] Lookaside cache operations retries
+
+Both upload and download network operations might fail
+and in this case, a retry mechanism was implemented.
+In case of failure, there is a delay and another attempt(s).
+Delays are increasing with every attempt.
+
+JIRA: RHELCMP-11210
+
+Signed-off-by: Ondrej Nosek <onosek@redhat.com>
+---
+ pyrpkg/lookaside.py | 129 ++++++++++++++++++++++++++--------------
+ tests/test_lookaside.py | 12 ++--
+ 2 files changed, 89 insertions(+), 52 deletions(-)
+
+diff --git a/pyrpkg/lookaside.py b/pyrpkg/lookaside.py
+index f94ffdb..01eee4a 100644
+--- a/pyrpkg/lookaside.py
++++ b/pyrpkg/lookaside.py
+@@ -14,11 +14,13 @@ way it is done by Fedora, RHEL, and other distributions maintainers.
+ """
+
+
++import functools
+ import hashlib
+ import io
+ import logging
+ import os
+ import sys
++import time
+
+ import pycurl
+ import six
+@@ -31,7 +33,7 @@ from .errors import (AlreadyUploadedError, DownloadError, InvalidHashType,
+ class CGILookasideCache(object):
+ """A class to interact with a CGI-based lookaside cache"""
+ def __init__(self, hashtype, download_url, upload_url,
+- client_cert=None, ca_cert=None):
++ client_cert=None, ca_cert=None, attempts=None, delay=None):
+ """Constructor
+
+ :param str hashtype: The hash algorithm to use for uploads. (e.g 'md5')
+@@ -45,12 +47,18 @@ class CGILookasideCache(object):
+ use for HTTPS connexions. (e.g if the server certificate is
+ self-signed. It defaults to None, in which case the system CA
+ bundle is used.
++ :param int attempts: repeat network operations after failure. The param
++ says how many tries to do. None = single attempt / no-retrying
++ :param int delay: Initial delay between network operation attempts.
++ Each attempt doubles the previous delay value. In seconds.
+ """
+ self.hashtype = hashtype
+ self.download_url = download_url
+ self.upload_url = upload_url
+ self.client_cert = client_cert
+ self.ca_cert = ca_cert
++ self.attempts = attempts if attempts is not None and attempts > 1 else 1
++ self.delay_between_attempts = delay if delay is not None and delay >= 0 else 15
+
+ self.log = logging.getLogger(__name__)
+
+@@ -170,20 +178,13 @@ class CGILookasideCache(object):
+ c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
+ c.setopt(pycurl.OPT_FILETIME, True)
+ c.setopt(pycurl.LOW_SPEED_LIMIT, 1000)
+- c.setopt(pycurl.LOW_SPEED_TIME, 300)
++ c.setopt(pycurl.LOW_SPEED_TIME, 60)
+ c.setopt(pycurl.FOLLOWLOCATION, 1)
+- with open(outfile, 'wb') as f:
+- c.setopt(pycurl.WRITEDATA, f)
+- try:
+- c.perform()
+- tstamp = c.getinfo(pycurl.INFO_FILETIME)
+- status = c.getinfo(pycurl.RESPONSE_CODE)
+-
+- except Exception as e:
+- raise DownloadError(e)
+
+- finally:
+- c.close()
++ # call retry method directly instead of @retry decorator - this approach allows passing
++ # object's internal variables into the retry method
++ status, tstamp = self.retry(raises=DownloadError)(self.retry_download)(c, outfile)
++ c.close()
+
+ # Get back a new line, after displaying the download progress
+ if sys.stdout.isatty():
+@@ -220,13 +221,8 @@ class CGILookasideCache(object):
+ c.setopt(pycurl.NOBODY, True)
+ c.setopt(pycurl.FOLLOWLOCATION, 1)
+
+- try:
+- c.perform()
+- status = c.getinfo(pycurl.RESPONSE_CODE)
+- except Exception as e:
+- raise DownloadError(e)
+- finally:
+- c.close()
++ status = self.retry(raises=DownloadError)(self.retry_remote_file_exists_head)(c)
++ c.close()
+
+ if status != 200:
+ self.log.debug('Unavailable file \'%s\' at %s' % (filename, url))
+@@ -275,19 +271,8 @@ class CGILookasideCache(object):
+ c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
+ c.setopt(pycurl.USERPWD, ':')
+
+- with io.BytesIO() as buf:
+- c.setopt(pycurl.WRITEFUNCTION, buf.write)
+- try:
+- c.perform()
+- status = c.getinfo(pycurl.RESPONSE_CODE)
+-
+- except Exception as e:
+- raise UploadError(e)
+-
+- finally:
+- c.close()
+-
+- output = buf.getvalue().strip()
++ status, output = self.retry(raises=UploadError)(self.retry_remote_file_exists)(c)
++ c.close()
+
+ if status != 200:
+ self.raise_upload_error(status)
+@@ -363,19 +348,8 @@ class CGILookasideCache(object):
+ c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
+ c.setopt(pycurl.USERPWD, ':')
+
+- with io.BytesIO() as buf:
+- c.setopt(pycurl.WRITEFUNCTION, buf.write)
+- try:
+- c.perform()
+- status = c.getinfo(pycurl.RESPONSE_CODE)
+-
+- except Exception as e:
+- raise UploadError(e)
+-
+- finally:
+- c.close()
+-
+- output = buf.getvalue().strip()
++ status, output = self.retry(raises=UploadError)(self.retry_upload)(c)
++ c.close()
+
+ # Get back a new line, after displaying the download progress
+ if sys.stdout.isatty():
+@@ -387,3 +361,66 @@ class CGILookasideCache(object):
+
+ if output:
+ self.log.debug(output)
++
++ def retry_download(self, curl, outfile):
++ with open(outfile, 'wb') as f:
++ curl.setopt(pycurl.WRITEDATA, f)
++ curl.perform()
++ tstamp = curl.getinfo(pycurl.INFO_FILETIME)
++ status = curl.getinfo(pycurl.RESPONSE_CODE)
++ return status, tstamp
++
++ def retry_remote_file_exists_head(self, curl):
++ curl.perform()
++ status = curl.getinfo(pycurl.RESPONSE_CODE)
++ return status
++
++ def retry_remote_file_exists(self, curl):
++ with io.BytesIO() as buf:
++ curl.setopt(pycurl.WRITEFUNCTION, buf.write)
++ curl.perform()
++ status = curl.getinfo(pycurl.RESPONSE_CODE)
++ output = buf.getvalue().strip()
++ return status, output
++
++ def retry_upload(self, curl):
++ with io.BytesIO() as buf:
++ curl.setopt(pycurl.WRITEFUNCTION, buf.write)
++ curl.perform()
++ status = curl.getinfo(pycurl.RESPONSE_CODE)
++ output = buf.getvalue().strip()
++ return status, output
++
++ def retry(self, attempts=None, delay_between_attempts=None, wait_on=pycurl.error, raises=None):
++ """A decorator that allows to retry a section of code until success or counter elapses
++ """
++
++ def wrapper(function):
++ @functools.wraps(function)
++ def inner(*args, **kwargs):
++
++ attempts_all = attempts or self.attempts
++ attempts_left = attempts_all
++ delay = delay_between_attempts or self.delay_between_attempts
++ while attempts_left > 0:
++ try:
++ return function(*args, **kwargs)
++ except wait_on as e:
++ self.log.warn("Network error: %s" % (e))
++ attempts_left -= 1
++ self.log.debug("Attempt %d/%d has failed."
++ % (attempts_all - attempts_left, attempts_all))
++ if attempts_left:
++ self.log.info("The operation will be retried in %ds." % (delay))
++ time.sleep(delay)
++ delay *= 2
++ self.log.info("Retrying ...")
++ else:
++ if raises is None:
++ raise # This re-raises the last exception.
++ else:
++ raise raises(e)
++
++ return inner
++
++ return wrapper
+diff --git a/tests/test_lookaside.py b/tests/test_lookaside.py
+index 35d3499..2fe1bdb 100644
+--- a/tests/test_lookaside.py
++++ b/tests/test_lookaside.py
+@@ -175,7 +175,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
+ return 200 if info == pycurl.RESPONSE_CODE else 0
+
+ def mock_perform():
+- with open(self.filename) as f:
++ with open(self.filename, "rb") as f:
+ curlopts[pycurl.WRITEDATA].write(f.read())
+
+ def mock_setopt(opt, value):
+@@ -200,7 +200,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
+ @mock.patch('pyrpkg.lookaside.pycurl.Curl')
+ def test_download_failed(self, mock_curl):
+ curl = mock_curl.return_value
+- curl.perform.side_effect = Exception(
++ curl.perform.side_effect = pycurl.error(
+ 'Could not resolve host: example.com')
+
+ with open(self.filename, 'wb') as f:
+@@ -219,7 +219,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
+ return 500 if info == pycurl.RESPONSE_CODE else 0
+
+ def mock_perform():
+- with open(self.filename) as f:
++ with open(self.filename, "rb") as f:
+ curlopts[pycurl.WRITEDATA].write(f.read())
+
+ def mock_setopt(opt, value):
+@@ -424,7 +424,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
+ @mock.patch('pyrpkg.lookaside.pycurl.Curl')
+ def test_remote_file_exists_check_failed(self, mock_curl):
+ curl = mock_curl.return_value
+- curl.perform.side_effect = Exception(
++ curl.perform.side_effect = pycurl.error(
+ 'Could not resolve host: example.com')
+
+ lc = CGILookasideCache('_', '_', '_')
+@@ -452,7 +452,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
+ @mock.patch('pyrpkg.lookaside.pycurl.Curl')
+ def test_remote_file_exists_check_unexpected_error(self, mock_curl):
+ def mock_perform():
+- curlopts[pycurl.WRITEFUNCTION]('Something unexpected')
++ curlopts[pycurl.WRITEFUNCTION](b'Something unexpected')
+
+ def mock_setopt(opt, value):
+ curlopts[opt] = value
+@@ -590,7 +590,7 @@ class CGILookasideCacheTestCase(unittest.TestCase):
+ @mock.patch('pyrpkg.lookaside.pycurl.Curl')
+ def test_upload_failed(self, mock_curl):
+ curl = mock_curl.return_value
+- curl.perform.side_effect = Exception(
++ curl.perform.side_effect = pycurl.error(
+ 'Could not resolve host: example.com')
+
+ lc = CGILookasideCache('_', '_', '_')
+--
+2.43.0
+
diff --git a/0028-Make-lookaside-cache-retries-configurable.patch b/0028-Make-lookaside-cache-retries-configurable.patch
deleted file mode 100644
index f2fcfd5..0000000
--- a/0028-Make-lookaside-cache-retries-configurable.patch
+++ /dev/null
@@ -1,108 +0,0 @@
-From 08cebe5fae426c11b51e645754b87e4ec5737ef3 Mon Sep 17 00:00:00 2001
-From: Ondrej Nosek <onosek@redhat.com>
-Date: Tue, 22 Aug 2023 22:22:03 +0200
-Subject: [PATCH 4/4] Make lookaside cache retries configurable
-
-The number of attempts for lookaside cache network operations is now
-configurable - there are new keys 'lookaside_attempts'
-and 'lookaside_delay' in the configuration.
-The Former expresses a maximum number of attempts to try the operation.
-'0' or '1' is for a single try (no-retry).
-The latter means an initial delay between network operation attempts.
-Each attempt doubles the previous delay value. In seconds.
-
-JIRA: RHELCMP-11210
-
-Signed-off-by: Ondrej Nosek <onosek@redhat.com>
----
- pyrpkg/__init__.py | 10 ++++++++--
- pyrpkg/cli.py | 34 +++++++++++++++++++++++++++++++++-
- 2 files changed, 41 insertions(+), 3 deletions(-)
-
-diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
-index f69f2ce..5928d47 100644
---- a/pyrpkg/__init__.py
-+++ b/pyrpkg/__init__.py
-@@ -110,7 +110,8 @@ class Commands(object):
- build_client, user=None,
- dist=None, target=None, quiet=False,
- distgit_namespaced=False, realms=None, lookaside_namespaced=False,
-- git_excludes=None, results_dir='root', allow_pre_generated_srpm=False):
-+ git_excludes=None, results_dir='root', allow_pre_generated_srpm=False,
-+ lookaside_attempts=None, lookaside_delay=None):
- """Init the object and some configuration details."""
-
- # Path to operate on, most often pwd
-@@ -242,6 +243,10 @@ class Commands(object):
- # A Configuration value used in 'import_srpm' command (comes from the Copr team)
- # If pre-generated srpms are allowed, don't care specfile is processed by rpmautospec
- self.allow_pre_generated_srpm = allow_pre_generated_srpm
-+ # number of attempts for lookaside network operations
-+ self.lookaside_attempts = lookaside_attempts
-+ # initial delay between network operation attempts. In seconds.
-+ self.lookaside_delay = lookaside_delay
-
- # Define properties here
- # Properties allow us to "lazy load" various attributes, which also means
-@@ -262,7 +267,8 @@ class Commands(object):
- """
- return CGILookasideCache(
- self.lookasidehash, self.lookaside, self.lookaside_cgi,
-- client_cert=self.cert_file, ca_cert=self.ca_cert)
-+ client_cert=self.cert_file, ca_cert=self.ca_cert,
-+ attempts=self.lookaside_attempts, delay=self.lookaside_delay)
-
- @property
- def path(self):
-diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
-index 1bd7979..16298f0 100644
---- a/pyrpkg/cli.py
-+++ b/pyrpkg/cli.py
-@@ -261,7 +261,9 @@ class cliClient(object):
- realms=realms,
- lookaside_namespaced=la_namespaced,
- git_excludes=git_excludes,
-- results_dir=results_dir
-+ results_dir=results_dir,
-+ lookaside_attempts=self.lookaside_attempts,
-+ lookaside_delay=self.lookaside_delay
- )
-
- if self.args.repo_name:
-@@ -3087,3 +3089,33 @@ class cliClient(object):
-
- def pre_push_check(self):
- self.cmd.pre_push_check(self.args.ref)
-+
-+ @property
-+ def lookaside_attempts(self):
-+ """loads parameter 'lookaside_attempts' from the config file
-+ """
-+ val = None
-+ if self.config.has_option(self.name, 'lookaside_attempts'):
-+ val = self.config.get(self.name, 'lookaside_attempts')
-+ try:
-+ val = int(val)
-+ except Exception:
-+ self.log.error("Error: The config value 'lookaside_attempts' "
-+ "should be an integer.")
-+ val = None
-+ return val
-+
-+ @property
-+ def lookaside_delay(self):
-+ """loads parameter 'lookaside_delay' from the config file
-+ """
-+ val = None
-+ if self.config.has_option(self.name, 'lookaside_delay'):
-+ val = self.config.get(self.name, 'lookaside_delay')
-+ try:
-+ val = int(val)
-+ except Exception:
-+ self.log.error("Error: The config value 'lookaside_delay' "
-+ "should be an integer.")
-+ val = None
-+ return val
---
-2.41.0
-
diff --git a/0029-Make-lookaside-cache-retries-configurable.patch b/0029-Make-lookaside-cache-retries-configurable.patch
new file mode 100644
index 0000000..61fc41c
--- /dev/null
+++ b/0029-Make-lookaside-cache-retries-configurable.patch
@@ -0,0 +1,108 @@
+From 08cebe5fae426c11b51e645754b87e4ec5737ef3 Mon Sep 17 00:00:00 2001
+From: Ondrej Nosek <onosek@redhat.com>
+Date: Tue, 22 Aug 2023 22:22:03 +0200
+Subject: [PATCH 08/17] Make lookaside cache retries configurable
+
+The number of attempts for lookaside cache network operations is now
+configurable - there are new keys 'lookaside_attempts'
+and 'lookaside_delay' in the configuration.
+The Former expresses a maximum number of attempts to try the operation.
+'0' or '1' is for a single try (no-retry).
+The latter means an initial delay between network operation attempts.
+Each attempt doubles the previous delay value. In seconds.
+
+JIRA: RHELCMP-11210
+
+Signed-off-by: Ondrej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 10 ++++++++--
+ pyrpkg/cli.py | 34 +++++++++++++++++++++++++++++++++-
+ 2 files changed, 41 insertions(+), 3 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index f69f2ce..5928d47 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -110,7 +110,8 @@ class Commands(object):
+ build_client, user=None,
+ dist=None, target=None, quiet=False,
+ distgit_namespaced=False, realms=None, lookaside_namespaced=False,
+- git_excludes=None, results_dir='root', allow_pre_generated_srpm=False):
++ git_excludes=None, results_dir='root', allow_pre_generated_srpm=False,
++ lookaside_attempts=None, lookaside_delay=None):
+ """Init the object and some configuration details."""
+
+ # Path to operate on, most often pwd
+@@ -242,6 +243,10 @@ class Commands(object):
+ # A Configuration value used in 'import_srpm' command (comes from the Copr team)
+ # If pre-generated srpms are allowed, don't care specfile is processed by rpmautospec
+ self.allow_pre_generated_srpm = allow_pre_generated_srpm
++ # number of attempts for lookaside network operations
++ self.lookaside_attempts = lookaside_attempts
++ # initial delay between network operation attempts. In seconds.
++ self.lookaside_delay = lookaside_delay
+
+ # Define properties here
+ # Properties allow us to "lazy load" various attributes, which also means
+@@ -262,7 +267,8 @@ class Commands(object):
+ """
+ return CGILookasideCache(
+ self.lookasidehash, self.lookaside, self.lookaside_cgi,
+- client_cert=self.cert_file, ca_cert=self.ca_cert)
++ client_cert=self.cert_file, ca_cert=self.ca_cert,
++ attempts=self.lookaside_attempts, delay=self.lookaside_delay)
+
+ @property
+ def path(self):
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index 1bd7979..16298f0 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -261,7 +261,9 @@ class cliClient(object):
+ realms=realms,
+ lookaside_namespaced=la_namespaced,
+ git_excludes=git_excludes,
+- results_dir=results_dir
++ results_dir=results_dir,
++ lookaside_attempts=self.lookaside_attempts,
++ lookaside_delay=self.lookaside_delay
+ )
+
+ if self.args.repo_name:
+@@ -3087,3 +3089,33 @@ class cliClient(object):
+
+ def pre_push_check(self):
+ self.cmd.pre_push_check(self.args.ref)
++
++ @property
++ def lookaside_attempts(self):
++ """loads parameter 'lookaside_attempts' from the config file
++ """
++ val = None
++ if self.config.has_option(self.name, 'lookaside_attempts'):
++ val = self.config.get(self.name, 'lookaside_attempts')
++ try:
++ val = int(val)
++ except Exception:
++ self.log.error("Error: The config value 'lookaside_attempts' "
++ "should be an integer.")
++ val = None
++ return val
++
++ @property
++ def lookaside_delay(self):
++ """loads parameter 'lookaside_delay' from the config file
++ """
++ val = None
++ if self.config.has_option(self.name, 'lookaside_delay'):
++ val = self.config.get(self.name, 'lookaside_delay')
++ try:
++ val = int(val)
++ except Exception:
++ self.log.error("Error: The config value 'lookaside_delay' "
++ "should be an integer.")
++ val = None
++ return val
+--
+2.43.0
+
diff --git a/0029-pkg-import-Don-t-delete-changelog-generated-by-rpmau.patch b/0029-pkg-import-Don-t-delete-changelog-generated-by-rpmau.patch
deleted file mode 100644
index 7f9685a..0000000
--- a/0029-pkg-import-Don-t-delete-changelog-generated-by-rpmau.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 2fdb7806aa947d4997aea6cb33f3e893a2b7d876 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= <miro@hroncok.cz>
-Date: Thu, 5 Oct 2023 17:03:39 +0200
-Subject: [PATCH 1/5] *pkg import: Don't delete changelog generated by
- `rpmautospec convert`
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Signed-off-by: Miro Hrončok <miro@hroncok.cz>
----
- pyrpkg/__init__.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
-index 5928d47..b257fec 100644
---- a/pyrpkg/__init__.py
-+++ b/pyrpkg/__init__.py
-@@ -2040,7 +2040,7 @@ class Commands(object):
- # not be removed by import command.
- reserved_ourfiles = [
- 'README.md', 'gating.yaml', 'tests/*', '*.rpmlintrc',
-- '.fmf/*', '*.fmf']
-+ '.fmf/*', '*.fmf', 'changelog']
-
- # Get a list of files we're currently tracking
- ourfiles = self.repo.git.ls_files().split('\n')
---
-2.41.0
-
diff --git a/0030-pkg-import-Don-t-delete-changelog-generated-by-rpmau.patch b/0030-pkg-import-Don-t-delete-changelog-generated-by-rpmau.patch
new file mode 100644
index 0000000..f3bd9be
--- /dev/null
+++ b/0030-pkg-import-Don-t-delete-changelog-generated-by-rpmau.patch
@@ -0,0 +1,30 @@
+From 2fdb7806aa947d4997aea6cb33f3e893a2b7d876 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= <miro@hroncok.cz>
+Date: Thu, 5 Oct 2023 17:03:39 +0200
+Subject: [PATCH 09/17] *pkg import: Don't delete changelog generated by
+ `rpmautospec convert`
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Miro Hrončok <miro@hroncok.cz>
+---
+ pyrpkg/__init__.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index 5928d47..b257fec 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -2040,7 +2040,7 @@ class Commands(object):
+ # not be removed by import command.
+ reserved_ourfiles = [
+ 'README.md', 'gating.yaml', 'tests/*', '*.rpmlintrc',
+- '.fmf/*', '*.fmf']
++ '.fmf/*', '*.fmf', 'changelog']
+
+ # Get a list of files we're currently tracking
+ ourfiles = self.repo.git.ls_files().split('\n')
+--
+2.43.0
+
diff --git a/0030-pkg-import-Undo-rpmautospec-processing.patch b/0030-pkg-import-Undo-rpmautospec-processing.patch
deleted file mode 100644
index 131e076..0000000
--- a/0030-pkg-import-Undo-rpmautospec-processing.patch
+++ /dev/null
@@ -1,162 +0,0 @@
-From ebb5c4b82caec5160544fdbb2d539fd1b5a3abfb Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= <miro@hroncok.cz>
-Date: Thu, 5 Oct 2023 16:59:22 +0200
-Subject: [PATCH 2/5] *pkg import: Undo rpmautospec processing
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Fixes https://pagure.io/fedpkg/issue/527
-
-Depends-on: https://pagure.io/fedora-infra/rpmautospec/pull-request/312
-
-Signed-off-by: Miro Hrončok <miro@hroncok.cz>
----
- pyrpkg/__init__.py | 22 ++++++-------
- pyrpkg/utils.py | 77 ++++++++++++++++++++++++++++++++++++++++------
- 2 files changed, 78 insertions(+), 21 deletions(-)
-
-diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
-index b257fec..f562e71 100644
---- a/pyrpkg/__init__.py
-+++ b/pyrpkg/__init__.py
-@@ -53,7 +53,7 @@ from pyrpkg.sources import SourcesFile
- from pyrpkg.spec import SpecFile
- from pyrpkg.utils import (cached_property, extract_srpm, find_me,
- is_file_tracked, is_lookaside_eligible_file,
-- log_result, spec_file_processed_by_rpmautospec)
-+ log_result, spec_file_undo_rpmautospec)
-
- from .gitignore import GitIgnore
-
-@@ -1473,16 +1473,6 @@ class Commands(object):
- uploadfiles.append(file)
- else:
- files.append(file)
--
-- # Check all specfiles in SRPM. At this point (the 'import' command can run under
-- # the dist-git repo without any specfiles - right after initialization) we are
-- # not able determine which the main specfile is.
-- if file.endswith('.spec') and not file.startswith('.') \
-- and not self.allow_pre_generated_srpm \
-- and spec_file_processed_by_rpmautospec(file, target_dir):
-- raise rpkgError('SRPM was processed by rpmautospec '
-- '(specfile "{}" was analyzed)'.format(file))
--
- finally:
- shutil.rmtree(target_dir)
-
-@@ -2081,6 +2071,16 @@ class Commands(object):
- os.chdir(oldpath)
- raise rpkgError("Got an error from rpm2cpio: %s" % err)
-
-+ # Undo rpmautospec from all the spec files.
-+ # At this point (the 'import' command can run under the dist-git repo
-+ # without any specfiles - right after initialization) we are
-+ # not able determine which the main specfile is.
-+ if not self.allow_pre_generated_srpm:
-+ for file in files:
-+ if file.endswith('.spec') and not file.startswith('.'):
-+ if spec_file_undo_rpmautospec(file):
-+ self.log.debug("rpmautospec processing removed from {0}".format(file))
-+
- # And finally add all the files we know about (and our stock files)
- for file in ('.gitignore', 'sources'):
- if not os.path.exists(file):
-diff --git a/pyrpkg/utils.py b/pyrpkg/utils.py
-index 3337bdb..f3c8b25 100644
---- a/pyrpkg/utils.py
-+++ b/pyrpkg/utils.py
-@@ -336,22 +336,79 @@ def is_lookaside_eligible_file(file_name, dir_path=None):
- return encoding == "binary"
-
-
--def spec_file_processed_by_rpmautospec(file_name, dir_path=None):
-+def _replace_lines(lines, startline, endline, replacement_lines=None, strip_endline=False):
-+ replacement_lines = replacement_lines or []
-+ try:
-+ start = lines.index(startline)
-+ end = lines.index(endline, start)
-+ except ValueError:
-+ # if both are missing, nothing to do, all good
-+ # if only one of them is present, we better not touch it
-+ return lines, False
-+ else:
-+ # rpmautospec adds an empty line after the end
-+ # we want to remove it, but only if it is actually empty
-+ if strip_endline and lines[end+1] == "\n":
-+ end += 1
-+ lines = lines[:start] + replacement_lines + lines[end+1:]
-+ return lines, True
-+
-+
-+def spec_file_undo_rpmautospec(file_name, dir_path=None):
-+ """
-+ Given a path to specfile, undo changes generated by rpmautospec.
-+ Iff there is something to undo, the specfile will be overwritten.
-+
-+ Namely:
-+
-+ 1. Removes everything between the following lines:
-+ ## START: Set by rpmautospec
-+ ## END: Set by rpmautospec
-+ 2. Replaces everything between the following lines with %autochangelog:
-+ ## START: Generated by rpmautospec
-+ ## END: Generated by rpmautospec
-+
-+ Both of the steps only happen once. If the specfile contains multiple such sections,
-+ only the first one is removed/replaced.
-+
-+ The saved spec file is not guaranteed to be bit-by-bit identical with the original
-+ spec file used as an input to rpmautospec.
-+ However, subsequent repeated conversions there and back should be quite stable.
-+
-+ The return value says whether the specfile was overwritten.
-+ """
- file_path = os.path.join(dir_path or "", file_name)
-
- try:
-- contents = open(file_path).readlines()
-+ with open(file_path) as f:
-+ contents = f.readlines()
- except Exception:
-- # if we can't read it, let's assume the answer is "no".
-+ # if we can't read it, let's do nothing
- return False
-
-- # Check for the %autorelease header prepended to the file
-- if any('START: Set by rpmautospec' in line for line in contents[:10]):
-+ # remove the generated macro section near the beginning of the specfile
-+ contents, was_removed = _replace_lines(
-+ contents,
-+ '## START: Set by rpmautospec\n',
-+ '## END: Set by rpmautospec\n',
-+ strip_endline=True)
-+
-+ # replace the generated changelog with %autochangelog
-+ # note that this does not generally produce content identical to the original
-+ # e.g. the macro could have been conditionalized or in curly brackets
-+ # most importantly, the %changelog section might have been omitted entirely
-+ # however, this should be Good Enough for most of us
-+ contents, was_replaced = _replace_lines(
-+ contents,
-+ '## START: Generated by rpmautospec\n',
-+ '## END: Generated by rpmautospec\n',
-+ ['%autochangelog\n'])
-+
-+ # finally, replace the spec if needed
-+ # if we cannot write it, better blow up
-+ if was_removed or was_replaced:
-+ with open(file_path, 'w') as f:
-+ f.writelines(contents)
- return True
-
-- # It seems that currently there's no mechanism to detect
-- # %autochangelog processing. But most packages would use both
-- # %autochangelog and %autorelease together, so we should catch
-- # most cases by checking for %autorelease only.
-- # https://pagure.io/fedora-infra/rpmautospec/issue/269
- return False
---
-2.41.0
-
diff --git a/0031-Unittests-for-Undo-rpmautospec-processing.patch b/0031-Unittests-for-Undo-rpmautospec-processing.patch
deleted file mode 100644
index 571872c..0000000
--- a/0031-Unittests-for-Undo-rpmautospec-processing.patch
+++ /dev/null
@@ -1,258 +0,0 @@
-From 2bd726d20f3e5d1502a2191d0d263d0b6132982b Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Ond=C5=99ej=20Nosek?= <onosek@redhat.com>
-Date: Thu, 12 Oct 2023 01:40:36 +0200
-Subject: [PATCH 3/5] Unittests for "Undo rpmautospec processing"
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Merges: https://pagure.io/rpkg/pull-request/699
-Signed-off-by: Ondřej Nosek <onosek@redhat.com>
----
- Jenkinsfile | 2 +-
- jenkins_test.dockerfile | 3 +-
- pyrpkg/utils.py | 6 +--
- tests/fixtures/docpkg/docpkg-rpmautospec.spec | 53 +++++++++++++++++++
- tests/test_cli.py | 40 +++++++-------
- tests/test_utils.py | 36 ++++++++++++-
- 6 files changed, 116 insertions(+), 24 deletions(-)
- create mode 100644 tests/fixtures/docpkg/docpkg-rpmautospec.spec
-
-diff --git a/pyrpkg/utils.py b/pyrpkg/utils.py
-index f3c8b25..fc01c7d 100644
---- a/pyrpkg/utils.py
-+++ b/pyrpkg/utils.py
-@@ -104,7 +104,7 @@ def log_result(log_func, result, level=0, indent=2):
- elif isinstance(result, dict):
- for key, value in result.items():
- _log_value(log_func, key, level, indent, ':')
-- log_result(log_func, value, level+1)
-+ log_result(log_func, value, level + 1)
- else:
- _log_value(log_func, result, level, indent)
-
-@@ -348,9 +348,9 @@ def _replace_lines(lines, startline, endline, replacement_lines=None, strip_endl
- else:
- # rpmautospec adds an empty line after the end
- # we want to remove it, but only if it is actually empty
-- if strip_endline and lines[end+1] == "\n":
-+ if strip_endline and lines[end + 1] == "\n":
- end += 1
-- lines = lines[:start] + replacement_lines + lines[end+1:]
-+ lines = lines[:start] + replacement_lines + lines[end + 1:]
- return lines, True
-
-
-diff --git a/tests/fixtures/docpkg/docpkg-rpmautospec.spec b/tests/fixtures/docpkg/docpkg-rpmautospec.spec
-new file mode 100644
-index 0000000..5974b57
---- /dev/null
-+++ b/tests/fixtures/docpkg/docpkg-rpmautospec.spec
-@@ -0,0 +1,53 @@
-+## START: Set by rpmautospec
-+## (rpmautospec version 0.3.5)
-+## RPMAUTOSPEC: autorelease, autochangelog
-+%define autorelease(e:s:pb:n) %{?-p:0.}%{lua:
-+ release_number = 8;
-+ base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}"));
-+ print(release_number + base_release_number - 1);
-+}%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}}
-+## END: Set by rpmautospec
-+
-+# autogenerated specfile
-+Summary: Dummy summary
-+Name: docpkg-rpmautospec
-+Version: 0.2
-+Release: 1%{dist}
-+License: GPL
-+Group: Applications/Productivity
-+
-+BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
-+Source0: hello-world.txt
-+Source1: docpkg.tar.gz
-+Source2: source-without-extension
-+# added empty dir just to test import srpm `fedpkg import`. It should skip the dir.
-+Source3: EMPTY_DIR
-+
-+%description
-+This is a dummy description.
-+
-+%prep
-+cp %{SOURCE0} .
-+
-+%build
-+
-+%clean
-+rm -rf $$RPM_BUILD_ROOT
-+%install
-+rm -rf $RPM_BUILD_ROOT
-+mkdir $RPM_BUILD_ROOT
-+mkdir -p $RPM_BUILD_ROOT/usr/share/doc
-+cp %{SOURCE0} $RPM_BUILD_ROOT/usr/share/doc/hello-world.txt
-+
-+%files
-+%doc "/usr/share/doc/hello-world.txt"
-+
-+%changelog
-+## START: Generated by rpmautospec
-+* Sun Jan 1 2006 tester <tester@example.com> - 0.2-1
-+- - New release 0.2-1
-+
-+* Sun Jan 1 2006 John Doe <jdoe@example.com> - 0.2-1
-+- Initial version
-+
-+## END: Generated by rpmautospec
-diff --git a/tests/test_cli.py b/tests/test_cli.py
-index f2e68df..3d90f36 100644
---- a/tests/test_cli.py
-+++ b/tests/test_cli.py
-@@ -1706,10 +1706,10 @@ class TestFailureImportSrpm(CliTestCase):
- class TestImportSrpm(LookasideCacheMock, CliTestCase):
-
- @staticmethod
-- def build_srpm(srcrpmdir):
-+ def build_srpm(srcrpmdir, specfile_name='docpkg.spec'):
- """Build a fake SRPM used by this test case"""
- docpkg_dir = os.path.join(fixtures_dir, 'docpkg')
-- specfile = os.path.join(docpkg_dir, 'docpkg.spec')
-+ specfile = os.path.join(docpkg_dir, specfile_name)
- rpmbuild = [
- 'rpmbuild', '-bs',
- '--define', '_topdir {0}'.format(srcrpmdir),
-@@ -1735,6 +1735,9 @@ class TestImportSrpm(LookasideCacheMock, CliTestCase):
-
- self.srcrpmdir = tempfile.mkdtemp(prefix='test-import-srpm-topdir-')
- self.srpm_file = TestImportSrpm.build_srpm(self.srcrpmdir)
-+ self.srpm_file_rpmautospec = TestImportSrpm.build_srpm(
-+ self.srcrpmdir,
-+ specfile_name='docpkg-rpmautospec.spec')
-
- self.chaos_repo = tempfile.mkdtemp(prefix='rpkg-tests-chaos-repo-')
- cmds = (
-@@ -1827,40 +1830,29 @@ class TestImportSrpm(LookasideCacheMock, CliTestCase):
- self.assertFilesExist(['package.rpmlintrc'], search_dir=self.chaos_repo)
- self.assertFilesNotExist(['the_file_is_not_in_reserved.yaml'], search_dir=self.chaos_repo)
-
-- @patch('pyrpkg.spec_file_processed_by_rpmautospec')
-- def test_import_srpm_not_processed_by_rpmautospec(self, rpmautospec_processed):
-+ def test_import_srpm_not_processed_by_rpmautospec(self):
- cli_cmd = ['rpkg', '--path', self.chaos_repo, '--name', 'docpkg',
- 'import', '--skip-diffs', self.srpm_file]
-
-- rpmautospec_processed.return_value = False
- with patch('sys.argv', new=cli_cmd):
- cli = self.new_cli()
- with patch('pyrpkg.lookaside.CGILookasideCache.upload', self.lookasidecache_upload):
- cli.import_srpm() # no exception should be raised
-- rpmautospec_processed.assert_called_once()
-
-- @patch('pyrpkg.spec_file_processed_by_rpmautospec')
-- def test_import_srpm_processed_by_rpmautospec(self, rpmautospec_processed):
-+ def test_import_srpm_processed_by_rpmautospec(self):
- cli_cmd = ['rpkg', '--path', self.chaos_repo, '--name', 'docpkg',
-- 'import', '--skip-diffs', self.srpm_file]
-+ 'import', '--skip-diffs', self.srpm_file_rpmautospec]
-
-- rpmautospec_processed.return_value = True
- with patch('sys.argv', new=cli_cmd):
- cli = self.new_cli()
- with patch('pyrpkg.lookaside.CGILookasideCache.upload', self.lookasidecache_upload):
-- six.assertRaisesRegex(
-- self,
-- rpkgError,
-- 'SRPM was processed by rpmautospec',
-- cli.import_srpm)
-- rpmautospec_processed.assert_called_once()
-+ cli.import_srpm()
-
-- @patch('pyrpkg.spec_file_processed_by_rpmautospec')
-+ @patch('pyrpkg.spec_file_undo_rpmautospec')
- def test_import_srpm_processed_by_rpmautospec_allowed(self, rpmautospec_processed):
- cli_cmd = ['rpkg', '--path', self.chaos_repo, '--name', 'docpkg',
- 'import', '--skip-diffs', self.srpm_file]
-
-- rpmautospec_processed.return_value = True
- with patch('sys.argv', new=cli_cmd):
- cli = self.new_cli()
- cli.cmd.allow_pre_generated_srpm = True
-@@ -1868,6 +1860,18 @@ class TestImportSrpm(LookasideCacheMock, CliTestCase):
- cli.import_srpm() # no exception should be raised
- rpmautospec_processed.assert_not_called()
-
-+ @patch('pyrpkg.spec_file_undo_rpmautospec')
-+ def test_import_srpm_processed_by_rpmautospec_not_allowed(self, rpmautospec_processed):
-+ cli_cmd = ['rpkg', '--path', self.chaos_repo, '--name', 'docpkg',
-+ 'import', '--skip-diffs', self.srpm_file]
-+
-+ with patch('sys.argv', new=cli_cmd):
-+ cli = self.new_cli()
-+ cli.cmd.allow_pre_generated_srpm = False # or None
-+ with patch('pyrpkg.lookaside.CGILookasideCache.upload', self.lookasidecache_upload):
-+ cli.import_srpm() # no exception should be raised
-+ rpmautospec_processed.assert_called_once()
-+
-
- class TestMockbuild(CliTestCase):
- """Test mockbuild command"""
-diff --git a/tests/test_utils.py b/tests/test_utils.py
-index 34188c5..0cd62d8 100644
---- a/tests/test_utils.py
-+++ b/tests/test_utils.py
-@@ -1,15 +1,22 @@
- import os
-+import shutil
- import tempfile
- import unittest
- import warnings
-
-+try:
-+ import rpmautospec
-+except ImportError:
-+ rpmautospec = None
-+
- try:
- from unittest import mock
- except ImportError:
- import mock
-
- from pyrpkg.utils import (cached_property, is_file_in_directory,
-- is_file_tracked, log_result, warn_deprecated)
-+ is_file_tracked, log_result,
-+ spec_file_undo_rpmautospec, warn_deprecated)
-
- from utils import CommandTestCase
-
-@@ -287,3 +294,30 @@ class FileTrackedTestCase(CommandTestCase):
- self.repo_path
- )
- )
-+
-+
-+@unittest.skipIf(
-+ rpmautospec is None,
-+ "Skip test on releases where rpmautospec is not available (RHEL)")
-+class SpecFileUndoRpmautospec(CommandTestCase):
-+ def test_remove_autospec_from_specfile(self):
-+ fixtures_dir = os.path.join(os.path.dirname(__file__), "fixtures", "docpkg")
-+
-+ specfile_without_rpmautospec = os.path.join(fixtures_dir, "docpkg.spec")
-+ specfile_without_rpmautospec_copy = os.path.join(self.repo_path, "docpkg.spec")
-+ shutil.copy2(specfile_without_rpmautospec, specfile_without_rpmautospec_copy)
-+ self.assertFalse(rpmautospec.specfile_uses_rpmautospec(specfile_without_rpmautospec_copy))
-+ self.assertFalse(spec_file_undo_rpmautospec(specfile_without_rpmautospec_copy))
-+ self.assertFalse(rpmautospec.specfile_uses_rpmautospec(specfile_without_rpmautospec_copy))
-+ os.remove(specfile_without_rpmautospec_copy)
-+
-+ specfile_with_rpmautospec = os.path.join(fixtures_dir, "docpkg-rpmautospec.spec")
-+ specfile_with_rpmautospec_copy = os.path.join(self.repo_path, "docpkg-rpmautospec.spec")
-+ shutil.copy2(specfile_with_rpmautospec, specfile_with_rpmautospec_copy)
-+ # returns True if the specfile was modified
-+ self.assertTrue(spec_file_undo_rpmautospec(specfile_with_rpmautospec_copy))
-+ self.assertFalse(rpmautospec.specfile_uses_rpmautospec(
-+ specfile_with_rpmautospec_copy, check_autochangelog=False, check_autorelease=True))
-+ self.assertTrue(rpmautospec.specfile_uses_rpmautospec(
-+ specfile_with_rpmautospec_copy, check_autochangelog=True, check_autorelease=False))
-+ os.remove(specfile_with_rpmautospec_copy)
---
-2.41.0
-
diff --git a/0031-pkg-import-Undo-rpmautospec-processing.patch b/0031-pkg-import-Undo-rpmautospec-processing.patch
new file mode 100644
index 0000000..2d1f9e4
--- /dev/null
+++ b/0031-pkg-import-Undo-rpmautospec-processing.patch
@@ -0,0 +1,162 @@
+From ebb5c4b82caec5160544fdbb2d539fd1b5a3abfb Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= <miro@hroncok.cz>
+Date: Thu, 5 Oct 2023 16:59:22 +0200
+Subject: [PATCH 10/17] *pkg import: Undo rpmautospec processing
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Fixes https://pagure.io/fedpkg/issue/527
+
+Depends-on: https://pagure.io/fedora-infra/rpmautospec/pull-request/312
+
+Signed-off-by: Miro Hrončok <miro@hroncok.cz>
+---
+ pyrpkg/__init__.py | 22 ++++++-------
+ pyrpkg/utils.py | 77 ++++++++++++++++++++++++++++++++++++++++------
+ 2 files changed, 78 insertions(+), 21 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index b257fec..f562e71 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -53,7 +53,7 @@ from pyrpkg.sources import SourcesFile
+ from pyrpkg.spec import SpecFile
+ from pyrpkg.utils import (cached_property, extract_srpm, find_me,
+ is_file_tracked, is_lookaside_eligible_file,
+- log_result, spec_file_processed_by_rpmautospec)
++ log_result, spec_file_undo_rpmautospec)
+
+ from .gitignore import GitIgnore
+
+@@ -1473,16 +1473,6 @@ class Commands(object):
+ uploadfiles.append(file)
+ else:
+ files.append(file)
+-
+- # Check all specfiles in SRPM. At this point (the 'import' command can run under
+- # the dist-git repo without any specfiles - right after initialization) we are
+- # not able determine which the main specfile is.
+- if file.endswith('.spec') and not file.startswith('.') \
+- and not self.allow_pre_generated_srpm \
+- and spec_file_processed_by_rpmautospec(file, target_dir):
+- raise rpkgError('SRPM was processed by rpmautospec '
+- '(specfile "{}" was analyzed)'.format(file))
+-
+ finally:
+ shutil.rmtree(target_dir)
+
+@@ -2081,6 +2071,16 @@ class Commands(object):
+ os.chdir(oldpath)
+ raise rpkgError("Got an error from rpm2cpio: %s" % err)
+
++ # Undo rpmautospec from all the spec files.
++ # At this point (the 'import' command can run under the dist-git repo
++ # without any specfiles - right after initialization) we are
++ # not able determine which the main specfile is.
++ if not self.allow_pre_generated_srpm:
++ for file in files:
++ if file.endswith('.spec') and not file.startswith('.'):
++ if spec_file_undo_rpmautospec(file):
++ self.log.debug("rpmautospec processing removed from {0}".format(file))
++
+ # And finally add all the files we know about (and our stock files)
+ for file in ('.gitignore', 'sources'):
+ if not os.path.exists(file):
+diff --git a/pyrpkg/utils.py b/pyrpkg/utils.py
+index 3337bdb..f3c8b25 100644
+--- a/pyrpkg/utils.py
++++ b/pyrpkg/utils.py
+@@ -336,22 +336,79 @@ def is_lookaside_eligible_file(file_name, dir_path=None):
+ return encoding == "binary"
+
+
+-def spec_file_processed_by_rpmautospec(file_name, dir_path=None):
++def _replace_lines(lines, startline, endline, replacement_lines=None, strip_endline=False):
++ replacement_lines = replacement_lines or []
++ try:
++ start = lines.index(startline)
++ end = lines.index(endline, start)
++ except ValueError:
++ # if both are missing, nothing to do, all good
++ # if only one of them is present, we better not touch it
++ return lines, False
++ else:
++ # rpmautospec adds an empty line after the end
++ # we want to remove it, but only if it is actually empty
++ if strip_endline and lines[end+1] == "\n":
++ end += 1
++ lines = lines[:start] + replacement_lines + lines[end+1:]
++ return lines, True
++
++
++def spec_file_undo_rpmautospec(file_name, dir_path=None):
++ """
++ Given a path to specfile, undo changes generated by rpmautospec.
++ Iff there is something to undo, the specfile will be overwritten.
++
++ Namely:
++
++ 1. Removes everything between the following lines:
++ ## START: Set by rpmautospec
++ ## END: Set by rpmautospec
++ 2. Replaces everything between the following lines with %autochangelog:
++ ## START: Generated by rpmautospec
++ ## END: Generated by rpmautospec
++
++ Both of the steps only happen once. If the specfile contains multiple such sections,
++ only the first one is removed/replaced.
++
++ The saved spec file is not guaranteed to be bit-by-bit identical with the original
++ spec file used as an input to rpmautospec.
++ However, subsequent repeated conversions there and back should be quite stable.
++
++ The return value says whether the specfile was overwritten.
++ """
+ file_path = os.path.join(dir_path or "", file_name)
+
+ try:
+- contents = open(file_path).readlines()
++ with open(file_path) as f:
++ contents = f.readlines()
+ except Exception:
+- # if we can't read it, let's assume the answer is "no".
++ # if we can't read it, let's do nothing
+ return False
+
+- # Check for the %autorelease header prepended to the file
+- if any('START: Set by rpmautospec' in line for line in contents[:10]):
++ # remove the generated macro section near the beginning of the specfile
++ contents, was_removed = _replace_lines(
++ contents,
++ '## START: Set by rpmautospec\n',
++ '## END: Set by rpmautospec\n',
++ strip_endline=True)
++
++ # replace the generated changelog with %autochangelog
++ # note that this does not generally produce content identical to the original
++ # e.g. the macro could have been conditionalized or in curly brackets
++ # most importantly, the %changelog section might have been omitted entirely
++ # however, this should be Good Enough for most of us
++ contents, was_replaced = _replace_lines(
++ contents,
++ '## START: Generated by rpmautospec\n',
++ '## END: Generated by rpmautospec\n',
++ ['%autochangelog\n'])
++
++ # finally, replace the spec if needed
++ # if we cannot write it, better blow up
++ if was_removed or was_replaced:
++ with open(file_path, 'w') as f:
++ f.writelines(contents)
+ return True
+
+- # It seems that currently there's no mechanism to detect
+- # %autochangelog processing. But most packages would use both
+- # %autochangelog and %autorelease together, so we should catch
+- # most cases by checking for %autorelease only.
+- # https://pagure.io/fedora-infra/rpmautospec/issue/269
+ return False
+--
+2.43.0
+
diff --git a/0032-Unittests-for-Undo-rpmautospec-processing.patch b/0032-Unittests-for-Undo-rpmautospec-processing.patch
new file mode 100644
index 0000000..f806c1c
--- /dev/null
+++ b/0032-Unittests-for-Undo-rpmautospec-processing.patch
@@ -0,0 +1,289 @@
+From 2bd726d20f3e5d1502a2191d0d263d0b6132982b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Nosek?= <onosek@redhat.com>
+Date: Thu, 12 Oct 2023 01:40:36 +0200
+Subject: [PATCH] Unittests for "Undo rpmautospec processing"
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Merges: https://pagure.io/rpkg/pull-request/699
+Signed-off-by: Ondřej Nosek <onosek@redhat.com>
+---
+ Jenkinsfile | 2 +-
+ jenkins_test.dockerfile | 3 +-
+ pyrpkg/utils.py | 6 +--
+ tests/fixtures/docpkg/docpkg-rpmautospec.spec | 53 +++++++++++++++++++
+ tests/test_cli.py | 40 +++++++-------
+ tests/test_utils.py | 36 ++++++++++++-
+ 6 files changed, 116 insertions(+), 24 deletions(-)
+ create mode 100644 tests/fixtures/docpkg/docpkg-rpmautospec.spec
+
+diff --git a/Jenkinsfile b/Jenkinsfile
+index 7b6742f..e6d6394 100644
+--- a/Jenkinsfile
++++ b/Jenkinsfile
+@@ -50,7 +50,7 @@ git fetch proposed
+ git checkout "origin/$params.BRANCH_TO"
+ git merge --no-ff "proposed/$params.BRANCH" -m "Merge PR"
+
+-podman run --rm -v .:/src:Z quay.io/exd-guild-source-tools/rpkg-test:latest tox -e py36,py39,flake8,bandit --workdir /tmp/tox ${TOX_POSARGS}
++podman run --rm -v .:/src:Z quay.io/exd-guild-source-tools/rpkg-test:latest tox -e py36,py39,py311,flake8,bandit --workdir /tmp/tox ${TOX_POSARGS}
+ # disabled py27 environment for now; keep just flake8 for Python 2
+ podman run --rm -v .:/src:Z quay.io/exd-guild-source-tools/rpkg-test-py2:latest tox -e flake8python2 --workdir /tmp/tox ${TOX_POSARGS}
+ """
+diff --git a/jenkins_test.dockerfile b/jenkins_test.dockerfile
+index df8762e..d217a55 100644
+--- a/jenkins_test.dockerfile
++++ b/jenkins_test.dockerfile
+@@ -1,4 +1,4 @@
+-FROM fedora:37
++FROM fedora:38
+ LABEL \
+ name="rpkg test" \
+ description="Run tests using tox with Python 3" \
+@@ -9,6 +9,7 @@ RUN dnf -y update && dnf -y install \
+ python3-devel \
+ python3-openidc-client \
+ python3-libmodulemd \
++ python3-rpmautospec \
+ python3-setuptools \
+ rpmlint \
+ rpm-build \
+diff --git a/pyrpkg/utils.py b/pyrpkg/utils.py
+index f3c8b25..fc01c7d 100644
+--- a/pyrpkg/utils.py
++++ b/pyrpkg/utils.py
+@@ -104,7 +104,7 @@ def log_result(log_func, result, level=0, indent=2):
+ elif isinstance(result, dict):
+ for key, value in result.items():
+ _log_value(log_func, key, level, indent, ':')
+- log_result(log_func, value, level+1)
++ log_result(log_func, value, level + 1)
+ else:
+ _log_value(log_func, result, level, indent)
+
+@@ -348,9 +348,9 @@ def _replace_lines(lines, startline, endline, replacement_lines=None, strip_endl
+ else:
+ # rpmautospec adds an empty line after the end
+ # we want to remove it, but only if it is actually empty
+- if strip_endline and lines[end+1] == "\n":
++ if strip_endline and lines[end + 1] == "\n":
+ end += 1
+- lines = lines[:start] + replacement_lines + lines[end+1:]
++ lines = lines[:start] + replacement_lines + lines[end + 1:]
+ return lines, True
+
+
+diff --git a/tests/fixtures/docpkg/docpkg-rpmautospec.spec b/tests/fixtures/docpkg/docpkg-rpmautospec.spec
+new file mode 100644
+index 0000000..5974b57
+--- /dev/null
++++ b/tests/fixtures/docpkg/docpkg-rpmautospec.spec
+@@ -0,0 +1,53 @@
++## START: Set by rpmautospec
++## (rpmautospec version 0.3.5)
++## RPMAUTOSPEC: autorelease, autochangelog
++%define autorelease(e:s:pb:n) %{?-p:0.}%{lua:
++ release_number = 8;
++ base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}"));
++ print(release_number + base_release_number - 1);
++}%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}}
++## END: Set by rpmautospec
++
++# autogenerated specfile
++Summary: Dummy summary
++Name: docpkg-rpmautospec
++Version: 0.2
++Release: 1%{dist}
++License: GPL
++Group: Applications/Productivity
++
++BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
++Source0: hello-world.txt
++Source1: docpkg.tar.gz
++Source2: source-without-extension
++# added empty dir just to test import srpm `fedpkg import`. It should skip the dir.
++Source3: EMPTY_DIR
++
++%description
++This is a dummy description.
++
++%prep
++cp %{SOURCE0} .
++
++%build
++
++%clean
++rm -rf $$RPM_BUILD_ROOT
++%install
++rm -rf $RPM_BUILD_ROOT
++mkdir $RPM_BUILD_ROOT
++mkdir -p $RPM_BUILD_ROOT/usr/share/doc
++cp %{SOURCE0} $RPM_BUILD_ROOT/usr/share/doc/hello-world.txt
++
++%files
++%doc "/usr/share/doc/hello-world.txt"
++
++%changelog
++## START: Generated by rpmautospec
++* Sun Jan 1 2006 tester <tester@example.com> - 0.2-1
++- - New release 0.2-1
++
++* Sun Jan 1 2006 John Doe <jdoe@example.com> - 0.2-1
++- Initial version
++
++## END: Generated by rpmautospec
+diff --git a/tests/test_cli.py b/tests/test_cli.py
+index f2e68df..3d90f36 100644
+--- a/tests/test_cli.py
++++ b/tests/test_cli.py
+@@ -1706,10 +1706,10 @@ class TestFailureImportSrpm(CliTestCase):
+ class TestImportSrpm(LookasideCacheMock, CliTestCase):
+
+ @staticmethod
+- def build_srpm(srcrpmdir):
++ def build_srpm(srcrpmdir, specfile_name='docpkg.spec'):
+ """Build a fake SRPM used by this test case"""
+ docpkg_dir = os.path.join(fixtures_dir, 'docpkg')
+- specfile = os.path.join(docpkg_dir, 'docpkg.spec')
++ specfile = os.path.join(docpkg_dir, specfile_name)
+ rpmbuild = [
+ 'rpmbuild', '-bs',
+ '--define', '_topdir {0}'.format(srcrpmdir),
+@@ -1735,6 +1735,9 @@ class TestImportSrpm(LookasideCacheMock, CliTestCase):
+
+ self.srcrpmdir = tempfile.mkdtemp(prefix='test-import-srpm-topdir-')
+ self.srpm_file = TestImportSrpm.build_srpm(self.srcrpmdir)
++ self.srpm_file_rpmautospec = TestImportSrpm.build_srpm(
++ self.srcrpmdir,
++ specfile_name='docpkg-rpmautospec.spec')
+
+ self.chaos_repo = tempfile.mkdtemp(prefix='rpkg-tests-chaos-repo-')
+ cmds = (
+@@ -1827,40 +1830,29 @@ class TestImportSrpm(LookasideCacheMock, CliTestCase):
+ self.assertFilesExist(['package.rpmlintrc'], search_dir=self.chaos_repo)
+ self.assertFilesNotExist(['the_file_is_not_in_reserved.yaml'], search_dir=self.chaos_repo)
+
+- @patch('pyrpkg.spec_file_processed_by_rpmautospec')
+- def test_import_srpm_not_processed_by_rpmautospec(self, rpmautospec_processed):
++ def test_import_srpm_not_processed_by_rpmautospec(self):
+ cli_cmd = ['rpkg', '--path', self.chaos_repo, '--name', 'docpkg',
+ 'import', '--skip-diffs', self.srpm_file]
+
+- rpmautospec_processed.return_value = False
+ with patch('sys.argv', new=cli_cmd):
+ cli = self.new_cli()
+ with patch('pyrpkg.lookaside.CGILookasideCache.upload', self.lookasidecache_upload):
+ cli.import_srpm() # no exception should be raised
+- rpmautospec_processed.assert_called_once()
+
+- @patch('pyrpkg.spec_file_processed_by_rpmautospec')
+- def test_import_srpm_processed_by_rpmautospec(self, rpmautospec_processed):
++ def test_import_srpm_processed_by_rpmautospec(self):
+ cli_cmd = ['rpkg', '--path', self.chaos_repo, '--name', 'docpkg',
+- 'import', '--skip-diffs', self.srpm_file]
++ 'import', '--skip-diffs', self.srpm_file_rpmautospec]
+
+- rpmautospec_processed.return_value = True
+ with patch('sys.argv', new=cli_cmd):
+ cli = self.new_cli()
+ with patch('pyrpkg.lookaside.CGILookasideCache.upload', self.lookasidecache_upload):
+- six.assertRaisesRegex(
+- self,
+- rpkgError,
+- 'SRPM was processed by rpmautospec',
+- cli.import_srpm)
+- rpmautospec_processed.assert_called_once()
++ cli.import_srpm()
+
+- @patch('pyrpkg.spec_file_processed_by_rpmautospec')
++ @patch('pyrpkg.spec_file_undo_rpmautospec')
+ def test_import_srpm_processed_by_rpmautospec_allowed(self, rpmautospec_processed):
+ cli_cmd = ['rpkg', '--path', self.chaos_repo, '--name', 'docpkg',
+ 'import', '--skip-diffs', self.srpm_file]
+
+- rpmautospec_processed.return_value = True
+ with patch('sys.argv', new=cli_cmd):
+ cli = self.new_cli()
+ cli.cmd.allow_pre_generated_srpm = True
+@@ -1868,6 +1860,18 @@ class TestImportSrpm(LookasideCacheMock, CliTestCase):
+ cli.import_srpm() # no exception should be raised
+ rpmautospec_processed.assert_not_called()
+
++ @patch('pyrpkg.spec_file_undo_rpmautospec')
++ def test_import_srpm_processed_by_rpmautospec_not_allowed(self, rpmautospec_processed):
++ cli_cmd = ['rpkg', '--path', self.chaos_repo, '--name', 'docpkg',
++ 'import', '--skip-diffs', self.srpm_file]
++
++ with patch('sys.argv', new=cli_cmd):
++ cli = self.new_cli()
++ cli.cmd.allow_pre_generated_srpm = False # or None
++ with patch('pyrpkg.lookaside.CGILookasideCache.upload', self.lookasidecache_upload):
++ cli.import_srpm() # no exception should be raised
++ rpmautospec_processed.assert_called_once()
++
+
+ class TestMockbuild(CliTestCase):
+ """Test mockbuild command"""
+diff --git a/tests/test_utils.py b/tests/test_utils.py
+index 34188c5..0cd62d8 100644
+--- a/tests/test_utils.py
++++ b/tests/test_utils.py
+@@ -1,15 +1,22 @@
+ import os
++import shutil
+ import tempfile
+ import unittest
+ import warnings
+
++try:
++ import rpmautospec
++except ImportError:
++ rpmautospec = None
++
+ try:
+ from unittest import mock
+ except ImportError:
+ import mock
+
+ from pyrpkg.utils import (cached_property, is_file_in_directory,
+- is_file_tracked, log_result, warn_deprecated)
++ is_file_tracked, log_result,
++ spec_file_undo_rpmautospec, warn_deprecated)
+
+ from utils import CommandTestCase
+
+@@ -287,3 +294,30 @@ class FileTrackedTestCase(CommandTestCase):
+ self.repo_path
+ )
+ )
++
++
++@unittest.skipIf(
++ rpmautospec is None,
++ "Skip test on releases where rpmautospec is not available (RHEL)")
++class SpecFileUndoRpmautospec(CommandTestCase):
++ def test_remove_autospec_from_specfile(self):
++ fixtures_dir = os.path.join(os.path.dirname(__file__), "fixtures", "docpkg")
++
++ specfile_without_rpmautospec = os.path.join(fixtures_dir, "docpkg.spec")
++ specfile_without_rpmautospec_copy = os.path.join(self.repo_path, "docpkg.spec")
++ shutil.copy2(specfile_without_rpmautospec, specfile_without_rpmautospec_copy)
++ self.assertFalse(rpmautospec.specfile_uses_rpmautospec(specfile_without_rpmautospec_copy))
++ self.assertFalse(spec_file_undo_rpmautospec(specfile_without_rpmautospec_copy))
++ self.assertFalse(rpmautospec.specfile_uses_rpmautospec(specfile_without_rpmautospec_copy))
++ os.remove(specfile_without_rpmautospec_copy)
++
++ specfile_with_rpmautospec = os.path.join(fixtures_dir, "docpkg-rpmautospec.spec")
++ specfile_with_rpmautospec_copy = os.path.join(self.repo_path, "docpkg-rpmautospec.spec")
++ shutil.copy2(specfile_with_rpmautospec, specfile_with_rpmautospec_copy)
++ # returns True if the specfile was modified
++ self.assertTrue(spec_file_undo_rpmautospec(specfile_with_rpmautospec_copy))
++ self.assertFalse(rpmautospec.specfile_uses_rpmautospec(
++ specfile_with_rpmautospec_copy, check_autochangelog=False, check_autorelease=True))
++ self.assertTrue(rpmautospec.specfile_uses_rpmautospec(
++ specfile_with_rpmautospec_copy, check_autochangelog=True, check_autorelease=False))
++ os.remove(specfile_with_rpmautospec_copy)
+--
+2.43.0
+
diff --git a/0033-Update-docker-image-for-Jenkinks-tests.patch b/0033-Update-docker-image-for-Jenkinks-tests.patch
new file mode 100644
index 0000000..06d747d
--- /dev/null
+++ b/0033-Update-docker-image-for-Jenkinks-tests.patch
@@ -0,0 +1,73 @@
+From 898b2c61506e72847bae00aa8ffe228aebb69547 Mon Sep 17 00:00:00 2001
+From: Ondrej Nosek <onosek@redhat.com>
+Date: Tue, 7 Mar 2023 19:15:28 +0100
+Subject: [PATCH] Update docker image for Jenkinks tests
+
+Regenerate the docker image: Fedora 35 --> Fedora 37.
+Jenkinsfile as a pipeline script is unified with fedpkg.
+
+JIRA: RHELCMP-11391
+Signed-off-by: Ondrej Nosek <onosek@redhat.com>
+---
+ Jenkinsfile | 14 +++++++++-----
+ jenkins_test.dockerfile | 2 +-
+ 2 files changed, 10 insertions(+), 6 deletions(-)
+
+diff --git a/Jenkinsfile b/Jenkinsfile
+index b67b9a7..7b6742f 100644
+--- a/Jenkinsfile
++++ b/Jenkinsfile
+@@ -23,7 +23,7 @@ pipeline {
+ }
+ try {
+ echo "Requesting duffy node ..."
+- def session_str = sh returnStdout: true, script: "set +x; duffy client --url https://duffy.ci.centos.org/api/v1 --auth-name fedora-infra --auth-key $CICO_API_KEY request-session pool=virt-ec2-t2-centos-9s-x86_64,quantity=1"
++ def session_str = sh returnStdout: true, script: "set +x; duffy client --url https://duffy.ci.centos.org/api/v1 --auth-name fedora-infra --auth-key $CICO_API_KEY request-session pool=virt-ec2-t2-centos-8s-x86_64,quantity=1"
+ def session = readJSON text: session_str
+ DUFFY_SESSION_ID= session.session.id
+ def hostname = session.session.nodes[0].hostname
+@@ -31,7 +31,7 @@ pipeline {
+ def remote_dir = "/tmp/$JENKINS_AGENT_NAME"
+ echo "remote_dir: $remote_dir"
+ // this makes tests run in parallel if needed: "--parallel=auto --parallel-live"
+- def TOX_POSARGS =
++ def TOX_POSARGS = ""
+ writeFile file: 'job.sh', text: """
+ set -xe
+ dnf install -y git podman
+@@ -40,15 +40,19 @@ git config --global user.name "jenkins"
+ cd $remote_dir
+ git clone https://pagure.io/rpkg.git -b master
+ cd rpkg
+-git remote rm proposed || true
++# remove remote only if exists
++remotes=\$(git remote)
++if echo "\$remotes" | grep -q "^proposed\$"; then
++ git remote rm proposed || true
++fi
+ git remote add proposed "$params.REPO"
+ git fetch proposed
+ git checkout "origin/$params.BRANCH_TO"
+ git merge --no-ff "proposed/$params.BRANCH" -m "Merge PR"
+
+-podman run --rm -v .:/src:Z quay.io/exd-guild-source-tools/rpkg-test tox -e py36,py39,flake8,bandit --workdir /tmp/tox ${TOX_POSARGS}
++podman run --rm -v .:/src:Z quay.io/exd-guild-source-tools/rpkg-test:latest tox -e py36,py39,flake8,bandit --workdir /tmp/tox ${TOX_POSARGS}
+ # disabled py27 environment for now; keep just flake8 for Python 2
+-podman run --rm -v .:/src:Z quay.io/exd-guild-source-tools/rpkg-test-py2 tox -e flake8python2 --workdir /tmp/tox ${TOX_POSARGS}
++podman run --rm -v .:/src:Z quay.io/exd-guild-source-tools/rpkg-test-py2:latest tox -e flake8python2 --workdir /tmp/tox ${TOX_POSARGS}
+ """
+ sh "cat job.sh"
+ sh "ssh -o StrictHostKeyChecking=no root@$hostname mkdir $remote_dir"
+diff --git a/jenkins_test.dockerfile b/jenkins_test.dockerfile
+index 43245b1..df8762e 100644
+--- a/jenkins_test.dockerfile
++++ b/jenkins_test.dockerfile
+@@ -1,4 +1,4 @@
+-FROM fedora:35
++FROM fedora:37
+ LABEL \
+ name="rpkg test" \
+ description="Run tests using tox with Python 3" \
+--
+2.43.0
+
diff --git a/0034-mockbuild-new-argument-extra-pkgs.patch b/0034-mockbuild-new-argument-extra-pkgs.patch
new file mode 100644
index 0000000..9579dd3
--- /dev/null
+++ b/0034-mockbuild-new-argument-extra-pkgs.patch
@@ -0,0 +1,68 @@
+From 89d3bb0d34250fe24eed5bc63edd992e932a622a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Nosek?= <onosek@redhat.com>
+Date: Wed, 15 Nov 2023 02:53:10 +0100
+Subject: [PATCH 14/17] `mockbuild`: new argument --extra-pkgs
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Added the new argument `--extra-pkgs` to `mockbuild` command.
+It installs additional packages to mock's chroot. In some cases is
+possible to use the current mechanism of extra args placed
+after `--` at the end of the command line instead of `--extra-pkgs`.
+
+ Example: fedpkg mockbuild -- --additional-package <pkg>
+
+Argument(s) `additional-package` is passed to `mock`.
+`additional-package` can't be used together with `--shell`.
+
+JIRA: RHELCMP-11017
+Fixes: https://pagure.io/fedpkg/issue/498
+
+Signed-off-by: Ondřej Nosek <onosek@redhat.com>
+---
+ pyrpkg/cli.py | 21 +++++++++++++++++++++
+ 1 file changed, 21 insertions(+)
+
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index 6fd6df0..06f4c1a 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -1152,6 +1152,9 @@ class cliClient(object):
+ '--use-local-mock-config', default=None, dest="local_mock_config",
+ action='store_true',
+ help="Enforce use of local Mock configuration.")
++ mockbuild_parser.add_argument(
++ '--extra-pkgs', action='append', nargs='*',
++ help="Install additional packages into chroot")
+
+ mockbuild_parser.set_defaults(command=self.mockbuild)
+
+@@ -2514,6 +2517,24 @@ class cliClient(object):
+ "%s-%s-%s.src.rpm"
+ % (self.cmd.repo_name, self.cmd.ver, self.cmd.rel))
+ self.log.debug('Srpm generated: {0}'.format(self.cmd.srpmname))
++ if self.args.extra_pkgs:
++ mockargs_extra_pkgs = []
++ list_extra_pkgs = []
++ # process possible multiple argument's occurrences
++ for arg_arr in self.args.extra_pkgs:
++ for additional_package in arg_arr:
++ mockargs_extra_pkgs.extend(['--additional-package', additional_package])
++ list_extra_pkgs.append(additional_package)
++ # installation will run in separated mock process, so do not clean prepared chroot
++ # before the main mock run
++ mockargs_extra_pkgs.extend(['--no-cleanup-after'])
++ self.log.info('Installing extra packages into the mock chroot: {}'.format(
++ ', '.join(list_extra_pkgs)))
++ self.cmd.mockbuild(mockargs_extra_pkgs, self.args.root,
++ hashtype=self.args.hash,
++ shell=None, # nosec
++ force_local_mock_config=self.args.local_mock_config)
++
+ self.cmd.mockbuild(mockargs, self.args.root,
+ hashtype=self.args.hash,
+ shell=self.args.shell, # nosec
+--
+2.43.0
+
diff --git a/0035-Add-option-to-mockbuild-use-default-resultdir-of-moc.patch b/0035-Add-option-to-mockbuild-use-default-resultdir-of-moc.patch
new file mode 100644
index 0000000..9f8f4a0
--- /dev/null
+++ b/0035-Add-option-to-mockbuild-use-default-resultdir-of-moc.patch
@@ -0,0 +1,69 @@
+From 446b80ad746771dbfa938476504e13c829f2e7f6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?S=C3=A9rgio=20M=2E=20Basto?= <sergio@serjux.com>
+Date: Mon, 3 Oct 2022 01:11:20 +0100
+Subject: [PATCH 17/17] Add option to mockbuild use default resultdir of mock
+ (v3)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Merges: https://pagure.io/rpkg/pull-request/637
+
+Signed-off-by: Sérgio M. Basto <sergio@serjux.com>
+---
+ pyrpkg/__init__.py | 6 ++++--
+ pyrpkg/cli.py | 7 ++++++-
+ 2 files changed, 10 insertions(+), 3 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index d2d8c8a..4c56169 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -3127,7 +3127,7 @@ class Commands(object):
+ return root, config_dir
+
+ def mockbuild(self, mockargs=[], root=None, hashtype=None, shell=None,
+- force_local_mock_config=None, srpm_mock=False):
++ force_local_mock_config=None, srpm_mock=False, default_mock_resultdir=False):
+ """Build the package in mock, using mockargs
+
+ Log the output and returns nothing
+@@ -3167,7 +3167,9 @@ class Commands(object):
+ if config_dir:
+ cmd.extend(['--configdir', config_dir])
+
+- cmd += ['-r', root, '--resultdir', self.mock_results_dir]
++ cmd += ['-r', root]
++ if default_mock_resultdir is not True:
++ cmd += ['--resultdir', self.mock_results_dir]
+
+ if shell:
+ cmd.append('--shell')
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index b6adaad..621df32 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -1154,6 +1154,10 @@ class cliClient(object):
+ '--use-local-mock-config', default=None, dest="local_mock_config",
+ action='store_true',
+ help="Enforce use of local Mock configuration.")
++ mockbuild_parser.add_argument(
++ '--default-mock-resultdir', default=None, dest="default_mock_resultdir",
++ action='store_true',
++ help="Don't modify Mock resultdir.")
+ mockbuild_parser.add_argument(
+ '--extra-pkgs', action='append', nargs='*',
+ help="Install additional packages into chroot")
+@@ -2544,7 +2548,8 @@ class cliClient(object):
+ self.cmd.mockbuild(mockargs, self.args.root,
+ hashtype=self.args.hash,
+ shell=self.args.shell, # nosec
+- force_local_mock_config=self.args.local_mock_config)
++ force_local_mock_config=self.args.local_mock_config,
++ default_mock_resultdir=self.args.default_mock_resultdir)
+ except Exception as e:
+ raise rpkgError(e)
+
+--
+2.43.0
+
diff --git a/rpkg.spec b/rpkg.spec
index 4cdffff..5f213de 100644
--- a/rpkg.spec
+++ b/rpkg.spec
@@ -1,6 +1,6 @@
Name: rpkg
Version: 1.66
-Release: 14%{?dist}
+Release: 15%{?dist}
Summary: Python library for interacting with rpm+git
License: GPLv2+ and LGPLv2
@@ -39,32 +39,36 @@ Patch3: 0003-Remove-Environment-Markers-syntax.patch
%endif
Patch4: 0004-Process-source-URLs-with-fragment-in-pre-push-hook.patch
Patch5: 0005-container-build-update-signing-intent-help-for-OSBS-.patch
-Patch6: 0006-Do-not-generate-pre-push-hook-script-in-some-cases.patch
-Patch7: 0007-More-robust-spec-file-presence-checking.patch
-Patch8: 0008-Update-to-spec-file-presence-checking.patch
-Patch9: 0009-Add-more-information-about-pre-push-hook.patch
-Patch10: 0010-pre-push-check-have-to-use-spectool-with-define.patch
-Patch11: 0011-A-HEAD-query-into-a-lookaside-cache.patch
-Patch12: 0012-pre-push-hook-script-contains-a-user-s-config.patch
-Patch13: 0013-Fix-unittests-for-clone-and-pre-push-hook-script.patch
-Patch14: 0014-import_srpm-allow-pre-generated-srpms.patch
-Patch15: 0015-Ignore-missing-spec-file-in-pre-push-hook.patch
-Patch16: 0016-Check-remote-file-with-correct-hash.patch
-Patch17: 0017-Allow-empty-commits-when-uses_rpmautospec.patch
-Patch18: 0018-Config-file-option-to-skip-the-hook-script-creation.patch
-Patch19: 0019-Pre-push-hook-won-t-check-private-branches.patch
-Patch20: 0020-Use-release-s-rpmdefines-in-unused-sources-check.patch
-Patch21: 0021-Do-not-require-sources-file-for-all-namespaces.patch
-Patch22: 0022-commit-command-fails-on-containers-namespace.patch
-Patch23: 0023-Split-git-credential-data-on-first-only.patch
-Patch24: 0024-Support-for-checking-exploded-sources-before-push.patch
-Patch25: 0025-Fix-flake8-complaints.patch
-Patch26: 0026-Prepare-the-lookaside-cache-code-for-retries.patch
-Patch27: 0027-Lookaside-cache-operations-retries.patch
-Patch28: 0028-Make-lookaside-cache-retries-configurable.patch
-Patch29: 0029-pkg-import-Don-t-delete-changelog-generated-by-rpmau.patch
-Patch30: 0030-pkg-import-Undo-rpmautospec-processing.patch
-Patch31: 0031-Unittests-for-Undo-rpmautospec-processing.patch
+Patch6: 0033-Update-docker-image-for-Jenkinks-tests.patch
+Patch7: 0006-Do-not-generate-pre-push-hook-script-in-some-cases.patch
+Patch8: 0007-More-robust-spec-file-presence-checking.patch
+Patch9: 0008-Update-to-spec-file-presence-checking.patch
+Patch10: 0009-Add-more-information-about-pre-push-hook.patch
+Patch11: 0010-pre-push-check-have-to-use-spectool-with-define.patch
+Patch12: 0011-A-HEAD-query-into-a-lookaside-cache.patch
+Patch13: 0012-pre-push-hook-script-contains-a-user-s-config.patch
+Patch14: 0013-Fix-unittests-for-clone-and-pre-push-hook-script.patch
+Patch15: 0014-import_srpm-allow-pre-generated-srpms.patch
+Patch16: 0015-Ignore-missing-spec-file-in-pre-push-hook.patch
+Patch17: 0016-Check-remote-file-with-correct-hash.patch
+Patch18: 0017-Allow-empty-commits-when-uses_rpmautospec.patch
+Patch19: 0018-Config-file-option-to-skip-the-hook-script-creation.patch
+Patch20: 0019-Pre-push-hook-won-t-check-private-branches.patch
+Patch21: 0020-Use-release-s-rpmdefines-in-unused-sources-check.patch
+Patch22: 0021-Do-not-require-sources-file-for-all-namespaces.patch
+Patch23: 0022-copr-build-passes-extra_args-to-copr-cli-command.patch
+Patch24: 0023-commit-command-fails-on-containers-namespace.patch
+Patch25: 0024-Split-git-credential-data-on-first-only.patch
+Patch26: 0025-Support-for-checking-exploded-sources-before-push.patch
+Patch27: 0026-Fix-flake8-complaints.patch
+Patch28: 0027-Prepare-the-lookaside-cache-code-for-retries.patch
+Patch29: 0028-Lookaside-cache-operations-retries.patch
+Patch30: 0029-Make-lookaside-cache-retries-configurable.patch
+Patch31: 0030-pkg-import-Don-t-delete-changelog-generated-by-rpmau.patch
+Patch32: 0031-pkg-import-Undo-rpmautospec-processing.patch
+Patch33: 0032-Unittests-for-Undo-rpmautospec-processing.patch
+Patch34: 0034-mockbuild-new-argument-extra-pkgs.patch
+Patch35: 0035-Add-option-to-mockbuild-use-default-resultdir-of-moc.patch
%description
Python library for interacting with rpm+git
@@ -281,6 +285,11 @@ example_cli_dir=$RPM_BUILD_ROOT%{_datadir}/%{name}/examples/cli
%changelog
+* Wed Jan 10 2024 Ondřej Nosek <onosek@redhat.com> - 1.66-15
+- Patch: Add option to mockbuild use default resultdir of mock (v3)
+- Patch: mockbuild`: new argument --extra-pkgs
+- Patch: `copr-build` passes extra_args to copr-cli command
+
* Mon Dec 11 2023 Miro Hrončok <mhroncok@redhat.com> - 1.66-14
- Actually add the patches:
- Patch: *pkg import: Don't delete changelog generated by `rpmautospec convert`
^ permalink raw reply related [flat|nested] 6+ messages in thread* [rpms/rpkg] 1.70-1: A few patches:
@ 2026-08-10 21:44
0 siblings, 0 replies; 6+ messages in thread
From: @ 2026-08-10 21:44 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/rpkg
Branch : 1.70-1
Commit : 30368f20cf85f80c2396e9ff0ec3b11837a67558
Author : Ondřej Nosek <onosek@redhat.com>
Date : 2024-12-10T02:24:17+00:00
Stats : +199/-1 in 4 file(s)
URL : https://src.fedoraproject.org/rpms/rpkg/c/30368f20cf85f80c2396e9ff0ec3b11837a67558?branch=1.70-1
Log:
A few patches:
- Patch: `chain-build`: correct the info message
- Patch: Fix regular expression for parsing Source lines
- Patch: Add draft builds support
Signed-off-by: Ondřej Nosek <onosek@redhat.com>
---
diff --git a/0006-Add-draft-builds-support.patch b/0006-Add-draft-builds-support.patch
new file mode 100644
index 0000000..9eb57fb
--- /dev/null
+++ b/0006-Add-draft-builds-support.patch
@@ -0,0 +1,98 @@
+From 53a12c6fea813598851af65390695a69c8f29b76 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Nosek?= <onosek@redhat.com>
+Date: Wed, 9 Oct 2024 03:24:51 +0200
+Subject: [PATCH 1/3] Add draft builds support
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Generally, it means adding `--draft` argument to the koji build
+command.
+
+JIRA: RHELCMP-14108
+
+Signed-off-by: Ondřej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 6 +++++-
+ pyrpkg/cli.py | 11 +++++++++--
+ 2 files changed, 14 insertions(+), 3 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index 573fc36..c3e1722 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -2456,7 +2456,7 @@ class Commands(object):
+
+ def build(self, skip_tag=False, scratch=False, background=False,
+ url=None, chain=None, arches=None, sets=False, nvr_check=True,
+- fail_fast=False, custom_user_metadata=None):
++ fail_fast=False, custom_user_metadata=None, draft=False):
+ """Initiate a build in build system
+
+ :param bool skip_tag: Skip the tag action after the build.
+@@ -2473,6 +2473,7 @@ class Commands(object):
+ will cause the entire build to fail if any subtask/architecture
+ build fails.
+ :param str custom_user_metadata: JSON string of custom metadata
++ :param bool draft: Perform a draft build. Default is False.
+ :return: task ID returned from Koji API ``build`` and ``chainBuild``.
+ :rtype: int
+ """
+@@ -2524,6 +2525,9 @@ class Commands(object):
+ if scratch:
+ opts['scratch'] = True
+ cmd.append('--scratch')
++ if draft:
++ opts['draft'] = True
++ cmd.append('--draft')
+ if background:
+ cmd.append('--background')
+ priority = 5 # magic koji number :/
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index 97c2904..bf2cfa1 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -548,9 +548,13 @@ class cliClient(object):
+ build_parser.add_argument(
+ '--skip-tag', action='store_true', default=False,
+ help='Do not attempt to tag package')
+- build_parser.add_argument(
++ build_type_group = build_parser.add_mutually_exclusive_group()
++ build_type_group.add_argument(
+ '--scratch', action='store_true', default=False,
+ help='Perform a scratch build')
++ build_type_group.add_argument(
++ '--draft', action='store_true', default=False,
++ help='Perform a draft build')
+ build_parser.add_argument(
+ '--srpm', nargs='?', const='CONSTRUCT',
+ help='Build from an srpm. If no srpm is provided with this option'
+@@ -2125,7 +2129,8 @@ class cliClient(object):
+ sets=sets,
+ nvr_check=nvr_check,
+ fail_fast=self.args.fail_fast,
+- custom_user_metadata=custom_user_metadata)
++ custom_user_metadata=custom_user_metadata,
++ draft=self.args.draft)
+
+ def chainbuild(self):
+ """Implement chain-build command"""
+@@ -2176,6 +2181,7 @@ class cliClient(object):
+ self.args.chain = urls
+ self.args.skip_tag = False
+ self.args.scratch = False
++ self.args.draft = False
+ return self.build(sets)
+
+ def clean(self):
+@@ -2936,6 +2942,7 @@ class cliClient(object):
+ # A scratch build is just a build with --scratch
+ self.args.scratch = True
+ self.args.skip_tag = False
++ self.args.draft = False
+ return self.build()
+
+ def sources(self):
+--
+2.47.1
+
diff --git a/0007-Fix-regular-expression-for-parsing-Source-lines.patch b/0007-Fix-regular-expression-for-parsing-Source-lines.patch
new file mode 100644
index 0000000..d6301c8
--- /dev/null
+++ b/0007-Fix-regular-expression-for-parsing-Source-lines.patch
@@ -0,0 +1,54 @@
+From 2a9507f1882bc6dbcf49f9dd39cbff451d41f1c9 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Nosek?= <onosek@redhat.com>
+Date: Mon, 14 Oct 2024 01:39:42 +0200
+Subject: [PATCH 2/3] Fix regular expression for parsing Source lines
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+When pushing changes to the dist-git repo, the `pre-push-check`
+didn't identify hidden files (.file) among 'SourceX|PatchX'
+definitions. The regular expression was taken from another part
+of the code and improved.
+
+Fixes: #721
+JIRA: RHELCMP-13881
+
+Signed-off-by: Ondřej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 5 +++--
+ pyrpkg/spec.py | 2 +-
+ 2 files changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index c3e1722..fd953b3 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -4575,8 +4575,9 @@ class Commands(object):
+ source_files = []
+ # extract source files from the spectool's output
+ for line in stdout.split('\n'):
+- file_location = re.sub(r'(?:Source|Patch)\d+\s*:\s*(\w+)', r'\1', line, re.IGNORECASE)
+- if file_location:
++ match = SpecFile.sourcefile_expression.match(line)
++ if match:
++ file_location = match.group('val')
+ # find out the format of the source file path. From URL use just the file name.
+ # We want to keep hierarchy of the files if possible
+ res = urllib.parse.urlparse(file_location)
+diff --git a/pyrpkg/spec.py b/pyrpkg/spec.py
+index 5400de3..28e3a27 100644
+--- a/pyrpkg/spec.py
++++ b/pyrpkg/spec.py
+@@ -15,7 +15,7 @@ from pyrpkg.errors import rpkgError
+ class SpecFile(object):
+ """Simple specfile parser that finds source file names"""
+ sourcefile_expression = re.compile(
+- r'^((source[0-9]*|patch[0-9]*)\s*:\s*(?P<val>.*))\s*$',
++ r'^(?:Source|Patch)\d*\s*:\s*(?P<val>[^\s]+)\s*$',
+ re.IGNORECASE)
+
+ def __init__(self, spec, rpmdefines):
+--
+2.47.1
+
diff --git a/0008-chain-build-correct-the-info-message.patch b/0008-chain-build-correct-the-info-message.patch
new file mode 100644
index 0000000..89119a7
--- /dev/null
+++ b/0008-chain-build-correct-the-info-message.patch
@@ -0,0 +1,38 @@
+From f3cbcdd3b08311755940cb23dae131bbed901f9c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Nosek?= <onosek@redhat.com>
+Date: Wed, 16 Oct 2024 02:57:52 +0200
+Subject: [PATCH 3/3] `chain-build`: correct the info message
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The info message was missing the whole 'chain' which says what
+components will be built. Originally, it didn't contain the last
+group of components. It was confusing for users.
+
+Fixes: https://pagure.io/fedpkg/issue/567
+JIRA: RHELCMP-14076
+
+Signed-off-by: Ondřej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index fd953b3..dfccad3 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -2596,8 +2596,8 @@ class Commands(object):
+ cmd.extend(' : '.join(
+ [' '.join(build_sets) for build_sets in chain]
+ ).split())
+- self.log.info('Chain building %s + %s for %s',
+- build_reference, chain[:-1], self.target)
++ self.log.info('Chain building %s, chain consists of %s, for %s',
++ build_reference, chain, self.target)
+ self.log.debug(
+ 'Building chain %s for %s with options %s and a priority '
+ 'of %s', chain, self.target, opts, priority)
+--
+2.47.1
+
diff --git a/rpkg.spec b/rpkg.spec
index c956f52..f15df43 100644
--- a/rpkg.spec
+++ b/rpkg.spec
@@ -1,6 +1,6 @@
Name: rpkg
Version: 1.67
-Release: 4%{?dist}
+Release: 5%{?dist}
Summary: Python library for interacting with rpm+git
# Automatically converted from old format: GPLv2+ and LGPLv2 - review is highly recommended.
@@ -47,6 +47,9 @@ Patch3: 0003-Remove-Environment-Markers-syntax.patch
%endif
Patch4: 0004-Fix-package-in-Pypi.patch
Patch5: 0005-Fixing-encoding-of-the-url-when-checking-lookaside.patch
+Patch6: 0006-Add-draft-builds-support.patch
+Patch7: 0007-Fix-regular-expression-for-parsing-Source-lines.patch
+Patch8: 0008-chain-build-correct-the-info-message.patch
%description
@@ -278,6 +281,11 @@ example_cli_dir=$RPM_BUILD_ROOT%{_datadir}/%{name}/examples/cli
%changelog
+* Tue Dec 10 2024 Ondřej Nosek <onosek@redhat.com> - 1.67-5
+- Patch: `chain-build`: correct the info message
+- Patch: Fix regular expression for parsing Source lines
+- Patch: Add draft builds support
+
* Mon Sep 16 2024 Ondřej Nosek <onosek@redhat.com> - 1.67-4
- Patch: Fixing encoding of the url when checking lookaside
- Patch: Fix package in Pypi
^ permalink raw reply related [flat|nested] 6+ messages in thread* [rpms/rpkg] 1.70-1: A few patches:
@ 2026-08-10 21:44
0 siblings, 0 replies; 6+ messages in thread
From: @ 2026-08-10 21:44 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/rpkg
Branch : 1.70-1
Commit : 4d309050b8a601efc3b290e6f29d3af78daa7228
Author : Ondřej Nosek <onosek@redhat.com>
Date : 2024-09-17T23:47:31+00:00
Stats : +153/-6 in 3 file(s)
URL : https://src.fedoraproject.org/rpms/rpkg/c/4d309050b8a601efc3b290e6f29d3af78daa7228?branch=1.70-1
Log:
A few patches:
- Patch: Fixing encoding of the url when checking lookaside
- Patch: Fix package in Pypi
Signed-off-by: Ondřej Nosek <onosek@redhat.com>
---
diff --git a/0004-Fix-package-in-Pypi.patch b/0004-Fix-package-in-Pypi.patch
new file mode 100644
index 0000000..e82222f
--- /dev/null
+++ b/0004-Fix-package-in-Pypi.patch
@@ -0,0 +1,48 @@
+From 69a62d90ccab505bdd95b9817415b93582541d6c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Nosek?= <onosek@redhat.com>
+Date: Mon, 8 Jul 2024 02:29:04 +0200
+Subject: [PATCH 1/2] Fix package in Pypi
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The pyrpkg module couldn't be imported.
+Flake8 doesn't need installation of all dependencies - quicker.
+
+Signed-off-by: Ondřej Nosek <onosek@redhat.com>
+---
+ pyproject.toml | 4 ++--
+ tox.ini | 2 ++
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/pyproject.toml b/pyproject.toml
+index 9d41225..dc4a285 100644
+--- a/pyproject.toml
++++ b/pyproject.toml
+@@ -83,5 +83,5 @@ include = [
+
+ [tool.hatch.build.targets.wheel]
+ packages = [
+- "dist/rpkg",
++ "pyrpkg",
+ ]
+diff --git a/tox.ini b/tox.ini
+index 3357e2d..2638246 100644
+--- a/tox.ini
++++ b/tox.ini
+@@ -34,10 +34,12 @@ sitepackages=true
+ sitepackages=true
+
+ [testenv:flake8]
++skip_install = True
+ deps = flake8
+ commands = python -m flake8 pyrpkg/ tests/
+
+ [testenv:flake8python2]
++skip_install = True
+ deps = flake8
+ commands = python -m flake8 pyrpkg/ tests/
+
+--
+2.46.0
+
diff --git a/0005-Fixing-encoding-of-the-url-when-checking-lookaside.patch b/0005-Fixing-encoding-of-the-url-when-checking-lookaside.patch
new file mode 100644
index 0000000..0801d00
--- /dev/null
+++ b/0005-Fixing-encoding-of-the-url-when-checking-lookaside.patch
@@ -0,0 +1,72 @@
+From 4a1ed7633aad84c8e3bd9e856b5d8d95136a739d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Nosek?= <onosek@redhat.com>
+Date: Thu, 12 Sep 2024 00:28:04 +0200
+Subject: [PATCH 2/2] Fixing encoding of the url when checking lookaside
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+In RHEL-7 (Python 2.7) encoding of the url was unicode. Curl's
+method 'setopt' expects utf-8.
+
+JIRA: RHELCMP-13939
+
+Signed-off-by: Ondřej Nosek <onosek@redhat.com>
+---
+ pyrpkg/lookaside.py | 4 +++-
+ tests/test_lookaside.py | 10 ++++++++--
+ 2 files changed, 11 insertions(+), 3 deletions(-)
+
+diff --git a/pyrpkg/lookaside.py b/pyrpkg/lookaside.py
+index 72109bf..5929fc9 100644
+--- a/pyrpkg/lookaside.py
++++ b/pyrpkg/lookaside.py
+@@ -167,7 +167,7 @@ class CGILookasideCache(object):
+ self.log.info("Downloading %s from %s", filename, self.download_url)
+ urled_file = urllib.parse.quote(filename)
+ url = self.get_download_url(name, urled_file, hash, hashtype, **kwargs)
+- if isinstance(url, six.text_type):
++ if six.PY2 and isinstance(url, six.text_type):
+ url = url.encode('utf-8')
+ self.log.debug("Full url: %s", url)
+
+@@ -215,6 +215,8 @@ class CGILookasideCache(object):
+
+ urled_file = urllib.parse.quote(filename)
+ url = self.get_download_url(name, urled_file, hash, hashtype or self.hashtype)
++ if six.PY2 and isinstance(url, six.text_type):
++ url = url.encode('utf-8')
+
+ c = pycurl.Curl()
+ c.setopt(pycurl.URL, url)
+diff --git a/tests/test_lookaside.py b/tests/test_lookaside.py
+index 12da113..6bace2e 100644
+--- a/tests/test_lookaside.py
++++ b/tests/test_lookaside.py
+@@ -112,7 +112,10 @@ class CGILookasideCacheTestCase(unittest.TestCase):
+ lc = CGILookasideCache('sha512', 'http://example.com', '_')
+ lc.download(name, filename, hash, outfile, hashtype='sha512')
+ self.assertEqual(curl.perform.call_count, 1)
+- self.assertEqual(curlopts[pycurl.URL].decode('utf-8'), full_url)
++ if six.PY2:
++ self.assertEqual(curlopts[pycurl.URL].decode('utf-8'), full_url)
++ else:
++ self.assertEqual(curlopts[pycurl.URL], full_url)
+ self.assertEqual(os.path.getmtime(outfile), 0)
+
+ with open(outfile) as f:
+@@ -167,7 +170,10 @@ class CGILookasideCacheTestCase(unittest.TestCase):
+ lc.download(name, filename, hash, outfile, hashtype='sha512',
+ branch=branch)
+ self.assertEqual(curl.perform.call_count, 1)
+- self.assertEqual(curlopts[pycurl.URL].decode('utf-8'), full_url)
++ if six.PY2:
++ self.assertEqual(curlopts[pycurl.URL].decode('utf-8'), full_url)
++ else:
++ self.assertEqual(curlopts[pycurl.URL], full_url)
+
+ @mock.patch('pyrpkg.lookaside.pycurl.Curl')
+ def test_download_corrupted(self, mock_curl):
+--
+2.46.0
+
diff --git a/rpkg.spec b/rpkg.spec
index df1f182..c956f52 100644
--- a/rpkg.spec
+++ b/rpkg.spec
@@ -1,6 +1,6 @@
Name: rpkg
Version: 1.67
-Release: 3%{?dist}
+Release: 4%{?dist}
Summary: Python library for interacting with rpm+git
# Automatically converted from old format: GPLv2+ and LGPLv2 - review is highly recommended.
@@ -23,6 +23,13 @@ Source0: https://pagure.io/releases/rpkg/%{name}-%{version}.tar.gz
%global with_python3 1
%endif
+# No support for setup.py since Python 3.12 (RHEL 10)
+%if 0%{?rhel} >= 10
+%global with_hatchling 1
+%else
+%global with_hatchling 0
+%endif
+
# Fix for bug 1579367
# Due to https://pagure.io/koji/issue/912, python[23]-koji package does not
@@ -38,6 +45,8 @@ Patch2: 0002-Remove-pytest-coverage-execution.patch
%if 0%{?with_python2}
Patch3: 0003-Remove-Environment-Markers-syntax.patch
%endif
+Patch4: 0004-Fix-package-in-Pypi.patch
+Patch5: 0005-Fixing-encoding-of-the-url-when-checking-lookaside.patch
%description
@@ -124,11 +133,17 @@ BuildRequires: python3-openidc-client
BuildRequires: python3-pycurl
BuildRequires: python3-six >= 1.9.0
BuildRequires: python3-requests
-BuildRequires: python3-setuptools
BuildRequires: python3-pytest
BuildRequires: python3-PyYAML
BuildRequires: rpmlint
BuildRequires: rpmdevtools
+%if 0%{?with_hatchling}
+BuildRequires: pyproject-rpm-macros
+BuildRequires: python3-hatchling
+BuildRequires: python3-pip
+%else
+BuildRequires: python3-setuptools
+%endif
Requires: mock
Requires: redhat-rpm-config
@@ -177,18 +192,18 @@ Common files for python2-%{name} and python3-%{name}.
%prep
%autosetup -p1
-# Removes section from setup.py that is relevant only for pip and
-# is not compatible with in RHEL-6 tools
-sed -i -n '/extras_require/,/}/!p' setup.py
-
%build
%if 0%{?with_python2}
%{__python2} setup.py build
%endif
%if 0%{?with_python3}
+%if 0%{?with_hatchling}
+%pyproject_wheel
+%else
%py3_build
%endif
+%endif
%install
@@ -197,8 +212,12 @@ sed -i -n '/extras_require/,/}/!p' setup.py
%endif
%if 0%{?with_python3}
+%if 0%{?with_hatchling}
+%pyproject_install
+%else
%py3_install
%endif
+%endif
# Create configuration directory to holding downstream clients config files
@@ -246,8 +265,12 @@ example_cli_dir=$RPM_BUILD_ROOT%{_datadir}/%{name}/examples/cli
%doc README.rst CHANGELOG.rst
%license COPYING COPYING-koji LGPL
%{python3_sitelib}/pyrpkg
+%if 0%{?with_hatchling}
+%{python3_sitelib}/%{name}-%{version}.dist-info
+%else
%{python3_sitelib}/%{name}-%{version}-py*.egg-info
%endif
+%endif
%files common
%{_datadir}/%{name}
@@ -255,6 +278,10 @@ example_cli_dir=$RPM_BUILD_ROOT%{_datadir}/%{name}/examples/cli
%changelog
+* Mon Sep 16 2024 Ondřej Nosek <onosek@redhat.com> - 1.67-4
+- Patch: Fixing encoding of the url when checking lookaside
+- Patch: Fix package in Pypi
+
* Fri Jul 19 2024 Fedora Release Engineering <releng@fedoraproject.org> - 1.67-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild
^ permalink raw reply related [flat|nested] 6+ messages in thread* [rpms/rpkg] 1.70-1: A few patches:
@ 2026-08-10 21:44
0 siblings, 0 replies; 6+ messages in thread
From: @ 2026-08-10 21:44 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/rpkg
Branch : 1.70-1
Commit : f8a7f14fbe3c2bd3229a3e023cf78e090f39dde6
Author : Ondřej Nosek <onosek@redhat.com>
Date : 2025-09-03T00:29:53+00:00
Stats : +309/-5 in 7 file(s)
URL : https://src.fedoraproject.org/rpms/rpkg/c/f8a7f14fbe3c2bd3229a3e023cf78e090f39dde6?branch=1.70-1
Log:
A few patches:
- Patch: `pre-push-check`: bogus error - file wasn't listed
- Patch: Fix mockbuild --srpm-mock specfile_path
- Patch: `patch`: Execute subprocess in text mode
- Patch: type: fix typo in requirements README.
- Patch: `install`: add rpmbuild arguments `--with` and `--without`
- Patch: `srpm`: man page generation fixed
Signed-off-by: Ondřej Nosek <onosek@redhat.com>
---
diff --git a/0002-pre-push-check-bogus-error-file-wasn-t-listed.patch b/0002-pre-push-check-bogus-error-file-wasn-t-listed.patch
new file mode 100644
index 0000000..cca0e36
--- /dev/null
+++ b/0002-pre-push-check-bogus-error-file-wasn-t-listed.patch
@@ -0,0 +1,39 @@
+From 3e766e09ab5403f01c92bb57f0bf4b7ed5cb8b10 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Nosek?= <onosek@redhat.com>
+Date: Tue, 8 Jul 2025 16:49:57 +0200
+Subject: [PATCH 08/13] `pre-push-check`: bogus error - file wasn't listed
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+During the check before a push operation, a file was falsely marked
+as not listed in the specfile and the check failed.
+The marked patch was named "./0001-some-fix.patch" which is rarely
+seen in the specfile. As a fix, file names are normalized and thus
+'./' prefix is removed.
+
+Fixes: #747
+JIRA: RHELCMP-14651
+
+Signed-off-by: Ondřej Nosek <onosek@redhat.com>
+---
+ pyrpkg/__init__.py | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index bf8c3e2..8b2ec89 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -4617,7 +4617,8 @@ class Commands(object):
+ elif res.scheme and res.netloc:
+ source_files.append(os.path.basename(res.path))
+ else:
+- source_files.append(file_location)
++ # file path could rarely be in format './0001-my-fix.patch' - normalize it
++ source_files.append(os.path.normpath(file_location))
+
+ if not len(source_files):
+ self.log.warning('No source files found in the specfile \'{0}\'. '
+--
+2.51.0
+
diff --git a/0003-Fix-mockbuild-srpm-mock-specfile_path.patch b/0003-Fix-mockbuild-srpm-mock-specfile_path.patch
new file mode 100644
index 0000000..db8fb72
--- /dev/null
+++ b/0003-Fix-mockbuild-srpm-mock-specfile_path.patch
@@ -0,0 +1,27 @@
+From 266ce739a075c6cee1a0221fd8faf0fc823daf37 Mon Sep 17 00:00:00 2001
+From: Tony Wang <wngtk@outlook.com>
+Date: Wed, 2 Jul 2025 21:51:01 +0800
+Subject: [PATCH 09/13] Fix mockbuild --srpm-mock specfile_path
+
+Signed-off-by: Tony Wang <wngtk@outlook.com>
+---
+ pyrpkg/__init__.py | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index 8b2ec89..ad5ee31 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -3207,7 +3207,8 @@ class Commands(object):
+ if shell:
+ cmd.append('--shell')
+ elif srpm_mock:
+- cmd += ['--buildsrpm', '--sources', self.layout.sourcedir, '--spec', self.spec]
++ specfile_path = os.path.join(self.layout.specdir, self.spec)
++ cmd += ['--buildsrpm', '--sources', self.layout.sourcedir, '--spec', specfile_path]
+ else:
+ cmd += ['--rebuild', self.srpmname]
+
+--
+2.51.0
+
diff --git a/0004-patch-Execute-subprocess-in-text-mode.patch b/0004-patch-Execute-subprocess-in-text-mode.patch
new file mode 100644
index 0000000..76696c0
--- /dev/null
+++ b/0004-patch-Execute-subprocess-in-text-mode.patch
@@ -0,0 +1,30 @@
+From f828c71a8aa080d8c95aea21396f5b799f610af4 Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 12 Jun 2025 05:49:15 -0400
+Subject: [PATCH 10/13] `patch`: Execute subprocess in text mode
+
+This prevents `fedpkg patch suffix` aborting with the error message
+"Could not execute patch: write() argument must be str, not bytes".
+
+Merges: https://pagure.io/rpkg/pull-request/744
+
+Signed-off-by: FeRD (Frank Dana) <ferdnyc@gmail.com>
+---
+ pyrpkg/__init__.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index ad5ee31..a0fbb6d 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -2178,6 +2178,7 @@ class Commands(object):
+ self.log.debug('Running %s', ' '.join(cmd))
+ (output, errors) = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
++ universal_newlines=True,
+ cwd=self.path).communicate()
+ except Exception as e:
+ raise rpkgError('Error running gendiff: %s' % e)
+--
+2.51.0
+
diff --git a/0005-type-fix-typo-in-requirements-README.patch b/0005-type-fix-typo-in-requirements-README.patch
new file mode 100644
index 0000000..a2bb057
--- /dev/null
+++ b/0005-type-fix-typo-in-requirements-README.patch
@@ -0,0 +1,24 @@
+From f487f5cd260ee4029c7545ffb7126997ded75600 Mon Sep 17 00:00:00 2001
+From: "Guillermo N." <gleiro@redhat.com>
+Date: Fri, 18 Jul 2025 20:47:37 +0200
+Subject: [PATCH 11/13] type: fix typo in requirements README.
+
+Signed-off-by: Guillermo N. <gleiro@redhat.com>
+---
+ requirements/README.rst | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/requirements/README.rst b/requirements/README.rst
+index 4e94bca..166c182 100644
+--- a/requirements/README.rst
++++ b/requirements/README.rst
+@@ -3,4 +3,5 @@ Requirements
+
+ * pypi.txt: contains Python packages that can be installed from PyPI via
+ ``pip``. Some of required packages are not available in PyPI as of writing
+- this README file. They has to be installed from package manager too.
++ this README file. They have to be installed from package manager too.
++
+--
+2.51.0
+
diff --git a/0006-install-add-rpmbuild-arguments-with-and-without.patch b/0006-install-add-rpmbuild-arguments-with-and-without.patch
new file mode 100644
index 0000000..d236711
--- /dev/null
+++ b/0006-install-add-rpmbuild-arguments-with-and-without.patch
@@ -0,0 +1,132 @@
+From 04338101aa86d383e993358f349872fd0056168a Mon Sep 17 00:00:00 2001
+From: "Guillermo N." <gleiro@redhat.com>
+Date: Fri, 18 Jul 2025 20:55:19 +0200
+Subject: [PATCH 12/13] `install`: add rpmbuild arguments `--with` and
+ `--without`
+
+This commit introduces two new command-line flags for on-the-fly
+modification of `rpmbuild` arguments directly from the `install` subcommand:
+ - `--with <bcond>`: Appends a build condition (bcond) to `rpmbuild`
+ arguments.
+ - `--without <bcond>`: Removes or disables a build condition (bcond) from
+ `rpmbuild` arguments.
+
+These flags address the feature requested on `fedpkg` downstream repo.
+
+Resolves: https://pagure.io/fedpkg/issue/541
+
+Signed-off-by: Guillermo N. <gleiro@redhat.com>
+---
+ pyrpkg/__init__.py | 8 +++++++-
+ pyrpkg/cli.py | 21 ++++++++++++++++++++-
+ tests/test_cli.py | 2 ++
+ 3 files changed, 29 insertions(+), 2 deletions(-)
+
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index a0fbb6d..41a84c2 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -2763,7 +2763,7 @@ class Commands(object):
+ self.kojisession.uploadWrapper(file, path, name=name, callback=callback)
+
+ def install(self, arch=None, short=False, builddir=None, nocheck=False,
+- buildrootdir=None, define=None, extra_args=None):
++ buildrootdir=None, define=None, extra_args=None, installargs=None):
+ """Run ``rpmbuild -bi``
+
+ optionally for a specific arch, short-circuit it,
+@@ -2771,6 +2771,8 @@ class Commands(object):
+
+ Logs the output and returns nothing
+
++ :param list installargs: Modifiers for rpmbuild defaults (similar to
++ 'extra_args' but derived from different command-line parsing).
+ :param str arch: specify a specific arch.
+ :param list define: specify a list of rpmbuild macros.
+ :param bool short: short-circuit it.
+@@ -2787,6 +2789,8 @@ class Commands(object):
+ # setup the rpm command
+ cmd = ['rpmbuild']
+ cmd.extend(self.rpmdefines)
++ if installargs:
++ cmd.extend(installargs)
+ if builddir:
+ # Tack on a new builddir to the end of the defines
+ cmd.extend(["--define", "_builddir %s" % os.path.abspath(builddir)])
+@@ -2905,6 +2909,8 @@ class Commands(object):
+ written into current working directory and in format
+ `.build-{version}-{release}.log`.
+
++ :param list localargs: Modifiers for rpmbuild defaults (similar to
++ 'extra_args' but derived from different command-line parsing).
+ :param str arch: to optionally build for a specific arch.
+ :param list define: optional list of rpmbuild macros.
+ :param str hashtype: an alternative algorithm used for payload file
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index 8f5ae40..10c6fd4 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -1030,6 +1030,13 @@ class cliClient(object):
+ '--nocheck',
+ action='store_true',
+ help='nocheck install')
++ # Pass --with/without options to rpmbuild
++ install_parser.add_argument(
++ '--with', help='Enable configure option (bcond) for the build',
++ dest='bcond_with', action='append')
++ install_parser.add_argument(
++ '--without', help='Disable configure option (bcond) for the build',
++ dest='bcond_without', action='append')
+ install_parser.set_defaults(command=self.install, default=False)
+
+ def register_lint(self):
+@@ -2449,13 +2456,25 @@ class cliClient(object):
+
+ def install(self):
+ self.sources()
++
++ installargs = []
++
++ if self.args.bcond_with:
++ for arg in self.args.bcond_with:
++ installargs.extend(['--with', arg])
++
++ if self.args.bcond_without:
++ for arg in self.args.bcond_without:
++ installargs.extend(['--without', arg])
++
+ self.cmd.install(builddir=self.args.builddir,
+ arch=self.args.arch,
+ define=self.args.define,
+ extra_args=self.extra_args,
+ short=self.args.short_circuit,
+ nocheck=self.args.nocheck,
+- buildrootdir=self.args.buildrootdir,)
++ buildrootdir=self.args.buildrootdir,
++ installargs=installargs)
+
+ def lint(self):
+ self.cmd.lint(self.args.info, self.args.rpmlintconf)
+diff --git a/tests/test_cli.py b/tests/test_cli.py
+index ae532b0..f734c43 100644
+--- a/tests/test_cli.py
++++ b/tests/test_cli.py
+@@ -865,6 +865,7 @@ class TestInstall(CliTestCase):
+ cli_cmd = [
+ 'rpkg', '--path', self.cloned_repo_path, '--release', 'rhel-6',
+ '-q', 'install', '--nocheck', '--arch', 'i686',
++ '--with', 'a', '--without', 'b',
+ '--builddir', builddir, '--buildrootdir', buildrootdir
+ ]
+
+@@ -874,6 +875,7 @@ class TestInstall(CliTestCase):
+
+ spec = os.path.join(cli.cmd.path, cli.cmd.spec)
+ rpmbuild = ['rpmbuild'] + cli.cmd.rpmdefines + [
++ '--with', 'a', '--without', 'b',
+ '--define', '_builddir %s' % builddir, '--target', 'i686',
+ '--nocheck', '--quiet',
+ '--define', '_buildrootdir %s' % buildrootdir,
+--
+2.51.0
+
diff --git a/0007-srpm-man-page-generation-fixed.patch b/0007-srpm-man-page-generation-fixed.patch
new file mode 100644
index 0000000..d35197a
--- /dev/null
+++ b/0007-srpm-man-page-generation-fixed.patch
@@ -0,0 +1,37 @@
+From e8d209e9d66b13ad6eef5e84f7620781b0bbb6d8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Nosek?= <onosek@redhat.com>
+Date: Fri, 29 Aug 2025 01:07:49 +0200
+Subject: [PATCH 13/13] `srpm`: man page generation fixed
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Usage wasn't generated properly for this command. The usage was
+overridden with fixed text instead of automatic generation.
+
+Relates: #751
+
+Signed-off-by: Ondřej Nosek <onosek@redhat.com>
+---
+ pyrpkg/cli.py | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index 10c6fd4..9fbb166 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -1548,9 +1548,10 @@ class cliClient(object):
+ srpm_parser = self.subparsers.add_parser(
+ 'srpm', help='Create a source rpm',
+ parents=[self.rpm_parser_common],
+- usage='Create a source rpm',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.dedent("""
++ Create a source rpm
++
+ This command wraps "rpmbuild -bs", roughly equivalent to:
+
+ rpmbuild -bs mypackage.spec \\
+--
+2.51.0
+
diff --git a/rpkg.spec b/rpkg.spec
index 44d19cc..70e68de 100644
--- a/rpkg.spec
+++ b/rpkg.spec
@@ -1,6 +1,6 @@
Name: rpkg
Version: 1.68
-Release: 5%{?dist}
+Release: 6%{?dist}
Summary: Python library for interacting with rpm+git
# Automatically converted from old format: GPLv2+ and LGPLv2 - review is highly recommended.
@@ -25,10 +25,12 @@ Source0: https://pagure.io/releases/rpkg/%{name}-%{version}.tar.gz
# No support for setup.py since Python 3.12 (RHEL 10)
# hatchling is supported in >Python 3.6 releases (RHEL 8)
-%if 0%{?rhel} >= 9 || 0%{?fedora} >= 41
-%global with_hatchling 1
-%else
+%if 0%{?rhel} && 0%{?rhel} <= 8
%global with_hatchling 0
+%{echo:--> with_hatchling unset, 0%{?rhel} %{?fedora}}
+%else
+%global with_hatchling 1
+%{echo:--> with_hatchling set, 0%{?rhel} %{?fedora}}
%endif
@@ -46,7 +48,12 @@ Patch0: remove-koji-and-rpm-py-installer-from-requires.patch
%if 0%{?with_python2}
Patch1: 0001-Remove-Environment-Markers-syntax.patch
%endif
-
+Patch2: 0002-pre-push-check-bogus-error-file-wasn-t-listed.patch
+Patch3: 0003-Fix-mockbuild-srpm-mock-specfile_path.patch
+Patch4: 0004-patch-Execute-subprocess-in-text-mode.patch
+Patch5: 0005-type-fix-typo-in-requirements-README.patch
+Patch6: 0006-install-add-rpmbuild-arguments-with-and-without.patch
+Patch7: 0007-srpm-man-page-generation-fixed.patch
%description
Python library for interacting with rpm+git
@@ -277,6 +284,14 @@ example_cli_dir=$RPM_BUILD_ROOT%{_datadir}/%{name}/examples/cli
%changelog
+* Tue Sep 02 2025 Ondřej Nosek <onosek@redhat.com> - 1.68-6
+- Patch: `pre-push-check`: bogus error - file wasn't listed
+- Patch: Fix mockbuild --srpm-mock specfile_path
+- Patch: `patch`: Execute subprocess in text mode
+- Patch: type: fix typo in requirements README.
+- Patch: `install`: add rpmbuild arguments `--with` and `--without`
+- Patch: `srpm`: man page generation fixed
+
* Fri Aug 15 2025 Python Maint <python-maint@redhat.com> - 1.68-5
- Rebuilt for Python 3.14.0rc2 bytecode
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-08-10 21:44 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-10 21:44 [rpms/rpkg] 1.70-1: A few patches:
2026-08-10 21:44
2026-08-10 21:44
2026-08-10 21:44
2026-08-10 21:44
2026-08-10 21:44
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox