commit:     0a0b89f1a3fc6c549f68c76bd3c031a8a1b5d902
Author:     Jethro Donaldson <devel <AT> jro <DOT> nz>
AuthorDate: Thu Dec  4 09:49:03 2025 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Mon Feb 16 06:54:16 2026 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=0a0b89f1

config: add usepkg-exclude and usepkg-include to repos.conf

Allow configuration in repos.conf of specific packages which are to
be satisfied (or not) using binary packages. These attributes appear
under the repository section and are specific to that repository,
with behaviour otherwise identical to the command line arguments of
the same name.

Where atoms specified with usepkg-exclude or usepkg-include in
repos.conf match those for the opposing command line options then
emit a warning and override the repos.conf atoms.

Signed-off-by: Jethro Donaldson <devel <AT> jro.nz>
Part-of: https://github.com/gentoo/portage/pull/1527
Signed-off-by: Sam James <sam <AT> gentoo.org>

 lib/_emerge/actions.py                             |  42 ++
 lib/_emerge/depgraph.py                            |  12 +-
 lib/_emerge/main.py                                |  30 +-
 lib/portage/repository/config.py                   |  70 +++
 lib/portage/tests/resolver/ResolverPlayground.py   |  21 +-
 .../tests/resolver/test_binpackage_selection.py    | 541 +++++++++++++++++++++
 man/emerge.1                                       |   3 +-
 man/portage.5                                      |  14 +
 8 files changed, 690 insertions(+), 43 deletions(-)

diff --git a/lib/_emerge/actions.py b/lib/_emerge/actions.py
index 472feb135f..1838582ff6 100644
--- a/lib/_emerge/actions.py
+++ b/lib/_emerge/actions.py
@@ -2851,6 +2851,48 @@ def binpkg_selection_config(opts, settings):
                 "\n    %s\n" % ("\n    ".join(usepkg_include.getAtoms()))
             )
             usepkg_include.clear()
