proj/pkgcore/pkgdev:main commit in: /, tests/scripts/, src/pkgdev/scripts/

"Arthur Zamarin" <[email protected]>
Newsgroups gmane.linux.gentoo.cvs
Message-ID <1786821294.efc3d5cd5028bba3d07fb63786fb136deaad98ae.arthurzam@gentoo>
commit:     efc3d5cd5028bba3d07fb63786fb136deaad98ae
Author:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
AuthorDate: Sat Aug 15 19:14:54 2026 +0000
Commit:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Sat Aug 15 19:14:54 2026 +0000
URL:        https://gitweb.gentoo.org/proj/pkgcore/pkgdev.git/commit/?id=efc3d5cd

bugs: expand --filter-stablereqs across every package in a target

Signed-off-by: Arthur Zamarin <arthurzam <AT> gentoo.org>

 NEWS.rst                          |  3 +++
 src/pkgdev/scripts/pkgdev_bugs.py | 50 ++++++++++++++++++++++++++-------------
 tests/scripts/test_pkgdev_bugs.py | 50 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 16 deletions(-)

diff --git a/NEWS.rst b/NEWS.rst
index d338b23..2bda22f 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -16,6 +16,9 @@ pkgdev 0.2.18 (unreleased)
 - bugs: a bugzilla error while filing now names the bug and packages being
   filed or modified (Arthur Zamarin, #229)
 
+- bugs: fix ``--filter-stablereqs`` collapsing a target which matches several
+  packages down to a single one (Arthur Zamarin)
+
 pkgdev 0.2.17 (2026-08-14)
 --------------------------
 

diff --git a/src/pkgdev/scripts/pkgdev_bugs.py b/src/pkgdev/scripts/pkgdev_bugs.py
index 86fda45..cc649c5 100644
--- a/src/pkgdev/scripts/pkgdev_bugs.py
+++ b/src/pkgdev/scripts/pkgdev_bugs.py
@@ -133,6 +133,9 @@ bugs = ArgumentParser(
 
             # ... limited to those with an active StableRequest result
             pkgdev bugs --find-by-maintainer [email protected] --filter-stablereqs
+
+            # file stablereq bugs for all packages inside category "dev-libs" with an active StableRequest
+            pkgdev bugs --filter-stablereqs "dev-libs/*"
     """,
 )
 BugzillaApiKey.mangle_argparser(bugs)
@@ -676,6 +679,21 @@ class DependencyGraph:
                             )
                 yield from results.items()
 
+    def _stablereqs(self, pkgset) -> list[tuple[atom, list[package]]]:
+        """The stablereq of every package in the set, as a target of its own."""
+        per_pkg: dict[str, list[package]] = defaultdict(list)
+        for pkg in pkgset:
+            per_pkg[pkg.key].append(pkg)
+
+        found = []
+        # the check reads the versions of a single package, so feed one at a time
+        for pkgs in per_pkg.values():
+            for res in self.stablereq_check.feed(sorted(pkgs)):
+                if isinstance(res, stablereq.StableRequest):
+                    found.append((atom(f"={res.category}/{res.package}-{res.version}"), pkgs))
+                    break
+        return found
+
     def load_targets(self, targets: list[tuple[str, object, frozenset[str]]]):
         result = []
         search_repo = self.options.search_repo
@@ -684,23 +702,23 @@ class DependencyGraph:
             try:
                 pkgset = search_repo.match(target)
                 if self.options.filter_stablereqs:
-                    for res in self.stablereq_check.feed(sorted(pkgset)):
-                        if isinstance(res, stablereq.StableRequest):
-                            target = atom(f"={res.category}/{res.package}-{res.version}")
-                            break
-                    else:  # no stablereq
+                    # a target may match many packages, each with its own stablereq
+                    found = self._stablereqs(pkgset)
+                else:
+                    found = [(target, pkgset)]
+
+                for restrict, candidates in found:
+                    if masked.match(restrict):
+                        self.err.write(
+                            self.err.fg("yellow"),
+                            f"Target {restrict} is masked, skipping",
+                            self.err.reset,
+                        )
                         continue
-                if masked.match(target):
-                    self.err.write(
-                        self.err.fg("yellow"),
-                        f"Target {target} is masked, skipping",
-                        self.err.reset,
-                    )
-                    continue
-                match = self.find_best_match([target], pkgset, False)
-                result.append(match)
-                if arches:
-                    self.target_arches[match] = arches
+                    match = self.find_best_match([restrict], candidates, False)
+                    result.append(match)
+                    if arches:
+                        self.target_arches[match] = arches
             except (ValueError, IndexError):
                 bugs.error(f"Restriction {target} has no match in repository", status=3)
         self.targets = tuple(result)

diff --git a/tests/scripts/test_pkgdev_bugs.py b/tests/scripts/test_pkgdev_bugs.py
index e560ab8..6a51f4d 100644
--- a/tests/scripts/test_pkgdev_bugs.py
+++ b/tests/scripts/test_pkgdev_bugs.py
@@ -6,6 +6,7 @@ from types import SimpleNamespace
 import pytest
 from pkgcore.bugzilla import BugCategory, BugzillaError
 from pkgcore.ebuild.atom import atom
+from pkgcore.util import parserestrict
 
 from pkgdev.scripts import pkgdev_bugs as bugs
 
@@ -598,3 +599,52 @@ class TestFilingErrorContext:
         with pytest.raises(BugzillaError) as excinfo:
             node.file_bug(bugzilla_cassette.client(api_key="API"), frozenset(), (), None)
         assert "adding dependencies to bug 200" in str(excinfo.value)
+
+
+class TestFilterStablereqs:
+    """A target matching many packages has a stablereq per package, not one."""
+
+    def mk_stablereq_graph(self, repo, wanted):
+        graph = mk_graph(repo)
+        graph.options.filter_stablereqs = True
+        fed = []
+
+        class Check:
+            def feed(self, pkgs):
+                fed.append(tuple({pkg.key for pkg in pkgs}))
+                for pkg in reversed(pkgs):
+                    if pkg.versioned_atom.cpvstr in wanted:
+                        yield bugs.stablereq.StableRequest(
+                            slot=pkg.slot, keywords=pkg.keywords, age=40, pkg=pkg
+                        )
+
+        graph.stablereq_check = Check()
+        return graph, fed
+
+    def test_every_package_in_the_target_is_checked(self, repo):
+        for name in ("a", "b", "c"):
+            repo.create_ebuild(f"cat/{name}-1", KEYWORDS=["amd64"])
+            repo.create_ebuild(f"cat/{name}-2", KEYWORDS=["~amd64"])
+        repo.sync()
+        graph, fed = self.mk_stablereq_graph(repo, {"cat/a-2", "cat/c-2"})
+
+        graph.load_targets([(None, parserestrict.parse_match("cat/*"), frozenset())])
+
+        assert sorted(str(pkg.versioned_atom) for pkg in graph.targets) == ["=cat/a-2", "=cat/c-2"]
+        # one package per feed, as the check reads a single package's versions
+        assert fed and all(len(keys) == 1 for keys in fed)
+
+    def test_target_without_any_stablereq_is_dropped(self, repo):
+        repo.create_ebuild("cat/a-1", KEYWORDS=["amd64"])
+        repo.sync()
+        graph, _ = self.mk_stablereq_graph(repo, set())
+        graph.load_targets([(None, parserestrict.parse_match("cat/*"), frozenset())])
+        assert graph.targets == ()
+
+    def test_single_package_target_is_unchanged(self, repo):
+        repo.create_ebuild("cat/a-1", KEYWORDS=["amd64"])
+        repo.create_ebuild("cat/a-2", KEYWORDS=["~amd64"])
+        repo.sync()
+        graph, _ = self.mk_stablereq_graph(repo, {"cat/a-2"})
+        graph.load_targets([(None, parserestrict.parse_match("cat/a"), frozenset())])
+        assert [str(pkg.versioned_atom) for pkg in graph.targets] == ["=cat/a-2"]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.