Преглед изворни кода

Merge pull request #8729 from naderman/t/partial-update-always-update-replace

Give a clearer error message explaining how to update a conflicting locked dependency
Jordi Boggiano пре 5 година
родитељ
комит
51c48b1519

+ 4 - 2
src/Composer/Command/RequireCommand.php

@@ -66,6 +66,8 @@ class RequireCommand extends InitCommand
                 new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
                 new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements.'),
                 new InputOption('update-with-all-dependencies', null, InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements.'),
+                new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-dependencies'),
+                new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'),
                 new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                 new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
                 new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'),
@@ -256,9 +258,9 @@ EOT
         $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');
 
         $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
-        if ($input->getOption('update-with-all-dependencies')) {
+        if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) {
             $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
-        } elseif ($input->getOption('update-with-dependencies')) {
+        } elseif ($input->getOption('update-with-dependencies') || $input->getOption('with-dependencies')) {
             $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
         }
 

+ 2 - 2
src/Composer/DependencyResolver/PoolBuilder.php

@@ -268,8 +268,8 @@ class PoolBuilder
             }
         }
 
-        // if we're doing a partial update with deps and we're not loading an initial fixed package
-        // we also need to trigger an update for transitive deps which are being replaced
+        // if we're doing a partial update with deps we also need to unfix packages which are being replaced in case they
+        // are currently locked and thus prevent this updateable package from being installable/updateable
         if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) {
             foreach ($package->getReplaces() as $link) {
                 $replace = $link->getTarget();

+ 11 - 1
src/Composer/DependencyResolver/Problem.php

@@ -92,7 +92,6 @@ class Problem
         }
 
         $messages = array();
-
         foreach ($reasons as $rule) {
             $messages[] = $rule->getPrettyString($repositorySet, $request, $pool, $installedMap, $learnedPool);
         }
@@ -100,6 +99,17 @@ class Problem
         return "\n    - ".implode("\n    - ", $messages);
     }
 
+    public function isCausedByLock()
+    {
+        foreach ($this->reasons as $sectionRules) {
+            foreach ($sectionRules as $rule) {
+                if ($rule->isCausedByLock()) {
+                    return true;
+                }
+            }
+        }
+    }
+
     /**
      * Store a reason descriptor but ignore duplicates
      *

+ 5 - 0
src/Composer/DependencyResolver/Rule.php

@@ -123,6 +123,11 @@ abstract class Rule
 
     abstract public function isAssertion();
 
+    public function isCausedByLock()
+    {
+        return $this->getReason() === self::RULE_FIXED && $this->reasonData['lockable'];
+    }
+
     public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, array $installedMap = array(), array $learnedPool = array())
     {
         $literals = $this->getLiterals();

+ 7 - 0
src/Composer/DependencyResolver/SolverProblemsException.php

@@ -36,12 +36,15 @@ class SolverProblemsException extends \RuntimeException
         $installedMap = $request->getPresentMap(true);
         $text = "\n";
         $hasExtensionProblems = false;
+        $isCausedByLock = false;
         foreach ($this->problems as $i => $problem) {
             $text .= "  Problem ".($i + 1).$problem->getPrettyString($repositorySet, $request, $pool, $installedMap, $this->learnedPool)."\n";
 
             if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
                 $hasExtensionProblems = true;
             }
+
+            $isCausedByLock |= $problem->isCausedByLock();
         }
 
         if (!$isDevExtraction && (strpos($text, 'could not be found') || strpos($text, 'no matching package found'))) {
@@ -52,6 +55,10 @@ class SolverProblemsException extends \RuntimeException
             $text .= $this->createExtensionHint();
         }
 
+        if ($isCausedByLock && !$isDevExtraction) {
+            $text .= "\nUse the option --with-all-dependencies to allow updates and removals for packages currently locked to specific versions.";
+        }
+
         return $text;
     }
 

+ 55 - 0
tests/Composer/Test/Fixtures/installer/update-allow-list-require-new-replace.test

@@ -0,0 +1,55 @@
+--TEST--
+If a new requirement cannot be installed on a partial update due to replace, there should be a suggestion to use --with-all-dependencies
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                { "name": "current/pkg", "version": "1.0.0", "require": { "current/dep": "*" } },
+                { "name": "current/dep", "version": "1.0.0" },
+                { "name": "new/pkg", "version": "1.0.0", "replace": { "current/dep": "1.0.0" } }
+            ]
+        }
+    ],
+    "require": {
+        "current/pkg": "1.*",
+        "new/pkg": "1.*"
+    }
+}
+--INSTALLED--
+[
+    { "name": "current/pkg", "version": "1.0.0", "require": { "current/dep": "*" } },
+    { "name": "current/dep", "version": "1.0.0" }
+]
+--LOCK--
+{
+    "packages": [
+        { "name": "current/pkg", "version": "1.0.0", "require": { "current/dep": "*" } },
+        { "name": "current/dep", "version": "1.0.0" }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "dev",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
+--RUN--
+update new/pkg
+--EXPECT-EXIT-CODE--
+2
+--EXPECT-OUTPUT--
+Loading composer repositories with package information
+Updating dependencies
+Your requirements could not be resolved to an installable set of packages.
+
+  Problem 1
+    - current/dep is locked to version 1.0.0 and an update of this package was not requested.
+    - new/pkg 1.0.0 can not be installed as that would require removing current/dep 1.0.0. new/pkg replaces current/dep and can thus not coexist with it.
+    - Root composer.json requires new/pkg 1.* -> satisfiable by new/pkg[1.0.0].
+
+Use the option --with-all-dependencies to allow updates and removals for packages currently locked to specific versions.
+--EXPECT--