From e51381f349ca2394ce74c181afdc3e1afa2974af Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 7 Jun 2022 15:03:49 +0200 Subject: [PATCH] Add --major-only flag to outdated/show commands to restrict the list to packages with major updates available, fixes #10439 --- doc/03-cli.md | 12 ++++-- src/Composer/Command/OutdatedCommand.php | 10 +++-- src/Composer/Command/ShowCommand.php | 47 ++++++++++++++++-------- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 30d21c673b5d..d3ceef5fdb8c 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -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). @@ -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. @@ -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. diff --git a/src/Composer/Command/OutdatedCommand.php b/src/Composer/Command/OutdatedCommand.php index ffa9ba3af731..5d3a05b164d5 100644 --- a/src/Composer/Command/OutdatedCommand.php +++ b/src/Composer/Command/OutdatedCommand.php @@ -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'), @@ -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; } diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index bdfb5c8cf3bf..a1f359f21f72 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -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']), @@ -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; } @@ -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') @@ -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')); @@ -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; } @@ -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; @@ -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(); @@ -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);