pull/10885/head
parent
73fd0f22e8
commit
70f2dd6edd
|
@ -322,6 +322,24 @@ uninstalled.
|
||||||
* **--apcu-autoloader-prefix:** Use a custom prefix for the APCu autoloader cache.
|
* **--apcu-autoloader-prefix:** Use a custom prefix for the APCu autoloader cache.
|
||||||
Implicitly enables `--apcu-autoloader`.
|
Implicitly enables `--apcu-autoloader`.
|
||||||
|
|
||||||
|
## bump
|
||||||
|
|
||||||
|
The `bump` command increases the lower limit of your composer.json requirements
|
||||||
|
to the currently installed versions. This helps to ensure your dependencies do not
|
||||||
|
accidentally get downgraded due to some other conflict, and can slightly improve
|
||||||
|
dependency resolution performance as it limits the amount of package versions
|
||||||
|
Composer has to look at.
|
||||||
|
|
||||||
|
Running this blindly on libraries is **NOT** recommended as it will narrow down
|
||||||
|
your allowed dependencies, which may cause dependency hell for your users.
|
||||||
|
Running it with `--dev-only` on libraries may be fine however as dev requirements
|
||||||
|
are local to the library and do not affect consumers of the package.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
* **--dev-only:** Only bump requirements in "require-dev".
|
||||||
|
* **--no-dev-only:** Only bump requirements in "require".
|
||||||
|
|
||||||
## reinstall
|
## reinstall
|
||||||
|
|
||||||
The `reinstall` command looks up installed packages by name,
|
The `reinstall` command looks up installed packages by name,
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Command;
|
||||||
|
|
||||||
|
use Composer\DependencyResolver\Request;
|
||||||
|
use Composer\Package\AliasPackage;
|
||||||
|
use Composer\Package\Locker;
|
||||||
|
use Composer\Package\Version\VersionBumper;
|
||||||
|
use Composer\Package\Version\VersionSelector;
|
||||||
|
use Composer\Util\Filesystem;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Composer\Console\Input\InputArgument;
|
||||||
|
use Composer\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Composer\Factory;
|
||||||
|
use Composer\Installer;
|
||||||
|
use Composer\Installer\InstallerEvents;
|
||||||
|
use Composer\Json\JsonFile;
|
||||||
|
use Composer\Json\JsonManipulator;
|
||||||
|
use Composer\Package\Version\VersionParser;
|
||||||
|
use Composer\Package\Loader\ArrayLoader;
|
||||||
|
use Composer\Package\BasePackage;
|
||||||
|
use Composer\Plugin\CommandEvent;
|
||||||
|
use Composer\Plugin\PluginEvents;
|
||||||
|
use Composer\Repository\CompositeRepository;
|
||||||
|
use Composer\Repository\PlatformRepository;
|
||||||
|
use Composer\IO\IOInterface;
|
||||||
|
use Composer\Util\Silencer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*/
|
||||||
|
final class BumpCommand extends BaseCommand
|
||||||
|
{
|
||||||
|
private const ERROR_GENERIC = 1;
|
||||||
|
private const ERROR_LOCK_OUTDATED = 2;
|
||||||
|
|
||||||
|
use CompletionTrait;
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('bump')
|
||||||
|
->setDescription('Increases the lower limit of your composer.json requirements to the currently installed versions.')
|
||||||
|
->setDefinition(array(
|
||||||
|
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name(s) to restrict which packages are bumped.', null, $this->suggestRootRequirement()),
|
||||||
|
new InputOption('dev-only', 'D', InputOption::VALUE_NONE, 'Only bump requirements in "require-dev".'),
|
||||||
|
new InputOption('no-dev-only', 'R', InputOption::VALUE_NONE, 'Only bump requirements in "require".'),
|
||||||
|
))
|
||||||
|
->setHelp(
|
||||||
|
<<<EOT
|
||||||
|
The <info>bump</info> command increases the lower limit of your composer.json requirements
|
||||||
|
to the currently installed versions. This helps to ensure your dependencies do not
|
||||||
|
accidentally get downgraded due to some other conflict, and can slightly improve
|
||||||
|
dependency resolution performance as it limits the amount of package versions
|
||||||
|
Composer has to look at.
|
||||||
|
|
||||||
|
Running this blindly on libraries is **NOT** recommended as it will narrow down
|
||||||
|
your allowed dependencies, which may cause dependency hell for your users.
|
||||||
|
Running it with <info>--dev-only</info> on libraries may be fine however as dev requirements
|
||||||
|
are local to the library and do not affect consumers of the package.
|
||||||
|
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Seld\JsonLint\ParsingException
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
/** @readonly */
|
||||||
|
$composerJsonPath = Factory::getComposerFile();
|
||||||
|
$io = $this->getIO();
|
||||||
|
|
||||||
|
if (!Filesystem::isReadable($composerJsonPath)) {
|
||||||
|
$io->writeError('<error>'.$composerJsonPath.' is not readable.</error>');
|
||||||
|
|
||||||
|
return self::ERROR_GENERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
$composerJson = new JsonFile($composerJsonPath);
|
||||||
|
$contents = file_get_contents($composerJson->getPath());
|
||||||
|
if (false === $contents) {
|
||||||
|
$io->writeError('<error>'.$composerJsonPath.' is not readable.</error>');
|
||||||
|
|
||||||
|
return self::ERROR_GENERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for writability by writing to the file as is_writable can not be trusted on network-mounts
|
||||||
|
// see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
|
||||||
|
if (!is_writable($composerJsonPath) && false === Silencer::call('file_put_contents', $composerJsonPath, $contents)) {
|
||||||
|
$io->writeError('<error>'.$composerJsonPath.' is not writable.</error>');
|
||||||
|
|
||||||
|
return self::ERROR_GENERIC;
|
||||||
|
}
|
||||||
|
unset($contents);
|
||||||
|
|
||||||
|
$composer = $this->requireComposer();
|
||||||
|
if ($composer->getLocker()->isLocked()) {
|
||||||
|
if (!$composer->getLocker()->isFresh()) {
|
||||||
|
$io->writeError('<error>The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.</error>');
|
||||||
|
|
||||||
|
return self::ERROR_LOCK_OUTDATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
$repo = $composer->getLocker()->getLockedRepository(true);
|
||||||
|
} else {
|
||||||
|
$repo = $composer->getRepositoryManager()->getLocalRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($composer->getPackage()->getType() !== 'project' && !$input->getOption('dev-only')) {
|
||||||
|
$io->writeError('<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>');
|
||||||
|
|
||||||
|
$contents = $composerJson->read();
|
||||||
|
if (!isset($contents['type'])) {
|
||||||
|
$io->writeError('<warning>If your package is not a library, you can explicitly specify the "type" by using "composer config type project".</warning>');
|
||||||
|
$io->writeError('<warning>Alternatively you can use --dev to only bump dependencies within "require-dev".</warning>');
|
||||||
|
}
|
||||||
|
unset($contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bumper = new VersionBumper();
|
||||||
|
$tasks = [];
|
||||||
|
if (!$input->getOption('no-dev-only')) {
|
||||||
|
$tasks['require-dev'] = $composer->getPackage()->getDevRequires();
|
||||||
|
};
|
||||||
|
if (!$input->getOption('dev-only')) {
|
||||||
|
$tasks['require'] = $composer->getPackage()->getRequires();
|
||||||
|
}
|
||||||
|
|
||||||
|
$updates = [];
|
||||||
|
foreach ($tasks as $key => $reqs) {
|
||||||
|
foreach ($reqs as $pkgName => $link) {
|
||||||
|
if (PlatformRepository::isPlatformPackage($pkgName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$currentConstraint = $link->getPrettyConstraint();
|
||||||
|
|
||||||
|
$package = $repo->findPackage($pkgName, '*');
|
||||||
|
// name must be provided or replaced
|
||||||
|
if (null === $package) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
while ($package instanceof AliasPackage) {
|
||||||
|
$package = $package->getAliasOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
$bumped = $bumper->bumpRequirement($link->getConstraint(), $package);
|
||||||
|
|
||||||
|
if ($bumped === $currentConstraint) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$updates[$key][$pkgName] = $bumped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->updateFileCleanly($composerJson, $updates)) {
|
||||||
|
$composerDefinition = $composerJson->read();
|
||||||
|
foreach ($updates as $key => $packages) {
|
||||||
|
foreach ($packages as $package => $version) {
|
||||||
|
$composerDefinition[$key][$package] = $version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$composerJson->write($composerDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
$changeCount = array_sum(array_map('count', $updates));
|
||||||
|
if ($changeCount > 0) {
|
||||||
|
$io->write('<info>'.$composerJsonPath.' has been updated ('.$changeCount.' changes).</info>');
|
||||||
|
} else {
|
||||||
|
$io->write('<info>No requirements to update in '.$composerJsonPath.'.</info>');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($composer->getLocker()->isLocked() && $changeCount > 0) {
|
||||||
|
$contents = file_get_contents($composerJson->getPath());
|
||||||
|
if (false === $contents) {
|
||||||
|
throw new \RuntimeException('Unable to read '.$composerJson->getPath().' contents to update the lock file hash.');
|
||||||
|
}
|
||||||
|
$lock = new JsonFile(Factory::getLockFile($composerJsonPath));
|
||||||
|
$lockData = $lock->read();
|
||||||
|
$lockData['content-hash'] = Locker::getContentHash($contents);
|
||||||
|
$lock->write($lockData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<'require'|'require-dev', array<string, string>> $updates
|
||||||
|
*/
|
||||||
|
private function updateFileCleanly(JsonFile $json, array $updates): bool
|
||||||
|
{
|
||||||
|
$contents = file_get_contents($json->getPath());
|
||||||
|
if (false === $contents) {
|
||||||
|
throw new \RuntimeException('Unable to read '.$json->getPath().' contents.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$manipulator = new JsonManipulator($contents);
|
||||||
|
|
||||||
|
foreach ($updates as $key => $packages) {
|
||||||
|
foreach ($packages as $package => $version) {
|
||||||
|
if (!$manipulator->addLink($key, $package, $version)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === file_put_contents($json->getPath(), $manipulator->getContents())) {
|
||||||
|
throw new \RuntimeException('Unable to write new '.$json->getPath().' contents.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,18 @@ trait CompletionTrait
|
||||||
return ['dist', 'source', 'auto'];
|
return ['dist', 'source', 'auto'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suggest package names from root requirements.
|
||||||
|
*/
|
||||||
|
private function suggestRootRequirement(): \Closure
|
||||||
|
{
|
||||||
|
return function (CompletionInput $input): array {
|
||||||
|
$composer = $this->requireComposer();
|
||||||
|
|
||||||
|
return array_merge(array_keys($composer->getPackage()->getRequires()), array_keys($composer->getPackage()->getDevRequires()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suggest package names from installed.
|
* Suggest package names from installed.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -116,13 +116,6 @@ EOT
|
||||||
*/
|
*/
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
|
|
||||||
pcntl_async_signals(true);
|
|
||||||
pcntl_signal(SIGINT, function () { $this->revertComposerFile(); });
|
|
||||||
pcntl_signal(SIGTERM, function () { $this->revertComposerFile(); });
|
|
||||||
pcntl_signal(SIGHUP, function () { $this->revertComposerFile(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->file = Factory::getComposerFile();
|
$this->file = Factory::getComposerFile();
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
|
|
||||||
|
@ -151,9 +144,16 @@ EOT
|
||||||
$this->composerBackup = file_get_contents($this->json->getPath());
|
$this->composerBackup = file_get_contents($this->json->getPath());
|
||||||
$this->lockBackup = file_exists($this->lock) ? file_get_contents($this->lock) : null;
|
$this->lockBackup = file_exists($this->lock) ? file_get_contents($this->lock) : null;
|
||||||
|
|
||||||
|
if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
|
||||||
|
pcntl_async_signals(true);
|
||||||
|
pcntl_signal(SIGINT, function () { $this->revertComposerFile(); });
|
||||||
|
pcntl_signal(SIGTERM, function () { $this->revertComposerFile(); });
|
||||||
|
pcntl_signal(SIGHUP, function () { $this->revertComposerFile(); });
|
||||||
|
}
|
||||||
|
|
||||||
// check for writability by writing to the file as is_writable can not be trusted on network-mounts
|
// check for writability by writing to the file as is_writable can not be trusted on network-mounts
|
||||||
// see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
|
// see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
|
||||||
if (!is_writable($this->file) && !Silencer::call('file_put_contents', $this->file, $this->composerBackup)) {
|
if (!is_writable($this->file) && false === Silencer::call('file_put_contents', $this->file, $this->composerBackup)) {
|
||||||
$io->writeError('<error>'.$this->file.' is not writable.</error>');
|
$io->writeError('<error>'.$this->file.' is not writable.</error>');
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -168,10 +168,10 @@ EOT
|
||||||
* @see https://github.com/composer/composer/pull/8313#issuecomment-532637955
|
* @see https://github.com/composer/composer/pull/8313#issuecomment-532637955
|
||||||
*/
|
*/
|
||||||
if ($packageType !== 'project') {
|
if ($packageType !== 'project') {
|
||||||
$io->writeError('<error>"--fixed" option is allowed for "project" package types only to prevent possible misuses.</error>');
|
$io->writeError('<error>The "--fixed" option is only allowed for packages with a "project" type to prevent possible misuses.</error>');
|
||||||
|
|
||||||
if (empty($config['type'])) {
|
if (!isset($config['type'])) {
|
||||||
$io->writeError('<error>If your package is not library, you should explicitly specify "type" parameter in composer.json.</error>');
|
$io->writeError('<error>If your package is not a library, you can explicitly specify the "type" by using "composer config type project".</error>');
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -547,6 +547,7 @@ class Application extends BaseApplication
|
||||||
new Command\CheckPlatformReqsCommand(),
|
new Command\CheckPlatformReqsCommand(),
|
||||||
new Command\FundCommand(),
|
new Command\FundCommand(),
|
||||||
new Command\ReinstallCommand(),
|
new Command\ReinstallCommand(),
|
||||||
|
new Command\BumpCommand(),
|
||||||
));
|
));
|
||||||
|
|
||||||
if (strpos(__FILE__, 'phar:') === 0 || '1' === Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) {
|
if (strpos(__FILE__, 'phar:') === 0 || '1' === Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) {
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Package\Version;
|
||||||
|
|
||||||
|
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
|
||||||
|
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
|
||||||
|
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
|
||||||
|
use Composer\Package\BasePackage;
|
||||||
|
use Composer\Package\AliasPackage;
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Composer;
|
||||||
|
use Composer\Package\Loader\ArrayLoader;
|
||||||
|
use Composer\Package\Dumper\ArrayDumper;
|
||||||
|
use Composer\Pcre\Preg;
|
||||||
|
use Composer\Repository\RepositorySet;
|
||||||
|
use Composer\Repository\PlatformRepository;
|
||||||
|
use Composer\Semver\Constraint\Constraint;
|
||||||
|
use Composer\Semver\Constraint\ConstraintInterface;
|
||||||
|
use Composer\Semver\Intervals;
|
||||||
|
use Composer\Util\Platform;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class VersionBumper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Given a constraint, this returns a new constraint with
|
||||||
|
* the lower bound bumped to match the given package's version.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
* * ^1.0 + 1.2.1 -> ^1.2.1
|
||||||
|
* * ^1.2 + 1.2.0 -> ^1.2
|
||||||
|
* * ^1.2 || ^2.3 + 1.3.0 -> ^1.3 || ^2.3
|
||||||
|
* * ^1.2 || ^2.3 + 2.4.0 -> ^1.2 || ^2.4
|
||||||
|
* * ^3@dev + 3.2.99999-dev -> ^3.2@dev
|
||||||
|
* * ~2 + 2.0-beta.1 -> ~2
|
||||||
|
* * dev-master + dev-master -> dev-master
|
||||||
|
*/
|
||||||
|
public function bumpRequirement(ConstraintInterface $constraint, PackageInterface $package): string
|
||||||
|
{
|
||||||
|
$parser = new VersionParser();
|
||||||
|
$prettyConstraint = $constraint->getPrettyString();
|
||||||
|
if (str_starts_with($constraint->getPrettyString(), 'dev-')) {
|
||||||
|
return $prettyConstraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = $package->getVersion();
|
||||||
|
if (str_starts_with($package->getVersion(), 'dev-')) {
|
||||||
|
$loader = new ArrayLoader($parser);
|
||||||
|
$dumper = new ArrayDumper();
|
||||||
|
$extra = $loader->getBranchAlias($dumper->dump($package));
|
||||||
|
|
||||||
|
// dev packages without branch alias cannot be processed
|
||||||
|
if (null === $extra || $extra === VersionParser::DEFAULT_BRANCH_ALIAS) {
|
||||||
|
return $prettyConstraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = $extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
$intervals = Intervals::get($constraint);
|
||||||
|
|
||||||
|
// complex constraints with branch names are not bumped
|
||||||
|
if (\count($intervals['branches']['names']) > 0) {
|
||||||
|
return $prettyConstraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
$major = Preg::replace('{^(\d+).*}', '$1', $version);
|
||||||
|
$newPrettyConstraint = '^'.Preg::replace('{(?:\.(?:0|9999999))+(-dev)?$}', '', $version);
|
||||||
|
|
||||||
|
// not a simple stable version, abort
|
||||||
|
if (!Preg::isMatch('{^\^\d+(\.\d+)*$}', $newPrettyConstraint)) {
|
||||||
|
return $prettyConstraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = '{
|
||||||
|
(?<=,|\ |\||^) # leading separator
|
||||||
|
(?P<constraint>
|
||||||
|
\^'.$major.'(?:\.\d+)* # e.g. ^2.anything
|
||||||
|
| ~'.$major.'(?:\.\d+)? # e.g. ~2 or ~2.2 but no more
|
||||||
|
| '.$major.'(?:\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc
|
||||||
|
)
|
||||||
|
(?=,|$|\ |\||@) # trailing separator
|
||||||
|
}x';
|
||||||
|
if (Preg::isMatchAllWithOffsets($pattern, $prettyConstraint, $matches)) {
|
||||||
|
$modified = $prettyConstraint;
|
||||||
|
foreach (array_reverse($matches['constraint']) as $match) {
|
||||||
|
$modified = substr_replace($modified, $newPrettyConstraint, $match[1], Platform::strlen($match[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is strictly equal to the previous one then no need to change anything
|
||||||
|
$newConstraint = $parser->parseConstraints($modified);
|
||||||
|
if (Intervals::isSubsetOf($newConstraint, $constraint) && Intervals::isSubsetOf($constraint, $newConstraint)) {
|
||||||
|
return $prettyConstraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $prettyConstraint;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Test\Command;
|
||||||
|
|
||||||
|
use Composer\Json\JsonFile;
|
||||||
|
use Composer\Test\TestCase;
|
||||||
|
|
||||||
|
class BumpCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideTests
|
||||||
|
* @param array<mixed> $composerJson
|
||||||
|
* @param array<mixed> $command
|
||||||
|
* @param array<mixed> $expected
|
||||||
|
*/
|
||||||
|
public function testBump(array $composerJson, array $command, array $expected, bool $lock = true): void
|
||||||
|
{
|
||||||
|
$this->initTempComposer($composerJson);
|
||||||
|
|
||||||
|
$packages = [
|
||||||
|
$this->getPackage('first/pkg', '2.3.4'),
|
||||||
|
$this->getPackage('second/pkg', '3.4.0'),
|
||||||
|
];
|
||||||
|
$devPackages = [
|
||||||
|
$this->getPackage('dev/pkg', '2.3.4.5'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->createInstalledJson($packages, $devPackages);
|
||||||
|
if ($lock) {
|
||||||
|
$this->createComposerLock($packages, $devPackages);
|
||||||
|
}
|
||||||
|
|
||||||
|
$appTester = $this->getApplicationTester();
|
||||||
|
$appTester->run(array_merge(['command' => 'bump'], $command));
|
||||||
|
|
||||||
|
$json = new JsonFile('./composer.json');
|
||||||
|
$this->assertSame($expected, $json->read());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideTests(): \Generator
|
||||||
|
{
|
||||||
|
yield 'bump all by default' => [
|
||||||
|
[
|
||||||
|
'require' => [
|
||||||
|
'first/pkg' => '^2.0',
|
||||||
|
'second/pkg' => '3.*',
|
||||||
|
],
|
||||||
|
'require-dev' => [
|
||||||
|
'dev/pkg' => '~2.0',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'require' => [
|
||||||
|
'first/pkg' => '^2.3.4',
|
||||||
|
'second/pkg' => '^3.4',
|
||||||
|
],
|
||||||
|
'require-dev' => [
|
||||||
|
'dev/pkg' => '^2.3.4.5',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'bump only dev with --dev-only' => [
|
||||||
|
[
|
||||||
|
'require' => [
|
||||||
|
'first/pkg' => '^2.0',
|
||||||
|
'second/pkg' => '3.*',
|
||||||
|
],
|
||||||
|
'require-dev' => [
|
||||||
|
'dev/pkg' => '~2.0',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
['--dev-only' => true],
|
||||||
|
[
|
||||||
|
'require' => [
|
||||||
|
'first/pkg' => '^2.0',
|
||||||
|
'second/pkg' => '3.*',
|
||||||
|
],
|
||||||
|
'require-dev' => [
|
||||||
|
'dev/pkg' => '^2.3.4.5',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'bump only non-dev with --no-dev-only' => [
|
||||||
|
[
|
||||||
|
'require' => [
|
||||||
|
'first/pkg' => '^2.0',
|
||||||
|
'second/pkg' => '3.*',
|
||||||
|
],
|
||||||
|
'require-dev' => [
|
||||||
|
'dev/pkg' => '~2.0',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
['--no-dev-only' => true],
|
||||||
|
[
|
||||||
|
'require' => [
|
||||||
|
'first/pkg' => '^2.3.4',
|
||||||
|
'second/pkg' => '^3.4',
|
||||||
|
],
|
||||||
|
'require-dev' => [
|
||||||
|
'dev/pkg' => '~2.0',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'bump works from installed repo without lock file' => [
|
||||||
|
[
|
||||||
|
'require' => [
|
||||||
|
'first/pkg' => '^2.0',
|
||||||
|
'second/pkg' => '3.*',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'require' => [
|
||||||
|
'first/pkg' => '^2.3.4',
|
||||||
|
'second/pkg' => '^3.4',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
false
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ class FactoryMock extends Factory
|
||||||
$rm->setLocalRepository(new InstalledArrayRepository);
|
$rm->setLocalRepository(new InstalledArrayRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createInstallationManager(Loop $loop, IOInterface $io, EventDispatcher $dispatcher = null): InstallationManager
|
public function createInstallationManager(Loop $loop = null, IOInterface $io = null, EventDispatcher $dispatcher = null): InstallationManager
|
||||||
{
|
{
|
||||||
return new InstallationManagerMock();
|
return new InstallationManagerMock();
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ class InstallationManagerMock extends InstallationManager
|
||||||
|
|
||||||
public function getInstallPath(PackageInterface $package): string
|
public function getInstallPath(PackageInterface $package): string
|
||||||
{
|
{
|
||||||
return '';
|
return 'vendor/'.$package->getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool
|
public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Test\Package\Version;
|
||||||
|
|
||||||
|
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
|
||||||
|
use Composer\Package\Version\VersionBumper;
|
||||||
|
use Composer\Package\Version\VersionSelector;
|
||||||
|
use Composer\Package\Package;
|
||||||
|
use Composer\Package\Link;
|
||||||
|
use Composer\Package\AliasPackage;
|
||||||
|
use Composer\Repository\PlatformRepository;
|
||||||
|
use Composer\Package\Version\VersionParser;
|
||||||
|
use Composer\Test\TestCase;
|
||||||
|
use Generator;
|
||||||
|
|
||||||
|
class VersionBumperTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideBumpRequirementTests
|
||||||
|
*/
|
||||||
|
public function testBumpRequirement(string $requirement, string $prettyVersion, string $expectedRequirement, ?string $branchAlias = null): void
|
||||||
|
{
|
||||||
|
$versionBumper = new VersionBumper();
|
||||||
|
$versionParser = new VersionParser();
|
||||||
|
|
||||||
|
$package = new Package('foo/bar', $versionParser->normalize($prettyVersion), $prettyVersion);
|
||||||
|
|
||||||
|
if ($branchAlias !== null) {
|
||||||
|
$package->setExtra(array('branch-alias' => array($prettyVersion => $branchAlias)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$newConstraint = $versionBumper->bumpRequirement($versionParser->parseConstraints($requirement), $package);
|
||||||
|
|
||||||
|
// assert that the recommended version is what we expect
|
||||||
|
$this->assertSame($expectedRequirement, $newConstraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideBumpRequirementTests(): Generator
|
||||||
|
{
|
||||||
|
// constraint, version, expected recommendation, [branch-alias]
|
||||||
|
yield 'upgrade caret' => ['^1.0', '1.2.1', '^1.2.1'];
|
||||||
|
yield 'skip trailing .0s' => ['^1.0', '1.0.0', '^1.0'];
|
||||||
|
yield 'skip trailing .0s/2' => ['^1.2', '1.2.0', '^1.2'];
|
||||||
|
yield 'preserve multi constraints' => ['^1.2 || ^2.3', '1.3.2', '^1.3.2 || ^2.3'];
|
||||||
|
yield 'preserve multi constraints/2' => ['^1.2 || ^2.3', '2.4.0', '^1.2 || ^2.4'];
|
||||||
|
yield 'preserve multi constraints/3' => ['^1.2 || ^2.3 || ^2', '2.4.0', '^1.2 || ^2.4 || ^2.4'];
|
||||||
|
yield '@dev is preserved' => ['^3@dev', '3.2.x-dev', '^3.2@dev'];
|
||||||
|
yield 'non-stable versions abort upgrades' => ['~2', '2.1-beta.1', '~2'];
|
||||||
|
yield 'dev reqs are skipped' => ['dev-main', 'dev-foo', 'dev-main'];
|
||||||
|
yield 'dev version does not upgrade' => ['^3.2', 'dev-main', '^3.2'];
|
||||||
|
yield 'upgrade dev version if aliased' => ['^3.2', 'dev-main', '^3.3', '3.3.x-dev'];
|
||||||
|
yield 'upgrade major wildcard to caret' => ['2.*', '2.4.0', '^2.4'];
|
||||||
|
yield 'upgrade major wildcard as x to caret' => ['2.x.x', '2.4.0', '^2.4'];
|
||||||
|
yield 'leave minor wildcard alone' => ['2.4.*', '2.4.3', '2.4.*'];
|
||||||
|
yield 'leave patch wildcard alone' => ['2.4.3.*', '2.4.3.2', '2.4.3.*'];
|
||||||
|
yield 'upgrade tilde to caret when compatible' => ['~2.2', '2.4.3', '^2.4.3'];
|
||||||
|
yield 'leave patch-only-tilde alone' => ['~2.2.3', '2.2.6', '~2.2.3'];
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,10 +16,13 @@ use Composer\Config;
|
||||||
use Composer\Console\Application;
|
use Composer\Console\Application;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Json\JsonFile;
|
use Composer\Json\JsonFile;
|
||||||
|
use Composer\Package\Locker;
|
||||||
use Composer\Pcre\Preg;
|
use Composer\Pcre\Preg;
|
||||||
|
use Composer\Repository\InstalledFilesystemRepository;
|
||||||
use Composer\Semver\VersionParser;
|
use Composer\Semver\VersionParser;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Composer\Semver\Constraint\Constraint;
|
use Composer\Semver\Constraint\Constraint;
|
||||||
|
use Composer\Test\Mock\FactoryMock;
|
||||||
use Composer\Test\Mock\HttpDownloaderMock;
|
use Composer\Test\Mock\HttpDownloaderMock;
|
||||||
use Composer\Test\Mock\ProcessExecutorMock;
|
use Composer\Test\Mock\ProcessExecutorMock;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
@ -109,6 +112,9 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
|
||||||
*
|
*
|
||||||
* The directory will be cleaned up on tearDown automatically.
|
* The directory will be cleaned up on tearDown automatically.
|
||||||
*
|
*
|
||||||
|
* @see createInstalledJson
|
||||||
|
* @see createComposerLock
|
||||||
|
* @see getApplicationTester
|
||||||
* @param mixed[] $composerJson
|
* @param mixed[] $composerJson
|
||||||
* @param mixed[] $authJson
|
* @param mixed[] $authJson
|
||||||
* @return string the newly created temp dir
|
* @return string the newly created temp dir
|
||||||
|
@ -138,6 +144,44 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
|
||||||
return $dir;
|
return $dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a vendor/composer/installed.json in CWD with the given packages
|
||||||
|
*
|
||||||
|
* @param PackageInterface[] $packages
|
||||||
|
* @param PackageInterface[] $devPackages
|
||||||
|
*/
|
||||||
|
protected function createInstalledJson(array $packages = [], array $devPackages = [], bool $devMode = true): void
|
||||||
|
{
|
||||||
|
mkdir('vendor/composer', 0777, true);
|
||||||
|
$repo = new InstalledFilesystemRepository(new JsonFile('vendor/composer/installed.json'));
|
||||||
|
$repo->setDevPackageNames(array_map(function (PackageInterface $pkg) { return $pkg->getPrettyName(); }, $devPackages));
|
||||||
|
foreach ($packages as $pkg) {
|
||||||
|
$repo->addPackage($pkg);
|
||||||
|
mkdir('vendor/'.$pkg->getName(), 0777, true);
|
||||||
|
}
|
||||||
|
foreach ($devPackages as $pkg) {
|
||||||
|
$repo->addPackage($pkg);
|
||||||
|
mkdir('vendor/'.$pkg->getName(), 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$factory = new FactoryMock();
|
||||||
|
$repo->write($devMode, $factory->createInstallationManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a composer.lock in CWD with the given packages
|
||||||
|
*
|
||||||
|
* @param PackageInterface[] $packages
|
||||||
|
* @param PackageInterface[] $devPackages
|
||||||
|
*/
|
||||||
|
protected function createComposerLock(array $packages = [], array $devPackages = []): void
|
||||||
|
{
|
||||||
|
$factory = new FactoryMock();
|
||||||
|
|
||||||
|
$locker = new Locker($this->getMockBuilder(IOInterface::class)->getMock(), new JsonFile('./composer.lock'), $factory->createInstallationManager(), (string) file_get_contents('./composer.json'));
|
||||||
|
$locker->setLockData($packages, $devPackages, [], [], [], 'dev', [], false, false, []);
|
||||||
|
}
|
||||||
|
|
||||||
public function getApplicationTester(): ApplicationTester
|
public function getApplicationTester(): ApplicationTester
|
||||||
{
|
{
|
||||||
$application = new Application();
|
$application = new Application();
|
||||||
|
|
Loading…
Reference in New Issue