Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --major-only flag to outdated/show commands to restrict the list to packages with major updates available #10827

Merged
merged 1 commit into from Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 9 additions & 3 deletions doc/03-cli.md
Expand Up @@ -479,7 +479,7 @@ php composer.phar show monolog/monolog 1.0.2

### Options

* **--all :** List all packages available in all your repositories.
* **--all:** List all packages available in all your repositories.
* **--installed (-i):** List the packages that are installed (this is enabled by default, and deprecated).
* **--locked:** List the locked packages from composer.lock.
* **--platform (-p):** List only platform packages (php & extensions).
Expand All @@ -490,8 +490,11 @@ php composer.phar show monolog/monolog 1.0.2
* **--tree (-t):** List your dependencies as a tree. If you pass a package name it will show the dependency tree for that package.
* **--latest (-l):** List all installed packages including their latest version.
* **--outdated (-o):** Implies --latest, but this lists *only* packages that have a newer version available.
* **--ignore:** Ignore specified package(s). Use it with the --outdated option if you don't want to be informed about new versions of some packages
* **--no-dev:** Filters dev dependencies from the package list.
* **--minor-only (-m):** Use with --latest. Only shows packages that have minor SemVer-compatible updates.
* **--major-only (-M):** Use with --latest or --outdated. Only shows packages that have major SemVer-compatible updates.
* **--minor-only (-m):** Use with --latest or --outdated. Only shows packages that have minor SemVer-compatible updates.
* **--patch-only (-p):** Use with --latest or --outdated. Only shows packages that have patch-level SemVer-compatible updates.
* **--direct (-D):** Restricts the list of packages to your direct dependencies.
* **--strict:** Return a non-zero exit code when there are outdated packages.
* **--format (-f):** Lets you pick between text (default) or json output format.
Expand All @@ -518,10 +521,13 @@ The color coding is as such:

### Options

* **--all (-a):** Show all packages, not just outdated (alias for `composer show -l`).
* **--all (-a):** Show all packages, not just outdated (alias for `composer show --latest`).
* **--direct (-D):** Restricts the list of packages to your direct dependencies.
* **--strict:** Returns non-zero exit code if any package is outdated.
* **--ignore:** Ignore specified package(s). Use it if you don't want to be informed about new versions of some packages
* **--major-only (-M):** Only shows packages that have major SemVer-compatible updates.
* **--minor-only (-m):** Only shows packages that have minor SemVer-compatible updates.
* **--patch-only (-p):** Only shows packages that have patch-level SemVer-compatible updates.
* **--format (-f):** Lets you pick between text (default) or json output format.
* **--no-dev:** Do not show outdated dev dependencies.
* **--locked:** Shows updates for packages from the lock file, regardless of what is currently in vendor dir.
Expand Down
10 changes: 7 additions & 3 deletions src/Composer/Command/OutdatedCommand.php
Expand Up @@ -40,10 +40,11 @@ protected function configure(): void
new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows updates for packages from the lock file, regardless of what is currently in vendor dir'),
new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'),
new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --outdated option.'),
new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates.'),
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates.'),
new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates.'),
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage()),
new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage()),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'),
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'),
Expand Down Expand Up @@ -84,6 +85,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if ($input->getOption('strict')) {
$args['--strict'] = true;
}
if ($input->getOption('major-only')) {
$args['--major-only'] = true;
}
if ($input->getOption('minor-only')) {
$args['--minor-only'] = true;
}
Expand Down
47 changes: 31 additions & 16 deletions src/Composer/Command/ShowCommand.php
Expand Up @@ -91,8 +91,9 @@ protected function configure()
new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'),
new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'),
new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage()),
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'),
new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --outdated option.'),
new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.'),
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.'),
new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.'),
new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
Expand Down Expand Up @@ -158,8 +159,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 1;
}

