From 0d96fd8149a5277293da8ebeac5df5ad6fa641fb Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 19 Jan 2023 21:42:09 +0100 Subject: [PATCH] Warn when require ends up auto-selecting a feature branch, fixes #11264 (#11270) --- src/Composer/Command/RequireCommand.php | 15 ++++++- src/Composer/Console/Application.php | 2 +- .../Test/Command/RequireCommandTest.php | 42 +++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 14fb1ef95..bf33b28c2 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -338,7 +338,7 @@ EOT try { $result = $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey); if ($result === 0 && count($requirementsToGuess) > 0) { - $this->updateRequirementsAfterResolution($requirementsToGuess, $requireKey, $removeKey, $sortPackages, $input->getOption('dry-run')); + $result = $this->updateRequirementsAfterResolution($requirementsToGuess, $requireKey, $removeKey, $sortPackages, $input->getOption('dry-run')); } return $result; @@ -506,7 +506,7 @@ EOT /** * @param list $requirementsToUpdate */ - private function updateRequirementsAfterResolution(array $requirementsToUpdate, string $requireKey, string $removeKey, bool $sortPackages, bool $dryRun): void + private function updateRequirementsAfterResolution(array $requirementsToUpdate, string $requireKey, string $removeKey, bool $sortPackages, bool $dryRun): int { $composer = $this->requireComposer(); $locker = $composer->getLocker(); @@ -529,6 +529,15 @@ EOT $requirements[$packageName], $packageName )); + + if (Preg::isMatch('{^dev-(?!main$|master$|trunk$|latest$)}', $requirements[$packageName])) { + $this->getIO()->warning('Version '.$requirements[$packageName].' looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state'); + if ($this->getIO()->isInteractive() && !$this->getIO()->askConfirmation('Are you sure you want to use this constraint (Y) or would you rather abort (n) the whole operation [Y,n]? ')) { + $this->revertComposerFile(); + + return 1; + } + } } if (!$dryRun) { @@ -544,6 +553,8 @@ EOT $lock->write($lockData); } } + + return 0; } /** diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 20c7e3762..bdd6ef6d3 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -147,7 +147,7 @@ class Application extends BaseApplication $this->disableScriptsByDefault = $input->hasParameterOption('--no-scripts'); $stdin = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); - if (Platform::getEnv('COMPOSER_NO_INTERACTION') || $stdin === false || !Platform::isTty($stdin)) { + if (Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') !== '1' && (Platform::getEnv('COMPOSER_NO_INTERACTION') || $stdin === false || !Platform::isTty($stdin))) { $input->setInteractive(false); } diff --git a/tests/Composer/Test/Command/RequireCommandTest.php b/tests/Composer/Test/Command/RequireCommandTest.php index 837c32641..f85999214 100644 --- a/tests/Composer/Test/Command/RequireCommandTest.php +++ b/tests/Composer/Test/Command/RequireCommandTest.php @@ -40,6 +40,48 @@ class RequireCommandTest extends TestCase $appTester->run(['command' => 'require', '--dry-run' => true, '--no-audit' => true, 'packages' => ['required/pkg']]); } + public function testRequireWarnsIfResolvedToFeatureBranch(): void + { + $this->initTempComposer([ + 'repositories' => [ + 'packages' => [ + 'type' => 'package', + 'package' => [ + ['name' => 'required/pkg', 'version' => '2.0.0', 'require' => ['common/dep' => '^1']], + ['name' => 'required/pkg', 'version' => 'dev-foo-bar', 'require' => ['common/dep' => '^2']], + ['name' => 'common/dep', 'version' => '2.0.0'], + ], + ], + ], + 'require' => [ + 'common/dep' => '^2.0', + ], + 'minimum-stability' => 'dev', + 'prefer-stable' => true, + ]); + + $appTester = $this->getApplicationTester(); + $appTester->setInputs(['n']); + $appTester->run(['command' => 'require', '--dry-run' => true, '--no-audit' => true, 'packages' => ['required/pkg']], ['interactive' => true]); + self::assertSame( +'./composer.json has been updated +Running composer update required/pkg +Loading composer repositories with package information +Updating dependencies +Lock file operations: 2 installs, 0 updates, 0 removals + - Locking common/dep (2.0.0) + - Locking required/pkg (dev-foo-bar) +Installing dependencies from lock file (including require-dev) +Package operations: 2 installs, 0 updates, 0 removals + - Installing common/dep (2.0.0) + - Installing required/pkg (dev-foo-bar) +Using version dev-foo-bar for required/pkg +Version dev-foo-bar looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state +Are you sure you want to use this constraint (Y) or would you rather abort (n) the whole operation [Y,n]? '.' +Installation failed, reverting ./composer.json to its original content. +', $appTester->getDisplay(true)); + } + /** * @dataProvider provideRequire * @param array $composerJson