Skip to content

Commit

Permalink
Warn when require ends up auto-selecting a feature branch, fixes #11264
Browse files Browse the repository at this point in the history
… (#11270)
  • Loading branch information
Seldaek committed Jan 19, 2023
1 parent c7f3282 commit 0d96fd8
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 3 deletions.
15 changes: 13 additions & 2 deletions src/Composer/Command/RequireCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
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;
Expand Down Expand Up @@ -506,7 +506,7 @@ private function doUpdate(InputInterface $input, OutputInterface $output, IOInte
/**
* @param list<string> $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();
Expand All @@ -529,6 +529,15 @@ private function updateRequirementsAfterResolution(array $requirementsToUpdate,
$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 (<comment>Y</comment>) or would you rather abort (<comment>n</comment>) the whole operation [<comment>Y,n</comment>]? ')) {
$this->revertComposerFile();

return 1;
}
}
}

if (!$dryRun) {
Expand All @@ -544,6 +553,8 @@ private function updateRequirementsAfterResolution(array $requirementsToUpdate,
$lock->write($lockData);
}
}

return 0;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Composer/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public function doRun(InputInterface $input, OutputInterface $output): int
$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);
}

Expand Down
42 changes: 42 additions & 0 deletions tests/Composer/Test/Command/RequireCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,48 @@ public function testRequireThrowsIfNoneMatches(): void
$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
<warning>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</warning>
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<mixed> $composerJson
Expand Down

0 comments on commit 0d96fd8

Please sign in to comment.