commit:     75a4f2c2e07c128fef4d9faf3a8fb9d67565239e
Author:     Sam James <sam <AT> gentoo <DOT> org>
AuthorDate: Thu Feb 16 06:26:07 2023 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Tue Feb 21 07:16:27 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=75a4f2c2

emerge: add --update-if-installed

This adds a new emerge option '--update-if-installed'.

The use case for such an option is as follows:
- User finds out libfoo-1.2 is buggy.
- They want to upgrade all their systems if libfoo is installed.
- They don't want to install libfoo if it's not already installed.

  Unfortunately, --update fails this last point, hence
  the need for a new option.

Closes: https://github.com/gentoo/portage/pull/988
Signed-off-by: Sam James <sam <AT> gentoo.org>

 NEWS                                      |   4 ++
 lib/_emerge/create_depgraph_params.py     |   6 ++
 lib/_emerge/depgraph.py                   |  17 ++++-
 lib/_emerge/main.py                       |   3 +-
 lib/portage/tests/resolver/test_update.py | 106 ++++++++++++++++++++++++++++++
 man/emerge.1                              |   6 ++
 6 files changed, 140 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index 29e9de038..9389d2c09 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,10 @@ Features:
 
 * emerge: add --onlydeps-with-ideps=<y|n> option (bug #890777)
 
+* emerge: add --update-if-installed option. This is useful for one-shot
+  emerge commands to be run across several machines to upgrade packages
+  only if they're installed.
+
 * install-qa-check.d: 60pkgconfig: add opt-in QA_PKGCONFIG_VERSION check
 
 * emerge: Log completion of package installs.

diff --git a/lib/_emerge/create_depgraph_params.py 
b/lib/_emerge/create_depgraph_params.py
index 531230402..1bbca5de9 100644
--- a/lib/_emerge/create_depgraph_params.py
+++ b/lib/_emerge/create_depgraph_params.py
@@ -129,6 +129,12 @@ def create_depgraph_params(myopts, myaction):
     if changed_slot:
         myparams["changed_slot"] = True
 
+    # --update-if-installed implies --update
+    update_if_installed = myopts.get("--update-if-installed")
+    if update_if_installed is not None:
+        myparams["update_if_installed"] = update_if_installed
+        myopts["--update"] = True
+
     if (
         "--update" in myopts
         or "--newrepo" in myopts

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 1631ed126..412dc7b6f 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -1,4 +1,4 @@
-# Copyright 1999-2021 Gentoo Authors
+# Copyright 1999-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import errno
@@ -5019,6 +5019,21 @@ class depgraph:
                     pkg, existing_node = self._select_package(
                         myroot, atom, onlydeps=onlydeps
                     )
+
+                    # Is the package installed (at any version)?
+                    if pkg and "update_if_installed" in 
self._dynamic_config.myparams:
+                        package_is_installed = any(
+                            self._iter_match_pkgs(
+                                self._frozen_config.roots[myroot], 
"installed", atom
+                            )
+                        )
+
+                        # This package isn't eligible for selection in the
+                        # merge list as the user passed --update-if-installed
+                        # and it isn't installed.
+                        if not package_is_installed:
+                            continue
+
                     if not pkg:
                         pprovided_match = False
                         for virt_choice in virtuals.get(atom.cp, []):

diff --git a/lib/_emerge/main.py b/lib/_emerge/main.py
index 38233e05c..850487d36 100644
--- a/lib/_emerge/main.py
+++ b/lib/_emerge/main.py
@@ -1,4 +1,4 @@
-# Copyright 1999-2020 Gentoo Authors
+# Copyright 1999-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import argparse
@@ -52,6 +52,7 @@ options = [
     "--tree",
     "--unordered-display",
     "--update",
+    "--update-if-installed",
 ]
 
 shortmapping = {

diff --git a/lib/portage/tests/resolver/test_update.py 
b/lib/portage/tests/resolver/test_update.py
new file mode 100644
index 000000000..e67013f9f
--- /dev/null
+++ b/lib/portage/tests/resolver/test_update.py
@@ -0,0 +1,106 @@
+# Copyright 2022-2023 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+from portage.tests import TestCase
+from portage.tests.resolver.ResolverPlayground import (
+    ResolverPlayground,
+    ResolverPlaygroundTestCase,
+)
+
+
+class UpdateIfInstalledTestCase(TestCase):
+    def testUpdateIfInstalledEmerge(self):
+        installed = {
+            "dev-lang/ghc-4": {},
+            "dev-libs/larryware-3": {},
+            "dev-libs/larryware-ng-3": {},
+            "virtual/libc-1": {},
+        }
+
+        ebuilds = installed.copy()
+        ebuilds.update(
+            {
+                "app-misc/cowsay-10": {},
+                "dev-lang/ghc-5": {},
+                "dev-libs/larryware-4": {},
+                "dev-libs/larryware-ng-4": {"RDEPEND": ">=net-libs/moo-1"},
+                "net-libs/moo-1": {},
+            }
+        )
+
+        playground = ResolverPlayground(
+            ebuilds=ebuilds, installed=installed, debug=False
+        )
+
+        test_cases = (
+            # We should only try to update ghc when passed ghc and
+            # --update-if-installed. We don't want larryware to appear here,
+            # despite it being eligible for an upgrade otherwise with --update.
+            ResolverPlaygroundTestCase(
+                ["dev-lang/ghc"],
+                mergelist=["dev-lang/ghc-5"],
+                options={
+                    "--update-if-installed": True,
+                },
+                success=True,
+            ),
+            # Only try to upgrade ghc even if passed another candidate,
+            # as there's no upgrade due for it. We don't want to
+            # reinstall virtual/libc for the sake of it.
+            ResolverPlaygroundTestCase(
+                ["dev-lang/ghc", "virtual/libc"],
+                mergelist=["dev-lang/ghc-5"],
+                options={
+                    "--update-if-installed": True,
+                },
+                success=True,
+            ),
+            # Try to upgrade a package with no new versions available.
+            # This is just checking we still have --update semantics.
+            ResolverPlaygroundTestCase(
+                ["virtual/libc"],
+                mergelist=[],
+                options={
+                    "--update-if-installed": True,
+                },
+                success=True,
+            ),
+            # If a new package is given, we want to do nothing.
+            ResolverPlaygroundTestCase(
+                ["app-misc/cowsay"],
+                mergelist=[],
+                options={
+                    "--update-if-installed": True,
+                },
+                success=True,
+            ),
+            # If a new package (app-misc/cowsay) is given combined with
+            # a package eligible for an upgrade (dev-libs/larryware),
+            # upgrade just the latter.
+            ResolverPlaygroundTestCase(
+                ["app-misc/cowsay", "dev-libs/larryware"],
+                mergelist=["dev-libs/larryware-4"],
+                options={
+                    "--update-if-installed": True,
+                },
+                success=True,
+            ),
+            # Make sure that we can still pull in upgrades as
+            # dependencies (net-libs/moo) of the package we requested
+            # (dev-libs/larryware-ng).
+            ResolverPlaygroundTestCase(
+                ["dev-libs/larryware-ng"],
+                mergelist=["net-libs/moo-1", "dev-libs/larryware-ng-4"],
+                options={
+                    "--update-if-installed": True,
+                },
+                success=True,
+            ),
+        )
+
+        try:
+            for test_case in test_cases:
+                playground.run_TestCase(test_case)
+                self.assertEqual(test_case.test_success, True, 
test_case.fail_msg)
+        finally:
+            playground.cleanup()

diff --git a/man/emerge.1 b/man/emerge.1
index c85ffd7b6..a1c5f33be 100644
--- a/man/emerge.1
+++ b/man/emerge.1
@@ -1054,6 +1054,12 @@ the command line are greedy, meaning that unspecific
 atoms may match multiple versions of slotted packages.
 This option also implies the \fB\-\-selective\fR option.
 .TP
+.BR \-\-update\-if\-installed
+Acts similar to \fB\-\-update\fR except it updates packages
+passed as arguments to the best version available only if they are
+already installed.  This is useful for oneshot commands across
+a series of systems to upgrade away from a buggy version.
+.TP
 .BR "\-\-use\-ebuild\-visibility [ y | n ]"
 Use unbuilt ebuild metadata for visibility
 checks on built packages.

Reply via email to