Add --patch-only flag to update command to restrict updates to patch versions and make them safer (#12122)
Fixes #11446pull/12129/head
parent
6b81140f81
commit
c8bd0e6278
|
@ -232,6 +232,7 @@ php composer.phar update vendor/package:2.0.1 vendor/package2:3.0.*
|
||||||
COMPOSER_PREFER_LOWEST=1 env var.
|
COMPOSER_PREFER_LOWEST=1 env var.
|
||||||
* **--minimal-changes (-m):** During a partial update with `-w`/`-W`, only perform absolutely necessary
|
* **--minimal-changes (-m):** During a partial update with `-w`/`-W`, only perform absolutely necessary
|
||||||
changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var.
|
changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var.
|
||||||
|
* **--patch-only:** Only allow patch version updates for currently installed dependencies.
|
||||||
* **--interactive:** Interactive interface with autocompletion to select the packages to update.
|
* **--interactive:** Interactive interface with autocompletion to select the packages to update.
|
||||||
* **--root-reqs:** Restricts the update to your first degree dependencies.
|
* **--root-reqs:** Restricts the update to your first degree dependencies.
|
||||||
* **--bump-after-update:** Runs `bump` after performing the update. Set to `dev` or `no-dev` to only bump those dependencies.
|
* **--bump-after-update:** Runs `bump` after performing the update. Set to `dev` or `no-dev` to only bump those dependencies.
|
||||||
|
|
|
@ -28,6 +28,7 @@ use Composer\Repository\CompositeRepository;
|
||||||
use Composer\Repository\PlatformRepository;
|
use Composer\Repository\PlatformRepository;
|
||||||
use Composer\Repository\RepositoryInterface;
|
use Composer\Repository\RepositoryInterface;
|
||||||
use Composer\Repository\RepositorySet;
|
use Composer\Repository\RepositorySet;
|
||||||
|
use Composer\Semver\Constraint\MultiConstraint;
|
||||||
use Composer\Semver\Intervals;
|
use Composer\Semver\Intervals;
|
||||||
use Composer\Util\HttpDownloader;
|
use Composer\Util\HttpDownloader;
|
||||||
use Composer\Advisory\Auditor;
|
use Composer\Advisory\Auditor;
|
||||||
|
@ -83,6 +84,7 @@ class UpdateCommand extends BaseCommand
|
||||||
new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'),
|
new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'),
|
||||||
new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'),
|
new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'),
|
||||||
new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During a partial update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'),
|
new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During a partial update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'),
|
||||||
|
new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Only allow patch version updates for currently installed dependencies.'),
|
||||||
new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'),
|
new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'),
|
||||||
new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'),
|
new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'),
|
||||||
new InputOption('bump-after-update', null, InputOption::VALUE_OPTIONAL, 'Runs bump after performing the update.', false, ['dev', 'no-dev', 'all']),
|
new InputOption('bump-after-update', null, InputOption::VALUE_OPTIONAL, 'Runs bump after performing the update.', false, ['dev', 'no-dev', 'all']),
|
||||||
|
@ -175,6 +177,26 @@ EOT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($input->getOption('patch-only')) {
|
||||||
|
if (!$composer->getLocker()->isLocked()) {
|
||||||
|
throw new \InvalidArgumentException('patch-only can only be used with a lock file present');
|
||||||
|
}
|
||||||
|
foreach ($composer->getLocker()->getLockedRepository(true)->getCanonicalPackages() as $package) {
|
||||||
|
if ($package->isDev()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!Preg::isMatch('{^(\d+\.\d+\.\d+)}', $package->getVersion(), $match)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$constraint = $parser->parseConstraints('~'.$match[1]);
|
||||||
|
if (isset($temporaryConstraints[$package->getName()])) {
|
||||||
|
$temporaryConstraints[$package->getName()] = MultiConstraint::create([$temporaryConstraints[$package->getName()], $constraint], true);
|
||||||
|
} else {
|
||||||
|
$temporaryConstraints[$package->getName()] = $constraint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($input->getOption('interactive')) {
|
if ($input->getOption('interactive')) {
|
||||||
$packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages);
|
$packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages);
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,69 @@ Your requirements could not be resolved to an installable set of packages.
|
||||||
- root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (dep/pkg:^2).
|
- root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (dep/pkg:^2).
|
||||||
OUTPUT
|
OUTPUT
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdateWithPatchOnly(): void
|
||||||
|
{
|
||||||
|
$this->initTempComposer([
|
||||||
|
'repositories' => [
|
||||||
|
'packages' => [
|
||||||
|
'type' => 'package',
|
||||||
|
'package' => [
|
||||||
|
['name' => 'root/req', 'version' => '1.0.0'],
|
||||||
|
['name' => 'root/req', 'version' => '1.0.1'],
|
||||||
|
['name' => 'root/req', 'version' => '1.1.0'],
|
||||||
|
['name' => 'root/req2', 'version' => '1.0.0'],
|
||||||
|
['name' => 'root/req2', 'version' => '1.0.1'],
|
||||||
|
['name' => 'root/req2', 'version' => '1.1.0'],
|
||||||
|
['name' => 'root/req3', 'version' => '1.0.0'],
|
||||||
|
['name' => 'root/req3', 'version' => '1.0.1'],
|
||||||
|
['name' => 'root/req3', 'version' => '1.1.0'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'require' => [
|
||||||
|
'root/req' => '1.*',
|
||||||
|
'root/req2' => '1.*',
|
||||||
|
'root/req3' => '1.*',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$package = self::getPackage('root/req', '1.0.0');
|
||||||
|
$package2 = self::getPackage('root/req2', '1.0.0');
|
||||||
|
$package3 = self::getPackage('root/req3', '1.0.0');
|
||||||
|
$this->createComposerLock([$package, $package2, $package3]);
|
||||||
|
|
||||||
|
$appTester = $this->getApplicationTester();
|
||||||
|
// root/req fails because of incompatible --with requirement
|
||||||
|
$appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true, '--no-install' => true, '--patch-only' => true, '--with' => ['root/req:^1.1']]));
|
||||||
|
|
||||||
|
$expected = <<<OUTPUT
|
||||||
|
Loading composer repositories with package information
|
||||||
|
Updating dependencies
|
||||||
|
Your requirements could not be resolved to an installable set of packages.
|
||||||
|
|
||||||
|
Problem 1
|
||||||
|
- Root composer.json requires root/req 1.*, found root/req[1.0.0, 1.0.1, 1.1.0] but it conflicts with your temporary update constraint (root/req:[[>= 1.1.0.0-dev < 2.0.0.0-dev] [>= 1.0.0.0-dev < 1.1.0.0-dev]]).
|
||||||
|
OUTPUT;
|
||||||
|
|
||||||
|
self::assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true)));
|
||||||
|
|
||||||
|
$appTester = $this->getApplicationTester();
|
||||||
|
// root/req upgrades to 1.0.1 as that is compatible with the --with requirement now
|
||||||
|
// root/req2 upgrades to 1.0.1 only due to --patch-only
|
||||||
|
// root/req3 does not update as it is not in the allowlist
|
||||||
|
$appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true, '--no-install' => true, '--patch-only' => true, '--with' => ['root/req:^1.0.1'], 'packages' => ['root/req', 'root/req2']]));
|
||||||
|
|
||||||
|
$expected = <<<OUTPUT
|
||||||
|
Loading composer repositories with package information
|
||||||
|
Updating dependencies
|
||||||
|
Lock file operations: 0 installs, 2 updates, 0 removals
|
||||||
|
- Upgrading root/req (1.0.0 => 1.0.1)
|
||||||
|
- Upgrading root/req2 (1.0.0 => 1.0.1)
|
||||||
|
OUTPUT;
|
||||||
|
|
||||||
|
self::assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInteractiveModeThrowsIfNoPackageToUpdate(): void
|
public function testInteractiveModeThrowsIfNoPackageToUpdate(): void
|
||||||
|
|
Loading…
Reference in New Issue