1
0
Fork 0

Show/outdated command fixes (#11000)

* Fix show command showing the split of direct/transitive deps from outdated, fixes #10999

* Fix a few minor edge cases in show command, add tests for show and outdated commands
pull/11003/head
Jordi Boggiano 2022-08-17 13:08:59 +03:00 committed by GitHub
parent 9b6d27f810
commit f95471f221
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 275 additions and 11 deletions

View File

@ -291,7 +291,7 @@ EOT
$hint .= ', try using --platform (-p) to show platform packages'; $hint .= ', try using --platform (-p) to show platform packages';
} }
if (!$input->getOption('all')) { if (!$input->getOption('all')) {
$hint .= ', try using --all (-a) to show all available packages'; $hint .= ', try using --available (-a) to show all available packages';
} }
throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.'); throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.');
@ -374,7 +374,7 @@ EOT
$packageFilterRegex = '{^'.str_replace('\\*', '.*?', preg_quote($packageFilter)).'$}i'; $packageFilterRegex = '{^'.str_replace('\\*', '.*?', preg_quote($packageFilter)).'$}i';
} }
$packageListFilter = array(); $packageListFilter = null;
if ($input->getOption('direct')) { if ($input->getOption('direct')) {
$packageListFilter = $this->getRootRequires(); $packageListFilter = $this->getRootRequires();
} }
@ -384,7 +384,7 @@ EOT
$input->setOption('path', false); $input->setOption('path', false);
} }
foreach ($repos->getRepositories() as $repo) { foreach (RepositoryUtils::flattenRepositories($repos) as $repo) {
if ($repo === $platformRepo) { if ($repo === $platformRepo) {
$type = 'platform'; $type = 'platform';
} elseif ($lockedRepo !== null && $repo === $lockedRepo) { } elseif ($lockedRepo !== null && $repo === $lockedRepo) {
@ -408,7 +408,7 @@ EOT
$package = $package->getAliasOf(); $package = $package->getAliasOf();
} }
if (!$packageFilterRegex || Preg::isMatch($packageFilterRegex, $package->getName())) { if (!$packageFilterRegex || Preg::isMatch($packageFilterRegex, $package->getName())) {
if (!$packageListFilter || in_array($package->getName(), $packageListFilter, true)) { if (null === $packageListFilter || in_array($package->getName(), $packageListFilter, true)) {
$packages[$type][$package->getName()] = $package; $packages[$type][$package->getName()] = $package;
} }
} }
@ -535,6 +535,7 @@ EOT
'nameLength' => $nameLength, 'nameLength' => $nameLength,
'versionLength' => $versionLength, 'versionLength' => $versionLength,
'latestLength' => $latestLength, 'latestLength' => $latestLength,
'writeLatest' => $writeLatest,
); );
if ($input->getOption('strict') && $hasOutdatedPackages) { if ($input->getOption('strict') && $hasOutdatedPackages) {
$exitCode = 1; $exitCode = 1;
@ -570,12 +571,13 @@ EOT
$nameLength = $viewMetaData[$type]['nameLength']; $nameLength = $viewMetaData[$type]['nameLength'];
$versionLength = $viewMetaData[$type]['versionLength']; $versionLength = $viewMetaData[$type]['versionLength'];
$latestLength = $viewMetaData[$type]['latestLength']; $latestLength = $viewMetaData[$type]['latestLength'];
$writeLatest = $viewMetaData[$type]['writeLatest'];
$writeVersion = $nameLength + $versionLength + 3 <= $width; $versionFits = $nameLength + $versionLength + 3 <= $width;
$writeLatest = $nameLength + $versionLength + $latestLength + 3 <= $width; $latestFits = $nameLength + $versionLength + $latestLength + 3 <= $width;
$writeDescription = $nameLength + $versionLength + $latestLength + 24 <= $width; $descriptionFits = $nameLength + $versionLength + $latestLength + 24 <= $width;
if ($writeLatest && !$io->isDecorated()) { if ($latestFits && !$io->isDecorated()) {
$latestLength += 2; $latestLength += 2;
} }
@ -601,19 +603,19 @@ EOT
$io->write(''); $io->write('');
$io->write('<info>Direct dependencies:</>'); $io->write('<info>Direct dependencies:</>');
if (\count($directDeps) > 0) { if (\count($directDeps) > 0) {
$this->printPackages($io, $directDeps, $indent, $writeVersion, $writeLatest, $writeDescription, $width, $versionLength, $nameLength, $latestLength); $this->printPackages($io, $directDeps, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
} else { } else {
$io->write('Everything up to date'); $io->write('Everything up to date');
} }
$io->write(''); $io->write('');
$io->write('<info>Transitive dependencies:</>'); $io->write('<info>Transitive dependencies:</>');
if (\count($transitiveDeps) > 0) { if (\count($transitiveDeps) > 0) {
$this->printPackages($io, $transitiveDeps, $indent, $writeVersion, $writeLatest, $writeDescription, $width, $versionLength, $nameLength, $latestLength); $this->printPackages($io, $transitiveDeps, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
} else { } else {
$io->write('Everything up to date'); $io->write('Everything up to date');
} }
} else { } else {
$this->printPackages($io, $packages, $indent, $writeVersion, $writeLatest, $writeDescription, $width, $versionLength, $nameLength, $latestLength); $this->printPackages($io, $packages, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
} }
if ($showAllTypes) { if ($showAllTypes) {

View File

@ -49,4 +49,30 @@ class RepositoryUtils
return $bucket; return $bucket;
} }
/**
* Unwraps CompositeRepository, InstalledRepository and optionally FilterRepository to get a flat array of pure repository instances
*
* @return RepositoryInterface[]
*/
public static function flattenRepositories(RepositoryInterface $repo, bool $unwrapFilterRepos = true): array
{
// unwrap filter repos
if ($unwrapFilterRepos && $repo instanceof FilterRepository) {
$repo = $repo->getRepository();
}
if (!$repo instanceof CompositeRepository) {
return [$repo];
}
$repos = [];
foreach ($repo->getRepositories() as $r) {
foreach (self::flattenRepositories($r, $unwrapFilterRepos) as $r2) {
$repos[] = $r2;
}
}
return $repos;
}
} }

View File

@ -0,0 +1,236 @@
<?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\Pcre\Preg;
use Composer\Pcre\Regex;
use Composer\Repository\PlatformRepository;
use Composer\Test\TestCase;
class ShowCommandTest extends TestCase
{
/**
* @dataProvider provideShow
* @param array<mixed> $command
* @param array<string, string> $requires
*/
public function testShow(array $command, string $expected, array $requires = []): void
{
$this->initTempComposer([
'repositories' => [
'packages' => [
'type' => 'package',
'package' => [
['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.0.0'],
['name' => 'outdated/major', 'description' => 'outdated/major v1.0.0 description', 'version' => '1.0.0'],
['name' => 'outdated/major', 'description' => 'outdated/major v1.0.1 description', 'version' => '1.0.1'],
['name' => 'outdated/major', 'description' => 'outdated/major v1.1.0 description', 'version' => '1.1.0'],
['name' => 'outdated/major', 'description' => 'outdated/major v1.1.1 description', 'version' => '1.1.1'],
['name' => 'outdated/major', 'description' => 'outdated/major v2.0.0 description', 'version' => '2.0.0'],
['name' => 'outdated/minor', 'description' => 'outdated/minor v1.0.0 description', 'version' => '1.0.0'],
['name' => 'outdated/minor', 'description' => 'outdated/minor v1.0.1 description', 'version' => '1.0.1'],
['name' => 'outdated/minor', 'description' => 'outdated/minor v1.1.0 description', 'version' => '1.1.0'],
['name' => 'outdated/minor', 'description' => 'outdated/minor v1.1.1 description', 'version' => '1.1.1'],
['name' => 'outdated/patch', 'description' => 'outdated/patch v1.0.0 description', 'version' => '1.0.0'],
['name' => 'outdated/patch', 'description' => 'outdated/patch v1.0.1 description', 'version' => '1.0.1'],
],
],
],
'require' => $requires === [] ? new \stdClass : $requires,
]);
$pkg = $this->getPackage('vendor/package', '1.0.0');
$pkg->setDescription('description of installed package');
$this->createInstalledJson([
$pkg,
$this->getPackage('outdated/major', '1.0.0'),
$this->getPackage('outdated/minor', '1.0.0'),
$this->getPackage('outdated/patch', '1.0.0'),
]);
$appTester = $this->getApplicationTester();
$appTester->run(array_merge(['command' => 'show'], $command));
self::assertSame(trim($expected), trim($appTester->getDisplay(true)));
}
public function provideShow(): \Generator
{
yield 'default shows installed with version and description' => [
[],
'outdated/major 1.0.0
outdated/minor 1.0.0
outdated/patch 1.0.0
vendor/package 1.0.0 description of installed package',
];
yield 'with -a show available packages with description but no version' => [
['-a' => true],
'outdated/major outdated/major v2.0.0 description
outdated/minor outdated/minor v1.1.1 description
outdated/patch outdated/patch v1.0.1 description
vendor/package generic description',
];
yield 'show with --direct shows nothing if no deps' => [
['--direct' => true],
'',
];
yield 'show with --direct shows only root deps' => [
['--direct' => true],
'outdated/major 1.0.0',
['outdated/major' => '*'],
];
yield 'outdated deps' => [
['command' => 'outdated'],
'Legend:
! patch or minor release available - update recommended
~ major release available - update possible
Direct dependencies:
Everything up to date
Transitive dependencies:
outdated/major 1.0.0 ~ 2.0.0
outdated/minor 1.0.0 <highlight>! 1.1.1</highlight>
outdated/patch 1.0.0 <highlight>! 1.0.1</highlight>',
];
yield 'outdated deps with --direct only show direct deps with updated' => [
['command' => 'outdated', '--direct' => true],
'Legend:
! patch or minor release available - update recommended
~ major release available - update possible
outdated/major 1.0.0 ~ 2.0.0',
[
'vendor/package' => '*',
'outdated/major' => '*',
],
];
yield 'outdated deps with --major-only only shows major updates' => [
['command' => 'outdated', '--major-only' => true],
'Legend:
! patch or minor release available - update recommended
~ major release available - update possible
Direct dependencies:
Everything up to date
Transitive dependencies:
outdated/major 1.0.0 ~ 2.0.0',
];
yield 'outdated deps with --minor-only only shows minor updates' => [
['command' => 'outdated', '--minor-only' => true],
'Legend:
! patch or minor release available - update recommended
~ major release available - update possible
Direct dependencies:
outdated/minor 1.0.0 <highlight>! 1.1.1</highlight>
Transitive dependencies:
outdated/major 1.0.0 <highlight>! 1.1.1</highlight>
outdated/patch 1.0.0 <highlight>! 1.0.1</highlight>',
['outdated/minor' => '*'],
];
yield 'outdated deps with --patch-only only shows patch updates' => [
['command' => 'outdated', '--patch-only' => true],
'Legend:
! patch or minor release available - update recommended
~ major release available - update possible
Direct dependencies:
Everything up to date
Transitive dependencies:
outdated/major 1.0.0 <highlight>! 1.0.1</highlight>
outdated/minor 1.0.0 <highlight>! 1.0.1</highlight>
outdated/patch 1.0.0 <highlight>! 1.0.1</highlight>',
];
}
public function testShowPlatformOnlyShowsPlatformPackages(): void
{
$this->initTempComposer([
'repositories' => [
'packages' => [
'type' => 'package',
'package' => [
['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.0.0'],
],
],
],
]);
$this->createInstalledJson([
$this->getPackage('vendor/package', '1.0.0'),
]);
$appTester = $this->getApplicationTester();
$appTester->run(['command' => 'show', '-p' => true]);
$output = trim($appTester->getDisplay(true));
foreach (Regex::matchAll('{^(\w+)}m', $output)->matches as $m) {
self::assertTrue(PlatformRepository::isPlatformPackage((string) $m[1]));
}
}
public function testShowAllShowsAllSections(): void
{
$this->initTempComposer([
'repositories' => [
'packages' => [
'type' => 'package',
'package' => [
['name' => 'vendor/available', 'description' => 'generic description', 'version' => '1.0.0'],
],
],
],
]);
$pkg = $this->getPackage('vendor/installed', '2.0.0');
$pkg->setDescription('description of installed package');
$this->createInstalledJson([
$pkg,
]);
$pkg = $this->getPackage('vendor/locked', '3.0.0');
$pkg->setDescription('description of locked package');
$this->createComposerLock([
$pkg,
]);
$appTester = $this->getApplicationTester();
$appTester->run(['command' => 'show', '--all' => true]);
$output = trim($appTester->getDisplay(true));
$output = Preg::replace('{platform:(\n .*)+}', 'platform: wiped', $output);
self::assertSame('platform: wiped
locked:
vendor/locked 3.0.0 description of locked package
available:
vendor/available generic description
installed:
vendor/installed 2.0.0 description of installed package', $output);
}
}