From 19aeef4e0d6169fb7f566fc0ff3367a4ea5ea5fe Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jan 27 2026 22:52:50 +0000 Subject: [PATCH 1/2] catch nvra dups on promote Fixes https://pagure.io/koji/issue/4530 --- diff --git a/kojihub/kojihub.py b/kojihub/kojihub.py index 4c7956c..c341dae 100644 --- a/kojihub/kojihub.py +++ b/kojihub/kojihub.py @@ -10813,6 +10813,16 @@ def _promote_build(build, force=False): f"Target build exists: {old_build['nvr']}(#{old_build['id']})" )) + # check for nvra conflicts + for rpminfo in list_rpms(buildID=binfo['id']): + _rinfo = dslice(rpminfo, ('name', 'version', 'release', 'arch')) + dup = get_rpm(_rinfo) + if dup and (not dup['draft'] and not dup['external_repo_id']): + nvra = "%(name)s-%(version)s-%(release)s.%(arch)s" % dup + raise koji.GenericError(err_fmt.format( + f"NVRA conflict: {nvra} (#{dup['id']})" + )) + # policy checks policy_data = { 'build': binfo['id'], diff --git a/tests/test_hub/test_promote_build.py b/tests/test_hub/test_promote_build.py index 2405de8..672f7d5 100644 --- a/tests/test_hub/test_promote_build.py +++ b/tests/test_hub/test_promote_build.py @@ -41,6 +41,8 @@ class TestPromoteBuild(unittest.TestCase): self.os_symlink = mock.patch('os.symlink').start() self.list_tags = mock.patch('kojihub.kojihub.list_tags', return_value=[{'id': 101}]).start() + self.list_rpms = mock.patch('kojihub.kojihub.list_rpms', return_value=[]).start() + self.get_rpm = mock.patch('kojihub.kojihub.get_rpm', return_value=None).start() self.set_tag_update = mock.patch('kojihub.kojihub.set_tag_update').start() self._now = datetime.datetime.now() self._datetime = mock.patch('kojihub.kojihub.datetime.datetime').start() @@ -178,6 +180,28 @@ class TestPromoteBuild(unittest.TestCase): 'release': 'tgtrel' }, strict=False) + def test_promote_build_nvra_exists(self): + self.get_build.side_effect = [ + self.draft_build, + None, + ] + + rinfo = { + 'name': 'N', 'version': 'V', 'release': 'R', 'arch': 'A', + 'draft': False, 'external_repo_id': 0, + 'id': 1234, + } + self.list_rpms.return_value = [rinfo.copy()] + self.get_rpm.return_value = rinfo.copy() + + with self.assertRaises(koji.GenericError) as cm: + self.exports.promoteBuild('a-regular-build') + self.assertEqual( + str(cm.exception), + "Cannot promote build testnvr. Reason: NVRA conflict: N-V-R.A (#1234)" + ) + self.assertEqual(len(self.updates), 0) + class TestPromoteBuildFiles(unittest.TestCase): # these tests use a tempdir @@ -219,6 +243,8 @@ class TestPromoteBuildFiles(unittest.TestCase): return_value={'id': 1, 'name': 'DEFAULT'}).start() self.list_tags = mock.patch('kojihub.kojihub.list_tags', return_value=[{'id': 101}]).start() + self.list_rpms = mock.patch('kojihub.kojihub.list_rpms', return_value=[]).start() + self.get_rpm = mock.patch('kojihub.kojihub.get_rpm', return_value=None).start() self.set_tag_update = mock.patch('kojihub.kojihub.set_tag_update').start() self._now = datetime.datetime.now() self._datetime = mock.patch('kojihub.kojihub.datetime.datetime').start() From 73d92a872651171ce0e7c7c2ab62f412f5361e1a Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jan 27 2026 22:52:50 +0000 Subject: [PATCH 2/2] drop redundant parentheses --- diff --git a/kojihub/kojihub.py b/kojihub/kojihub.py index c341dae..b0a3d5a 100644 --- a/kojihub/kojihub.py +++ b/kojihub/kojihub.py @@ -10817,7 +10817,7 @@ def _promote_build(build, force=False): for rpminfo in list_rpms(buildID=binfo['id']): _rinfo = dslice(rpminfo, ('name', 'version', 'release', 'arch')) dup = get_rpm(_rinfo) - if dup and (not dup['draft'] and not dup['external_repo_id']): + if dup and not dup['draft'] and not dup['external_repo_id']: nvra = "%(name)s-%(version)s-%(release)s.%(arch)s" % dup raise koji.GenericError(err_fmt.format( f"NVRA conflict: {nvra} (#{dup['id']})"