+        for repo in settings.repositories:
+            if not repo.usepkg_exclude.isEmpty():
+                writemsg(
+                    "\n!!! The following usepkg-exclude atoms for [%s] are "
+                    "ignored due to use of --nobindeps:\n"
+                    "\n    %s\n"
+                    % (repo.name, "\n    
".join(repo.usepkg_exclude.getAtoms()))
+                )
+                repo.usepkg_exclude.clear()
+            if not repo.usepkg_include.isEmpty():
+                writemsg(
+                    "\n!!! The following usepkg-include atoms for [%s] are "
+                    "ignored due to use of --nobindeps:\n"
+                    "\n    %s\n"
+                    % (repo.name, "\n    
".join(repo.usepkg_include.getAtoms()))
+                )
+                repo.usepkg_include.clear()
+
+    # --usepkg-exclude and --usepkg-include override repos.conf
+    for repo in settings.repositories:
+        conflicted_exclude = repo.usepkg_exclude.getAtoms().intersection(
+            usepkg_include.getAtoms()
+        )
+        if conflicted_exclude:
+            writemsg(
+                "\n!!! The following usepkg-exclude atoms for [%s] have "
+                "been overridden by the --usepkg-include option:\n"
+                "\n    %s\n" % (repo.name, "\n    ".join(conflicted_exclude))
+            )
+            for a in conflicted_exclude:
+                repo.usepkg_exclude.remove(a)
+        conflicted_include = repo.usepkg_include.getAtoms().intersection(
+            usepkg_exclude.getAtoms()
+        )
+        if conflicted_include:
+            writemsg(
+                "\n!!! The following usepkg-include atoms for [%s] have "
+                "been overridden by the --usepkg-exclude option:\n"
+                "\n    %s\n" % (repo.name, "\n    ".join(conflicted_include))
+            )
+            for a in conflicted_include:
+                repo.usepkg_include.remove(a)
 
     # --getbinpkg-include and --getbinpkg-exclude may not overlap
     conflicted_atoms = getbinpkg_exclude.getAtoms().intersection(

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 17c5e54900..7ac07ad5e4 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -197,9 +197,9 @@ class _frozen_depgraph_config:
         atoms = " ".join(myopts.get("--reinstall-atoms", [])).split()
         self.reinstall_atoms = WildcardPackageSet(atoms)
         atoms = " ".join(myopts.get("--usepkg-exclude", [])).split()
-        self.usepkg_exclude = WildcardPackageSet(atoms)
+        self.usepkg_exclude = WildcardPackageSet(atoms, allow_repo=True)
         atoms = " ".join(myopts.get("--usepkg-include", [])).split()
-        self.usepkg_include = WildcardPackageSet(atoms)
+        self.usepkg_include = WildcardPackageSet(atoms, allow_repo=True)
         atoms = " ".join(myopts.get("--useoldpkg-atoms", [])).split()
         self.useoldpkg_atoms = WildcardPackageSet(atoms)
         atoms = " ".join(myopts.get("--rebuild-exclude", [])).split()
@@ -207,6 +207,14 @@ class _frozen_depgraph_config:
         atoms = " ".join(myopts.get("--rebuild-ignore", [])).split()
         self.rebuild_ignore = WildcardPackageSet(atoms)
 
+        for repo in settings.repositories:
+            self.usepkg_exclude.update(
+                a + _repo_separator + repo.name for a in 
repo.usepkg_exclude.getAtoms()
+            )
+            self.usepkg_include.update(
+                a + _repo_separator + repo.name for a in 
repo.usepkg_include.getAtoms()
+            )
+
         self.rebuild_if_new_rev = "--rebuild-if-new-rev" in myopts
         self.rebuild_if_new_ver = "--rebuild-if-new-ver" in myopts
         self.rebuild_if_unbuilt = "--rebuild-if-unbuilt" in myopts

diff --git a/lib/_emerge/main.py b/lib/_emerge/main.py
index 5eef25193e..f176e40e44 100644
--- a/lib/_emerge/main.py
+++ b/lib/_emerge/main.py
@@ -11,6 +11,7 @@ import sys
 import portage
 
 from portage import os
+from portage.repository.config import _find_bad_atoms
 from portage.sync import _SUBMODULE_PATH_MAP
 
 from typing import Optional
@@ -281,35 +282,6 @@ def insert_optional_args(args):
     return new_args
 
 
-def _find_bad_atoms(atoms, less_strict=False):
-    """
-    Declares all atoms as invalid that have an operator,
-    a use dependency, a blocker or a repo spec.
-    It accepts atoms with wildcards.
-    In less_strict mode it accepts operators and repo specs.
-    """
-    from _emerge.is_valid_package_atom import insert_category_into_atom
-    from portage.dep import Atom
-
-    bad_atoms = []
-    for x in " ".join(atoms).split():
-        atom = x
-        if "/" not in x.split(":")[0]:
-            x_cat = insert_category_into_atom(x, "dummy-category")
-            if x_cat is not None:
-                atom = x_cat
-
-        bad_atom = False
-        try:
-            atom = Atom(atom, allow_wildcard=True, allow_repo=less_strict)
-        except portage.exception.InvalidAtom:
-            bad_atom = True
-
-        if bad_atom or (atom.operator and not less_strict) or atom.blocker or 
atom.use:
-            bad_atoms.append(x)
-    return bad_atoms
-
-
 def parse_opts(tmpcmdline, silent=False):
     myaction = None
     myopts = {}

diff --git a/lib/portage/repository/config.py b/lib/portage/repository/config.py
index 16b9ae701f..a98fc17f25 100644
--- a/lib/portage/repository/config.py
+++ b/lib/portage/repository/config.py
@@ -12,7 +12,9 @@ import typing
 import portage
 from pathlib import Path
 from portage import eclass_cache, os
+from portage._sets.base import WildcardPackageSet
 from portage.checksum import get_valid_checksum_keys
+from portage.dep import Atom
 from portage.const import PORTAGE_BASE_PATH, REPO_NAME_LOC, USER_CONFIG_PATH
 from portage.eapi import (
     eapi_allows_directories_on_profile_level_and_repository_level,
@@ -89,6 +91,34 @@ def _gen_valid_repo(name):
     return name
 
 
+def _find_bad_atoms(atoms, less_strict=False):
+    """
+    Declares all atoms as invalid that have an operator,
+    a use dependency, a blocker or a repo spec.
+    It accepts atoms with wildcards.
+    In less_strict mode it accepts operators and repo specs.
+    """
+    from _emerge.is_valid_package_atom import insert_category_into_atom
+
+    bad_atoms = []
+    for x in " ".join(atoms or []).split():
+        atom = x
+        if "/" not in x.split(":")[0]:
+            x_cat = insert_category_into_atom(x, "dummy-category")
+            if x_cat is not None:
+                atom = x_cat
+
+        bad_atom = False
+        try:
+            atom = Atom(atom, allow_wildcard=True, allow_repo=less_strict)
+        except portage.exception.InvalidAtom:
+            bad_atom = True
+
+        if bad_atom or (atom.operator and not less_strict) or atom.blocker or 
atom.use:
+            bad_atoms.append(x)
+    return bad_atoms
+
+
 def _find_invalid_path_char(path, pos=0, endpos=None):
     """
     Returns the position of the first invalid character found in basename,
@@ -163,6 +193,8 @@ class RepoConfig:
         "sync_user",
         "thin_manifest",
         "update_changelog",
+        "usepkg_exclude",
+        "usepkg_include",
         "user_location",
         "volatile",
         "_eapis_banned",
@@ -218,6 +250,40 @@ class RepoConfig:
         # The main-repo key makes only sense for the 'DEFAULT' section.
         self.main_repo = repo_opts.get("main-repo")
 
+        # usepkg-exclude and usepkg-include validation
+        for opt in ("usepkg-exclude", "usepkg-include"):
+            attr = opt.replace("-", "_")
+            if name == "DEFAULT":
+                setattr(self, attr, None)
+                continue
+            usepkg_atoms = repo_opts.get(opt, "").split()
+            bad_atoms = _find_bad_atoms(usepkg_atoms)
+            if bad_atoms:
+                writemsg(
+                    "\n!!! The following atoms are invalid in %s attribute for 
"
+                    "repo [%s] (only package names and slot atoms allowed):\n"
+                    "\n    %s\n" % (opt, name, "\n    ".join(bad_atoms))
+                )
+                for a in bad_atoms:
+                    usepkg_atoms.remove(a)
+            usepkg_set = WildcardPackageSet(usepkg_atoms)
+            setattr(self, attr, usepkg_set)
+        conflicted_atoms = (
+            self.usepkg_exclude
+            and self.usepkg_exclude.getAtoms().intersection(
+                self.usepkg_include.getAtoms()
+            )
+        )
+        if conflicted_atoms:
+            writemsg(
+                "\n!!! The following atoms appear in both the usepkg-exclude "
+                "usepkg-include lists for repo [%s]:\n"
+                "\n    %s\n" % (name, "\n    ".join(conflicted_atoms))
+            )
+            for a in conflicted_atoms:
+                self.usepkg_exclude.remove(a)
+                self.usepkg_include.remove(a)
+
         priority = repo_opts.get("priority")
         if priority is not None:
             try:
@@ -773,6 +839,8 @@ class RepoConfigLoader:
                             "sync_umask",
                             "sync_uri",
                             "sync_user",
+                            "usepkg_exclude",
+                            "usepkg_include",
                             "volatile",
                         ):
                             v = getattr(repos_conf_opts, k, None)
@@ -1338,6 +1406,8 @@ class RepoConfigLoader:
             "aliases",
             "eclass_overrides",
             "force",
+            "usepkg_exclude",
+            "usepkg_include",
         )
         repo_config_tuple_keys = ("masters",)
         keys = bool_keys + str_or_int_keys + str_tuple_keys + 
repo_config_tuple_keys

diff --git a/lib/portage/tests/resolver/ResolverPlayground.py 
b/lib/portage/tests/resolver/ResolverPlayground.py
index a66ed54944..d325690618 100644
--- a/lib/portage/tests/resolver/ResolverPlayground.py
+++ b/lib/portage/tests/resolver/ResolverPlayground.py
@@ -94,6 +94,7 @@ class ResolverPlayground:
             "package.use.stable",
             "package.use.stable.force",
             "package.use.stable.mask",
+            "repos.conf",
             "soname.provided",
             "use.force",
             "use.mask",
@@ -678,6 +679,11 @@ class ResolverPlayground:
         configs = user_config.copy()
         configs["make.conf"] = make_conf_lines
 
+        repos_conf_lines = list(user_config.get("repos.conf", ()))
+        configs["repos.conf"] = _combine_repo_config(
+            self._repositories, repos_conf_lines
+        )
+
         if self._binrepos:
             binrepos_conf_lines = list(user_config.get("binrepos.conf", ()))
             configs["binrepos.conf"] = _combine_repo_config(
@@ -750,17 +756,10 @@ class ResolverPlayground:
         if self.target_root != os.sep:
             create_trees_kwargs["target_root"] = self.target_root
 
-        env = {
-            "PATH": 
f"{self.eprefix}/usr/sbin:{self.eprefix}/usr/bin:{os.environ['PATH']}",
-            "PORTAGE_REPOSITORIES": "\n".join(
-                "[%s]\n%s"
-                % (
-                    repo_name,
-                    "\n".join(f"{k} = {v}" for k, v in repo_config.items()),
-                )
-                for repo_name, repo_config in self._repositories.items()
-            ),
-        }
+        path = 
f"{self.eprefix}/usr/sbin:{self.eprefix}/usr/bin:{os.environ['PATH']}"
+        env = {"PATH": path}
+        with open(os.path.join(self.eprefix, USER_CONFIG_PATH, "repos.conf")) 
as f:
+            env["PORTAGE_REPOSITORIES"] = f.read()
 
         if self.debug:
             env["PORTAGE_DEBUG"] = "1"

diff --git a/lib/portage/tests/resolver/test_binpackage_selection.py 
b/lib/portage/tests/resolver/test_binpackage_selection.py
index ac2f10ec63..ce1f57d9c7 100644
--- a/lib/portage/tests/resolver/test_binpackage_selection.py
+++ b/lib/portage/tests/resolver/test_binpackage_selection.py
@@ -8,6 +8,11 @@ from portage.tests.resolver.ResolverPlayground import (
 )
 
 
+# attach build-ids to existing package collection
+def with_build_id(pkgs, build):
+    return [(cpv, meta | {"BUILD_ID": build}) for cpv, meta in pkgs.items()]
+
+
 # base class for unit tests of binary package selection options
 class BinPkgSelectionTestCase(TestCase):
     pkgs_no_deps = {
@@ -28,6 +33,12 @@ class BinPkgSelectionTestCase(TestCase):
         "app-misc/baz-1.1": {},
     }
 
+    pkgs_other_repo = {
+        "app-misc/foo-1.0::other_repo": {},
+        "app-misc/bar-1.0::other_repo": {},
+        "app-misc/baz-1.0::other_repo": {},
+    }
+
     pkgs_with_deps = {
         "app-misc/foo-1.0": {"RDEPEND": "app-misc/bar"},
         "app-misc/bar-1.0": {"RDEPEND": "app-misc/baz"},
@@ -57,6 +68,15 @@ class BinPkgSelectionTestCase(TestCase):
         finally:
             playground.cleanup()
 
+    # runs multiple test cases in multiple playgrounds with different config
+    def runBinPkgSelectionTestUserConfig(self, config, test_cases, **kwargs):
+        for n, test_case in enumerate(test_cases.items()):
+            lines, tests = test_case
+            kwargs.setdefault("user_config", {})[config] = lines
+
+            with self.subTest(f"Playground {n+1}/{len(test_cases)}"):
+                self.runBinPkgSelectionTest(tests, **kwargs)
+
 
 # test --getbinpkg-exclude option
 class GetBinPkgExcludeTestCase(BinPkgSelectionTestCase):
@@ -954,6 +974,278 @@ class UsePkgExcludeTestCase(BinPkgSelectionTestCase):
             world=world,
         )
 
+    def testUsePkgExcludeReposConf(self):
+        binpkgs = self.pkgs_no_deps
+        ebuilds = self.pkgs_no_deps
+
+        test_cases = {
+            (
+                "[test_repo]",
+                "usepkg-exclude = foo",
+            ): (
+                # reposconf.conf attributes to have no effect without --usepkg
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    mergelist=[
+                        "app-misc/foo-1.0",
+                        "app-misc/bar-1.0",
+                        "app-misc/baz-1.0",
+                    ],
+                ),
+                # request all packages with usepkg-exclude in repos.conf
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True},
+                    mergelist=[
+                        "app-misc/foo-1.0",
+                        "[binary]app-misc/bar-1.0",
+                        "[binary]app-misc/baz-1.0",
+                    ],
+                ),
+                # suppliment repos.conf with --usepkg-exclude on command line
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True, "--usepkg-exclude": ["bar"]},
+                    mergelist=[
+                        "app-misc/foo-1.0",
+                        "app-misc/bar-1.0",
+                        "[binary]app-misc/baz-1.0",
+                    ],
+                ),
+                # override repos.conf with --usepkg-include on command line
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True, "--usepkg-include": ["foo"]},
+                    mergelist=[
+                        "[binary]app-misc/foo-1.0",
+                        "app-misc/bar-1.0",
+                        "app-misc/baz-1.0",
+                    ],
+                ),
+            ),
+            (
+                "[test_repo]",
+                "usepkg-exclude = foo",
+                "usepkg-include = foo",
+            ): (
+                # conflicted repos.conf attributes to have no effect
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True},
+                    mergelist=[
+                        "[binary]app-misc/foo-1.0",
+                        "[binary]app-misc/bar-1.0",
+                        "[binary]app-misc/baz-1.0",
+                    ],
+                ),
+            ),
+            (
+                "[test_repo]",
+                "usepkg-exclude = foo bar",
+                "usepkg-include = foo",
+            ): (
+                # conflicted repos.conf attributes to not interfere with
+                # non-overlapping usepkg-exclude
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True},
+                    mergelist=[
+                        "[binary]app-misc/foo-1.0",
+                        "app-misc/bar-1.0",
+                        "[binary]app-misc/baz-1.0",
+                    ],
+                ),
+                # remaining atoms in repos.conf sourced lists after conflicting
+                # attributes have been filtered still to be overridable using
+                # --usepkg-include on command line
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True, "--usepkg-include": ["bar"]},
+                    mergelist=[
+                        "app-misc/foo-1.0",
+                        "[binary]app-misc/bar-1.0",
+                        "app-misc/baz-1.0",
+                    ],
+                ),
+            ),
+        }
+
+        self.runBinPkgSelectionTestUserConfig(
+            "repos.conf",
+            test_cases,
+            binpkgs=binpkgs,
+            ebuilds=ebuilds,
+        )
+
+    def testUsePkgExcludeReposConfDeps(self):
+        binpkgs = self.pkgs_with_deps
+        ebuilds = self.pkgs_with_deps
+
+        user_config = {
+            "repos.conf": (
+                "[test_repo]",
+                "usepkg-exclude = foo",
+            )
+        }
+
+        test_cases = (
+            # usepkg-exclude in repos.conf to have no effect on --nobindeps
+            ResolverPlaygroundTestCase(
+                ["app-misc/foo"],
+                success=True,
+                options={"--usepkg": True, "--nobindeps": True},
+                mergelist=[
+                    "app-misc/baz-1.0",
+                    "app-misc/bar-1.0",
+                    "[binary]app-misc/foo-1.0",
+                ],
+            ),
+        )
+
+        self.runBinPkgSelectionTest(
+            test_cases,
+            binpkgs=binpkgs,
+            ebuilds=ebuilds,
+            user_config=user_config,
+        )
+
+    def testUsePkgExcludeMultiRepo(self):
+        # see commentary in testGetBinPkgExcludeMultiBinrepo()
+        pkgs_no_deps = with_build_id(self.pkgs_no_deps, "1")
+        pkgs_other_repo = with_build_id(self.pkgs_other_repo, "2")
+
+        ebuilds = self.pkgs_no_deps | self.pkgs_other_repo
+        binpkgs = pkgs_no_deps + pkgs_other_repo
+
+        user_config = {
+            "repos.conf": (
+                "[test_repo]",
+                "usepkg-exclude = foo",
+                "[other_repo]",
+                "usepkg-exclude = bar",
+            )
+        }
+
+        # note that repository priority order is not well defined *within* 
pkgdir
+        # and so test cases which assume a plan c/p atom will resolve to a 
binary
+        # for any given repo implicitly can spuriously fail, regardless of 
priority
+        # set in repos.conf.
+        #
+        # TL;DR = all atoms in these tests cases *must* be ::repo qualified!
+        test_cases = (
+            # request test_repo for which only app-misc/foo is excluded in 
repos.conf
+            ResolverPlaygroundTestCase(
+                [
+                    "app-misc/foo::test_repo",
+                    "app-misc/bar::test_repo",
+                    "app-misc/baz::test_repo",
+                ],
+                success=True,
+                ignore_mergelist_order=True,
+                check_repo_names=True,
+                options={"--usepkg": True},
+                mergelist=[
+                    "app-misc/foo-1.0",
+                    "[binary]app-misc/bar-1.0-1",
+                    "[binary]app-misc/baz-1.0-1",
+                ],
+            ),
+            # request other_repo for which only app-misc/bar is excluded in 
repos.conf
+            ResolverPlaygroundTestCase(
+                [
+                    "app-misc/foo::other_repo",
+                    "app-misc/bar::other_repo",
+                    "app-misc/baz::other_repo",
+                ],
+                success=True,
+                ignore_mergelist_order=True,
+                check_repo_names=True,
+                options={"--usepkg": True},
+                mergelist=[
+                    "[binary]app-misc/foo-1.0-2::other_repo",
+                    "app-misc/bar-1.0::other_repo",
+                    "[binary]app-misc/baz-1.0-2::other_repo",
+                ],
+            ),
+            # use repo qualifier to request packages from repos for which both
+            # app-misc/foo and app-misc/bar are excluded in repos.conf
+            ResolverPlaygroundTestCase(
+                [
+                    "app-misc/foo::test_repo",
+                    "app-misc/bar::other_repo",
+                    "app-misc/baz::test_repo",
+                ],
+                success=True,
+                ignore_mergelist_order=True,
+                check_repo_names=True,
+                options={"--usepkg": True},
+                mergelist=[
+                    "app-misc/foo-1.0",
+                    "app-misc/bar-1.0::other_repo",
+                    "[binary]app-misc/baz-1.0-1",
+                ],
+            ),
+            # use repo qualifier to request packages from repos for which 
neither
+            # app-misc/foo nor app-misc/bar are excluded in repos.conf
+            ResolverPlaygroundTestCase(
+                [
+                    "app-misc/foo::other_repo",
+                    "app-misc/bar::test_repo",
+                    "app-misc/baz::test_repo",
+                ],
+                success=True,
+                ignore_mergelist_order=True,
+                check_repo_names=True,
+                options={"--usepkg": True},
+                mergelist=[
+                    "[binary]app-misc/foo-1.0-2::other_repo",
+                    "[binary]app-misc/bar-1.0-1",
+                    "[binary]app-misc/baz-1.0-1",
+                ],
+            ),
+            # use repo qualifier to arrange for neither app-misc/foo nor 
app-misc/bar
+            # to be binary, but override repos.conf for foo using 
--usepkg-include
+            # which implicitly excludes app-misc/baz as the command line option
+            # applies to all repositories
+            ResolverPlaygroundTestCase(
+                [
+                    "app-misc/foo::test_repo",
+                    "app-misc/bar::other_repo",
+                    "app-misc/baz::test_repo",
+                ],
+                success=True,
+                ignore_mergelist_order=True,
+                check_repo_names=True,
+                options={"--usepkg": True, "--usepkg-include": ["foo"]},
+                mergelist=[
+                    "[binary]app-misc/foo-1.0-1",
+                    "app-misc/bar-1.0::other_repo",
+                    "app-misc/baz-1.0",
+                ],
+            ),
+        )
+
+        self.runBinPkgSelectionTest(
+            test_cases,
+            binpkgs=binpkgs,
+            ebuilds=ebuilds,
+            user_config=user_config,
+        )
+
 
 # test --usepkg-include option
 class UsePkgIncludeTestCase(BinPkgSelectionTestCase):
@@ -1252,3 +1544,252 @@ class UsePkgIncludeTestCase(BinPkgSelectionTestCase):
             installed=installed,
             world=world,
         )
+
+    def testUsePkgIncludeReposConf(self):
+        binpkgs = self.pkgs_no_deps
+        ebuilds = self.pkgs_no_deps
+
+        test_cases = {
+            (
+                "[test_repo]",
+                "usepkg-include = foo",
+            ): (
+                # repos.conf attribute to have no effect without --usepkg
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    mergelist=[
+                        "app-misc/foo-1.0",
+                        "app-misc/bar-1.0",
+                        "app-misc/baz-1.0",
+                    ],
+                ),
+                # request all packages with usepkg-include in repos.conf
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True},
+                    mergelist=[
+                        "[binary]app-misc/foo-1.0",
+                        "app-misc/bar-1.0",
+                        "app-misc/baz-1.0",
+                    ],
+                ),
+                # suppliment repos.conf with --usepkg-include on command line
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True, "--usepkg-include": ["bar"]},
+                    mergelist=[
+                        "[binary]app-misc/foo-1.0",
+                        "[binary]app-misc/bar-1.0",
+                        "app-misc/baz-1.0",
+                    ],
+                ),
+                # override repos.conf with --usepkg-exclude on  command line
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True, "--usepkg-exclude": ["foo"]},
+                    mergelist=[
+                        "app-misc/foo-1.0",
+                        "[binary]app-misc/bar-1.0",
+                        "[binary]app-misc/baz-1.0",
+                    ],
+                ),
+            ),
+            (
+                "[test_repo]",
+                "usepkg-exclude = foo",
+                "usepkg-include = foo bar",
+            ): (
+                # conflicted repos.conf attributes to not interfere with
+                # non-overlapping usepkg-include
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True},
+                    mergelist=[
+                        "app-misc/foo-1.0",
+                        "[binary]app-misc/bar-1.0",
+                        "app-misc/baz-1.0",
+                    ],
+                ),
+                # remaining atoms in repos.conf sourced lists after conflicting
+                # attributes have been filtered still to be overridable using
+                # --usepkg-exclude on command line
+                ResolverPlaygroundTestCase(
+                    self.pkg_atoms,
+                    success=True,
+                    ignore_mergelist_order=True,
+                    options={"--usepkg": True, "--usepkg-exclude": ["bar"]},
+                    mergelist=[
+                        "[binary]app-misc/foo-1.0",
+                        "app-misc/bar-1.0",
+                        "[binary]app-misc/baz-1.0",
+                    ],
+                ),
+            ),
+        }
+
+        self.runBinPkgSelectionTestUserConfig(
+            "repos.conf",
+            test_cases,
+            binpkgs=binpkgs,
+            ebuilds=ebuilds,
+        )
+
+    def testUsePkgIncludeReposConfDeps(self):
+        binpkgs = self.pkgs_with_deps
+        ebuilds = self.pkgs_with_deps
+
+        user_config = {
+            "repos.conf": (
+                "[test_repo]",
+                "usepkg-include = bar",
+            )
+        }
+
+        test_cases = (
+            # usepkg-include in repos.conf to have no effect on --nobindeps
+            ResolverPlaygroundTestCase(
+                ["app-misc/foo"],
+                success=True,
+                options={"--usepkg": True, "--nobindeps": True},
+                mergelist=[
+                    "app-misc/baz-1.0",
+                    "app-misc/bar-1.0",
+                    "[binary]app-misc/foo-1.0",
+                ],
+            ),
+        )
+
+        self.runBinPkgSelectionTest(
+            test_cases,
+            binpkgs=binpkgs,
+            ebuilds=ebuilds,
+            user_config=user_config,
+        )
+
+    def testUsePkgIncludeMultiRepo(self):
+        # see commentary in testGetBinPkgExcludeMultiBinrepo()
+        pkgs_no_deps = with_build_id(self.pkgs_no_deps, "1")
+        pkgs_other_repo = with_build_id(self.pkgs_other_repo, "2")
+
+        ebuilds = self.pkgs_no_deps | self.pkgs_other_repo
+        binpkgs = pkgs_no_deps + pkgs_other_repo
+
+        user_config = {
+            "repos.conf": (
+                "[test_repo]",
+                "usepkg-include = foo",
+                "[other_repo]",
+                "usepkg-include = bar",
+            )
+        }
+
+        # all atoms in these tests cases *must* be ::repo qualified! see 
comments
+        # in testUsePkgExcludeMultiRepo()
+        test_cases = (
+            # request test_repo for which only app-misc/foo is included in 
repos.conf
+            ResolverPlaygroundTestCase(
+                [
+                    "app-misc/foo::test_repo",
+                    "app-misc/bar::test_repo",
+                    "app-misc/baz::test_repo",
+                ],
+                success=True,
+                ignore_mergelist_order=True,
+                check_repo_names=True,
+                options={"--usepkg": True},
+                mergelist=[
+                    "[binary]app-misc/foo-1.0-1",
+                    "app-misc/bar-1.0",
+                    "app-misc/baz-1.0",
+                ],
+            ),
+            # request other_repo for which only app-misc/bar is included in 
repos.conf
+            ResolverPlaygroundTestCase(
+                [
+                    "app-misc/foo::other_repo",
+                    "app-misc/bar::other_repo",
+                    "app-misc/baz::other_repo",
+                ],
+                success=True,
+                ignore_mergelist_order=True,
+                check_repo_names=True,
+                options={"--usepkg": True},
+                mergelist=[
+                    "app-misc/foo-1.0::other_repo",
+                    "[binary]app-misc/bar-1.0-2::other_repo",
+                    "app-misc/baz-1.0::other_repo",
+                ],
+            ),
+            # use repo qualifier to request packages from repos for which both
+            # app-misc/foo and app-misc/bar are included in repos.conf
+            ResolverPlaygroundTestCase(
+                [
+                    "app-misc/foo::test_repo",
+                    "app-misc/bar::other_repo",
+                    "app-misc/baz::test_repo",
+                ],
+                success=True,
+                ignore_mergelist_order=True,
+                check_repo_names=True,
+                options={"--usepkg": True},
+                mergelist=[
+                    "[binary]app-misc/foo-1.0-1",
+                    "[binary]app-misc/bar-1.0-2::other_repo",
+                    "app-misc/baz-1.0",
+                ],
+            ),
+            # use repo qualifier to request packages from repos for which 
neither
+            # app-misc/foo nor app-misc/bar are included in repos.conf
+            ResolverPlaygroundTestCase(
+                [
+                    "app-misc/foo::other_repo",
+                    "app-misc/bar::test_repo",
+                    "app-misc/baz::test_repo",
+                ],
+                success=True,
+                ignore_mergelist_order=True,
+                check_repo_names=True,
+                options={"--usepkg": True},
+                mergelist=[
+                    "app-misc/foo-1.0::other_repo",
+                    "app-misc/bar-1.0",
+                    "app-misc/baz-1.0",
+                ],
+            ),
+            # use repo qualifier to arrange for both app-misc/foo and 
app-misc/bar
+            # to be binary, but override repos.conf for foo using 
--usepkg-exclude
+            # which has no effect on app-misc/baz
+            ResolverPlaygroundTestCase(
+                [
+                    "app-misc/foo::test_repo",
+                    "app-misc/bar::other_repo",
+                    "app-misc/baz::test_repo",
+                ],
+                success=True,
+                ignore_mergelist_order=True,
+                check_repo_names=True,
+                options={"--usepkg": True, "--usepkg-exclude": ["foo"]},
+                mergelist=[
+                    "app-misc/foo-1.0",
+                    "[binary]app-misc/bar-1.0-2::other_repo",
+                    "app-misc/baz-1.0",
+                ],
+            ),
+        )
+
+        self.runBinPkgSelectionTest(
+            test_cases,
+            binpkgs=binpkgs,
+            ebuilds=ebuilds,
+            user_config=user_config,
+        )

diff --git a/man/emerge.1 b/man/emerge.1
index a7b5fa0cf1..ff242eacb6 100644
--- a/man/emerge.1
+++ b/man/emerge.1
@@ -761,7 +761,8 @@ specific packages, see the \fB\-\-exclude\fR option.
 Prefer ebuilds for any dependencies and select binary packages only for the
 atoms or sets requested for install, i.e. those named on the command line. Has
 no effect unless \fB-k\fR is also used or implied. Both \fB--usepkg-exclude\fR
-and \fB--usepkg-include\fR are ignored when this option is used.
+and \fB--usepkg-include\fR are ignored when this option is used, as are the
+usepkg-exclude and usepkg-include attributes in \fBrepos.conf\fR.
 .TP
 .BR \-\-noconfmem
 Causes portage to disregard merge records indicating that a config file

diff --git a/man/portage.5 b/man/portage.5
index 6c1ec5e039..41c68ab961 100644
--- a/man/portage.5
+++ b/man/portage.5
@@ -1320,6 +1320,20 @@ Require the detached tarball signature to contain a good 
OpenPGP
 signature. This uses the OpenPGP key(ring) specified by the
 sync\-openpgp\-key\-path setting. Defaults to no, false.
 .TP
+.B usepkg\-exclude
+A space separated list of package names or slot atoms. Emerge will ignore
+matching binary packages except where overridden by the \-\-usepkg\-include
+command line option (see \fBemerge\fR(1)).
+
+This is an experimental option subject to change.
+.TP
+.B usepkg\-include
+A space separated list of package names or slot names. Emerge will ignore
+non-matching binary packages except where overridden by the \-\-usepkg\-exclude
+command line option (see \fBemerge\fR(1)).
+
+This is an experimental option subject to change.
+.TP
 .B volatile = yes|no|true|false
 Specifies whether a repository is volatile.  Volatile repositories
 are assumed to contain changes made outside of Portage.  This prohibits


Reply via email to