if ($input->getOption('patch-only') && $input->getOption('minor-only')) {
$io->writeError('The --patch-only option is not usable in combination with --minor-only');
if (count(array_filter([$input->getOption('patch-only'), $input->getOption('minor-only'), $input->getOption('major-only')])) > 1) {
$io->writeError('Only one of --major-only, --minor-only or --patch-only can be used at once');

return 1;
}
Expand Down Expand Up @@ -314,7 +315,7 @@ protected function execute(InputInterface $input, OutputInterface $output)

$latestPackage = null;
if ($input->getOption('latest')) {
$latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('minor-only'), $input->getOption('patch-only'), $platformReqFilter);
$latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('major-only'), $input->getOption('minor-only'), $input->getOption('patch-only'), $platformReqFilter);
}
if (
$input->getOption('outdated')
Expand Down Expand Up @@ -422,6 +423,7 @@ protected function execute(InputInterface $input, OutputInterface $output)

$showAllTypes = $input->getOption('all');
$showLatest = $input->getOption('latest');
$showMajorOnly = $input->getOption('major-only');
$showMinorOnly = $input->getOption('minor-only');
$showPatchOnly = $input->getOption('patch-only');
$ignoredPackages = array_map('strtolower', $input->getOption('ignore'));
Expand All @@ -440,7 +442,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
if ($showLatest && $showVersion) {
foreach ($packages[$type] as $package) {
if (is_object($package)) {
$latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMinorOnly, $showPatchOnly, $platformReqFilter);
$latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMajorOnly, $showMinorOnly, $showPatchOnly, $platformReqFilter);
if ($latestPackage === null) {
continue;
}
Expand Down Expand Up @@ -468,6 +470,8 @@ protected function execute(InputInterface $input, OutputInterface $output)

// Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code
$packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned());
// When using --major-only, and no bigger version than current major is found then it is considered up to date
$packageIsUpToDate = $packageIsUpToDate || ($latestPackage === null && $showMajorOnly);
$packageIsIgnored = \in_array($package->getPrettyName(), $ignoredPackages, true);
if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) {
continue;
Expand Down Expand Up @@ -1354,7 +1358,7 @@ private function writeTreeLine(string $line): void
/**
* Given a package, this finds the latest package matching it
*/
private function findLatestPackage(PackageInterface $package, Composer $composer, PlatformRepository $platformRepo, bool $minorOnly, bool $patchOnly, PlatformRequirementFilterInterface $platformReqFilter): ?PackageInterface
private function findLatestPackage(PackageInterface $package, Composer $composer, PlatformRepository $platformRepo, bool $majorOnly, bool $minorOnly, bool $patchOnly, PlatformRequirementFilterInterface $platformReqFilter): ?PackageInterface
{
// find the latest version allowed in this repo set
$name = $package->getName();
Expand All @@ -1373,19 +1377,30 @@ private function findLatestPackage(PackageInterface $package, Composer $composer
$targetVersion = null;
if (0 === strpos($package->getVersion(), 'dev-')) {
$targetVersion = $package->getVersion();
}

if ($targetVersion === null && $minorOnly) {
$targetVersion = '^' . $package->getVersion();
// dev-x branches are considered to be on the latest major version always, do not look up for a new commit as that is deemed a minor upgrade (albeit risky)
if ($majorOnly) {
return null;
}
}

if ($targetVersion === null && $patchOnly) {
$trimmedVersion = Preg::replace('{(\.0)+$}D', '', $package->getVersion());
$partsNeeded = substr($trimmedVersion, 0, 1) === '0' ? 4 : 3;
while (substr_count($trimmedVersion, '.') + 1 < $partsNeeded) {
$trimmedVersion .= '.0';
if ($targetVersion === null) {
if ($majorOnly && Preg::isMatch('{^(\d+)\.}', $package->getVersion(), $match)) {
$targetVersion = '>='.($match[1] + 1).',<9999999-dev';
}

if ($minorOnly) {
$targetVersion = '^'.$package->getVersion();
}

if ($patchOnly) {
$trimmedVersion = Preg::replace('{(\.0)+$}D', '', $package->getVersion());
$partsNeeded = substr($trimmedVersion, 0, 1) === '0' ? 4 : 3;
while (substr_count($trimmedVersion, '.') + 1 < $partsNeeded) {
$trimmedVersion .= '.0';
}
$targetVersion = '~'.$trimmedVersion;
}
$targetVersion = '~' . $trimmedVersion;
}

$candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter);
Expand Down