Эх сурвалжийг харах

Merge pull request #8860 from Seldaek/update-with

Add --with to update command to allow downgrading to a specific version / applying custom temporary constraints
Nils Adermann 4 жил өмнө
parent
commit
5d5515348a

+ 21 - 0
doc/03-cli.md

@@ -142,6 +142,26 @@ You can also use wildcards to update a bunch of packages at once:
 php composer.phar update "vendor/*"
 ```
 
+
+If you want to downgrade a package to a specific version without changing your
+composer.json you can use `--with` and provide a custom version constraint:
+
+```sh
+php composer.phar update --with vendor/package:2.0.1
+```
+
+The custom constraint has to be a subset of the existing constraint you have,
+and this feature is only available for your root package dependencies.
+
+If you only want to update the package(s) for which you provide custom constraints
+using `--with`, you can skip `--with` and just use constraints with the partial
+update syntax:
+
+```sh
+php composer.phar update vendor/package:2.0.1 vendor/package2:3.0.*
+```
+
+
 ### Options
 
 * **--prefer-source:** Install packages from `source` when available.
@@ -152,6 +172,7 @@ php composer.phar update "vendor/*"
 * **--no-install:** Does not run the install step after updating the composer.lock file.
 * **--lock:** Only updates the lock file hash to suppress warning about the
   lock file being out of date.
+* **--with:** Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0
 * **--no-autoloader:** Skips autoloader generation.
 * **--no-scripts:** Skips execution of scripts defined in `composer.json`.
 * **--no-progress:** Removes the progress display that can mess with some

+ 22 - 0
src/Composer/Command/BaseCommand.php

@@ -19,6 +19,7 @@ use Composer\Factory;
 use Composer\IO\IOInterface;
 use Composer\IO\NullIO;
 use Composer\Plugin\PreCommandRunEvent;
+use Composer\Package\Version\VersionParser;
 use Composer\Plugin\PluginEvents;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Output\OutputInterface;
@@ -180,4 +181,25 @@ abstract class BaseCommand extends Command
 
         return array($preferSource, $preferDist);
     }
+
+    protected function formatRequirements(array $requirements)
+    {
+        $requires = array();
+        $requirements = $this->normalizeRequirements($requirements);
+        foreach ($requirements as $requirement) {
+            if (!isset($requirement['version'])) {
+                throw new \UnexpectedValueException('Option '.$requirement['name'] .' is missing a version constraint, use e.g. '.$requirement['name'].':^1.0');
+            }
+            $requires[$requirement['name']] = $requirement['version'];
+        }
+
+        return $requires;
+    }
+
+    protected function normalizeRequirements(array $requirements)
+    {
+        $parser = new VersionParser();
+
+        return $parser->parseNameVersionPairs($requirements);
+    }
 }

+ 0 - 18
src/Composer/Command/InitCommand.php

@@ -585,17 +585,6 @@ EOT
         return array($this->parseAuthorString($author));
     }
 
-    protected function formatRequirements(array $requirements)
-    {
-        $requires = array();
-        $requirements = $this->normalizeRequirements($requirements);
-        foreach ($requirements as $requirement) {
-            $requires[$requirement['name']] = $requirement['version'];
-        }
-
-        return $requires;
-    }
-
     protected function getGitConfig()
     {
         if (null !== $this->gitConfig) {
@@ -660,13 +649,6 @@ EOT
         return false;
     }
 
-    protected function normalizeRequirements(array $requirements)
-    {
-        $parser = new VersionParser();
-
-        return $parser->parseNameVersionPairs($requirements);
-    }
-
     protected function addVendorIgnore($ignoreFile, $vendor = '/vendor/')
     {
         $contents = "";

+ 64 - 5
src/Composer/Command/UpdateCommand.php

@@ -18,6 +18,9 @@ use Composer\Installer;
 use Composer\IO\IOInterface;
 use Composer\Plugin\CommandEvent;
 use Composer\Plugin\PluginEvents;
+use Composer\Package\Version\VersionParser;
+use Composer\Semver\Constraint\MultiConstraint;
+use Composer\Package\Link;
 use Symfony\Component\Console\Helper\Table;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
@@ -39,6 +42,7 @@ class UpdateCommand extends BaseCommand
             ->setDescription('Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.')
             ->setDefinition(array(
                 new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.'),
+                new InputOption('with', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0'),
                 new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                 new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                 new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
@@ -80,6 +84,14 @@ from a specific vendor:
 
 <info>php composer.phar update vendor/package1 foo/* [...]</info>
 
+To run an update with more restrictive constraints you can use:
+
+<info>php composer.phar update --with vendor/package:1.0.*</info>
+
+To run a partial update with more restrictive constraints you can use the shorthand:
+
+<info>php composer.phar update vendor/package:1.0.*</info>
+
 To select packages names interactively with auto-completion use <info>-i</info>.
 
 Read more at https://getcomposer.org/doc/03-cli.md#update-u
@@ -101,22 +113,54 @@ EOT
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
 
         $packages = $input->getArgument('packages');
+        $reqs = $this->formatRequirements($input->getOption('with'));
+
+        // extract --with shorthands from the allowlist
+        if ($packages) {
+            $allowlistPackagesWithRequirements = array_filter($packages, function ($pkg) {
+                return preg_match('{\S+[ =:]\S+}', $pkg) > 0;
+            });
+            foreach ($this->formatRequirements($allowlistPackagesWithRequirements) as $package => $constraint) {
+                var_Dump($package, $constraint);
+                $reqs[$package] = $constraint;
+            }
+
+            // replace the foo/bar:req by foo/bar in the allowlist
+            foreach ($allowlistPackagesWithRequirements as $package) {
+                $packageName = preg_replace('{^([^ =:]+)[ =:].*$}', '$1', $package);
+                $index = array_search($package, $packages);
+                $packages[$index] = $packageName;
+            }
+        }
+
+        $rootRequires = $composer->getPackage()->getRequires();
+        $rootDevRequires = $composer->getPackage()->getDevRequires();
+        foreach ($reqs as $package => $constraint) {
+            if (isset($rootRequires[$package])) {
+                $rootRequires[$package] = $this->appendConstraintToLink($rootRequires[$package], $constraint);
+            } elseif (isset($rootDevRequires[$package])) {
+                $rootDevRequires[$package] = $this->appendConstraintToLink($rootDevRequires[$package], $constraint);
+            } else {
+                throw new \UnexpectedValueException('Only root package requirements can receive temporary constraints and '.$package.' is not one');
+            }
+        }
+        $composer->getPackage()->setRequires($rootRequires);
+        $composer->getPackage()->setDevRequires($rootDevRequires);
 
         if ($input->getOption('interactive')) {
             $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages);
         }
 
         if ($input->getOption('root-reqs')) {
-            $require = array_keys($composer->getPackage()->getRequires());
+            $requires = array_keys($rootRequires);
             if (!$input->getOption('no-dev')) {
-                $requireDev = array_keys($composer->getPackage()->getDevRequires());
-                $require = array_merge($require, $requireDev);
+                $requires = array_merge($requires, array_keys($rootDevRequires));
             }
 
             if (!empty($packages)) {
-                $packages = array_intersect($packages, $require);
+                $packages = array_intersect($packages, $requires);
             } else {
-                $packages = $require;
+                $packages = $requires;
             }
         }
 
@@ -242,4 +286,19 @@ EOT
 
         throw new \RuntimeException('Installation aborted.');
     }
+
+    private function appendConstraintToLink(Link $link, $constraint)
+    {
+        $parser = new VersionParser;
+        $oldPrettyString = $link->getConstraint()->getPrettyString();
+        $newConstraint = MultiConstraint::create(array($link->getConstraint(), $parser->parseConstraints($constraint)));
+        $newConstraint->setPrettyString($oldPrettyString.' && '.$constraint);
+        return new Link(
+            $link->getSource(),
+            $link->getTarget(),
+            $newConstraint,
+            $link->getDescription(),
+            $link->getPrettyConstraint() . ' && ' . $constraint
+        );
+    }
 }