1
0
Fork 0

Add --with to update command to allow downgrading to a specific version/applying custom temporary constraints, fixes #8756

pull/8860/head
Jordi Boggiano 2020-05-01 16:56:17 +02:00
parent fc8be2bed8
commit 7f308d986e
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC
4 changed files with 107 additions and 23 deletions

View File

@ -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

View File

@ -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);
}
}

View File

@ -577,17 +577,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) {
@ -652,13 +641,6 @@ EOT
return false;
}
protected function normalizeRequirements(array $requirements)
{
$parser = new VersionParser();
return $parser->parseNameVersionPairs($requirements);
}
protected function addVendorIgnore($ignoreFile, $vendor = '/vendor/')
{
$contents = "";

View File

@ -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
);
}
}