From 8d766c8eb2e43d25fae813b5301306c9484afca4 Mon Sep 17 00:00:00 2001 From: Steve Buzonas Date: Thu, 12 May 2016 21:11:48 -0400 Subject: [PATCH 1/7] cleaner implementation of status output with support for path repositories --- src/Composer/Command/StatusCommand.php | 105 +++++++++++++++--- src/Composer/Downloader/GitDownloader.php | 21 +++- src/Composer/Downloader/HgDownloader.php | 17 +++ src/Composer/Downloader/PathDownloader.php | 21 +++- src/Composer/Downloader/SvnDownloader.php | 18 +++ .../VcsCapableDownloaderInterface.php | 33 ++++++ .../Package/Version/VersionGuesser.php | 7 ++ 7 files changed, 202 insertions(+), 20 deletions(-) create mode 100644 src/Composer/Downloader/VcsCapableDownloaderInterface.php diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index d5b483788..506fdbb5e 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -16,10 +16,15 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Downloader\ChangeReportInterface; +use Composer\Downloader\DvcsDownloaderInterface; +use Composer\Downloader\VcsCapableDownloaderInterface; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Script\ScriptEvents; -use Composer\Downloader\DvcsDownloaderInterface; +use Composer\Util\ProcessExecutor; /** * @author Tiago Ribeiro @@ -27,6 +32,11 @@ use Composer\Downloader\DvcsDownloaderInterface; */ class StatusCommand extends BaseCommand { + + const EXIT_CODE_ERRORS = 1; + const EXIT_CODE_UNPUSHED_CHANGES = 2; + const EXIT_CODE_VERSION_CHANGES = 4; + protected function configure() { $this @@ -63,6 +73,11 @@ EOT $errors = array(); $io = $this->getIO(); $unpushedChanges = array(); + $vcsVersionChanges = array(); + + $parser = new VersionParser; + $guesser = new VersionGuesser($composer->getConfig(), new ProcessExecutor($io), $parser); + $dumper = new ArrayDumper; // list packages foreach ($installedRepo->getCanonicalPackages() as $package) { @@ -78,31 +93,64 @@ EOT if ($changes = $downloader->getLocalChanges($package, $targetDir, true)) { $errors[$targetDir] = $changes; } + } + + if ($downloader instanceof VcsCapableDownloaderInterface) { + if ($currentRef = $downloader->getVcsReference($package, $targetDir)) { + switch ($package->getInstallationSource()) { + case 'source': + $previousRef = $package->getSourceReference(); + break; + case 'dist': + $previousRef = $package->getDistReference(); + break; + default: + $previousRef = null; + } - if ($downloader instanceof DvcsDownloaderInterface) { - if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) { - $unpushedChanges[$targetDir] = $unpushed; + $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir); + + if ($previousRef && $currentVersion['commit'] !== $previousRef) { + $vcsVersionChanges[$targetDir] = array( + 'previous' => array( + 'version' => $package->getVersion(), + 'ref' => $previousRef + ), + 'current' => array( + 'version' => $currentVersion['version'], + 'ref' => $currentVersion['commit'], + ), + ); } } } + + if ($downloader instanceof DvcsDownloaderInterface) { + if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) { + $unpushedChanges[$targetDir] = $unpushed; + } + } } // output errors/warnings - if (!$errors && !$unpushedChanges) { + if (!$errors && !$unpushedChanges && !$vcsVersionChanges) { $io->writeError('No local changes'); - } elseif ($errors) { - $io->writeError('You have changes in the following dependencies:'); + return 0; } - foreach ($errors as $path => $changes) { - if ($input->getOption('verbose')) { - $indentedChanges = implode("\n", array_map(function ($line) { - return ' ' . ltrim($line); - }, explode("\n", $changes))); - $io->write(''.$path.':'); - $io->write($indentedChanges); - } else { - $io->write($path); + if ($errors) { + $io->writeError('You have changes in the following dependencies:'); + + foreach ($errors as $path => $changes) { + if ($input->getOption('verbose')) { + $indentedChanges = implode("\n", array_map(function ($line) { + return ' ' . ltrim($line); + }, explode("\n", $changes))); + $io->write(''.$path.':'); + $io->write($indentedChanges); + } else { + $io->write($path); + } } } @@ -122,13 +170,34 @@ EOT } } - if (($errors || $unpushedChanges) && !$input->getOption('verbose')) { + if ($vcsVersionChanges) { + $io->writeError('You have version variations in the following dependencies:'); + + foreach ($vcsVersionChanges as $path => $changes) { + if ($input->getOption('verbose')) { + $currentVersion = $changes['current']['version']; + $previousVersion = $changes['previous']['version']; + + if ($io->isVeryVerbose()) { + $currentVersion .= sprintf(' (%s)', $changes['current']['ref']); + $previousVersion .= sprintf(' (%s)', $changes['previous']['ref']); + } + + $io->write(''.$path.':'); + $io->write(sprintf(' From %s to %s', $previousVersion, $currentVersion)); + } else { + $io->write($path); + } + } + } + + if (($errors || $unpushedChanges || $vcsVersionChanges) && !$input->getOption('verbose')) { $io->writeError('Use --verbose (-v) to see a list of files'); } // Dispatch post-status-command $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true); - return ($errors ? 1 : 0) + ($unpushedChanges ? 2 : 0); + return ($errors ? self::EXIT_CODE_ERRORS : 0) + ($unpushedChanges ? self::EXIT_CODE_UNPUSHED_CHANGES : 0) + ($vcsVersionChanges ? self::EXIT_CODE_VERSION_CHANGES : 0); } } diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 94a8d78d9..ee9609dd7 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -23,7 +23,7 @@ use Composer\Config; /** * @author Jordi Boggiano */ -class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface +class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface, VcsCapableDownloaderInterface { private $hasStashedChanges = false; private $hasDiscardedChanges = false; @@ -196,6 +196,25 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface return $unpushedChanges; } + /** + * {@inheritDoc} + */ + public function getVcsReference(PackageInterface $package, $path) + { + if (!$this->hasMetadataRepository($path)) { + return; + } + + GitUtil::cleanEnv(); + + $command = 'git log --pretty="%H" -n1 HEAD'; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + } + + return trim($output) ?: null; + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 4222282c0..01a91db2e 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -77,6 +77,23 @@ class HgDownloader extends VcsDownloader return trim($output) ?: null; } + /** + * {@inheritDoc} + */ + public function getVcsReference(PackageInterface $package, $path) + { + if (!$this->hasMetadataRepository($path)) { + return; + } + + $command = 'hg parent --template ' . escapeshellarg('{node}'); + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + } + + return trim($output) ?: null; + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index e7e934544..bc18f88d8 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -12,8 +12,12 @@ namespace Composer\Downloader; +use Composer\Package\Dumper\ArrayDumper; use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; @@ -23,7 +27,7 @@ use Symfony\Component\Filesystem\Filesystem; * @author Samuel Roze * @author Johann Reinke */ -class PathDownloader extends FileDownloader +class PathDownloader extends FileDownloader implements VcsCapableDownloaderInterface { const STRATEGY_SYMLINK = 10; const STRATEGY_MIRROR = 20; @@ -127,4 +131,19 @@ class PathDownloader extends FileDownloader parent::remove($package, $path); } } + + /** + * {@inheritDoc} + */ + public function getVcsReference(PackageInterface $package, $path) + { + $parser = new VersionParser; + $guesser = new VersionGuesser($this->config, new ProcessExecutor($this->io), $parser); + $dumper = new ArrayDumper; + + $packageConfig = $dumper->dump($package); + if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) { + return $packageVersion['commit']; + } + } } diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index a21f35858..36b660022 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -81,6 +81,24 @@ class SvnDownloader extends VcsDownloader return preg_match('{^ *[^X ] +}m', $output) ? $output : null; } + /** + * {@inheritDoc} + */ + public function getVcsReference(PackageInterface $package, $path) + { + if (!$this->hasMetadataRepository($path)) { + return; + } + + SvnUtil::cleanEnv(); + + if (0 !== $this->process->execute('svnversion', $output, $path)) { + throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + } + + return trim($output) ?: null; + } + /** * Execute an SVN command and try to fix up the process with credentials * if necessary. diff --git a/src/Composer/Downloader/VcsCapableDownloaderInterface.php b/src/Composer/Downloader/VcsCapableDownloaderInterface.php new file mode 100644 index 000000000..fa4ddf485 --- /dev/null +++ b/src/Composer/Downloader/VcsCapableDownloaderInterface.php @@ -0,0 +1,33 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; + +/** + * VCS Capable Downloader interface. + * + * @author Steve Buzonas + */ +interface VcsCapableDownloaderInterface +{ + + /** + * Gets the VCS Reference for the package at path + * + * @param PackageInterface $package package directory + * @param string $path package directory + * @return string|null reference or null + */ + public function getVcsReference(PackageInterface $package, $path); +} diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index e2f8d64dd..ff5b90b76 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -125,6 +125,13 @@ class VersionGuesser $version = $this->versionFromGitTags($path); } + if (!$commit) { + $command = 'git log --pretty="%H" -n1 HEAD'; + if (0 === $this->process->execute($command, $output, $path)) { + $commit = trim($output) ?: null; + } + } + return array('version' => $version, 'commit' => $commit); } From 89d2f58c05044c94114dae64a5af7710e47ef7ad Mon Sep 17 00:00:00 2001 From: Steve Buzonas Date: Thu, 12 May 2016 21:21:50 -0400 Subject: [PATCH 2/7] use version parser in vcs downloader --- src/Composer/Downloader/GitDownloader.php | 21 +-------------------- src/Composer/Downloader/HgDownloader.php | 17 ----------------- src/Composer/Downloader/SvnDownloader.php | 18 ------------------ src/Composer/Downloader/VcsDownloader.php | 20 +++++++++++++++++++- 4 files changed, 20 insertions(+), 56 deletions(-) diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index ee9609dd7..94a8d78d9 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -23,7 +23,7 @@ use Composer\Config; /** * @author Jordi Boggiano */ -class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface, VcsCapableDownloaderInterface +class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface { private $hasStashedChanges = false; private $hasDiscardedChanges = false; @@ -196,25 +196,6 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface, Vc return $unpushedChanges; } - /** - * {@inheritDoc} - */ - public function getVcsReference(PackageInterface $package, $path) - { - if (!$this->hasMetadataRepository($path)) { - return; - } - - GitUtil::cleanEnv(); - - $command = 'git log --pretty="%H" -n1 HEAD'; - if (0 !== $this->process->execute($command, $output, $path)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } - - return trim($output) ?: null; - } - /** * {@inheritDoc} */ diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 01a91db2e..4222282c0 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -77,23 +77,6 @@ class HgDownloader extends VcsDownloader return trim($output) ?: null; } - /** - * {@inheritDoc} - */ - public function getVcsReference(PackageInterface $package, $path) - { - if (!$this->hasMetadataRepository($path)) { - return; - } - - $command = 'hg parent --template ' . escapeshellarg('{node}'); - if (0 !== $this->process->execute($command, $output, $path)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } - - return trim($output) ?: null; - } - /** * {@inheritDoc} */ diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index 36b660022..a21f35858 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -81,24 +81,6 @@ class SvnDownloader extends VcsDownloader return preg_match('{^ *[^X ] +}m', $output) ? $output : null; } - /** - * {@inheritDoc} - */ - public function getVcsReference(PackageInterface $package, $path) - { - if (!$this->hasMetadataRepository($path)) { - return; - } - - SvnUtil::cleanEnv(); - - if (0 !== $this->process->execute('svnversion', $output, $path)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } - - return trim($output) ?: null; - } - /** * Execute an SVN command and try to fix up the process with credentials * if necessary. diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index 30323856f..e4fe312d7 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -13,7 +13,10 @@ namespace Composer\Downloader; use Composer\Config; +use Composer\Package\Dumper\ArrayDumper; use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; use Composer\Util\ProcessExecutor; use Composer\IO\IOInterface; use Composer\Util\Filesystem; @@ -21,7 +24,7 @@ use Composer\Util\Filesystem; /** * @author Jordi Boggiano */ -abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterface +abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterface, VcsCapableDownloaderInterface { /** @var IOInterface */ protected $io; @@ -193,6 +196,21 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa return $this; } + /** + * {@inheritDoc} + */ + public function getVcsReference(PackageInterface $package, $path) + { + $parser = new VersionParser; + $guesser = new VersionGuesser($this->config, $this->process, $parser); + $dumper = new ArrayDumper; + + $packageConfig = $dumper->dump($package); + if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) { + return $packageVersion['commit']; + } + } + /** * Prompt the user to check if changes should be stashed/removed or the operation aborted * From 758e7cab53abc579168345bca9e94652a0a6a810 Mon Sep 17 00:00:00 2001 From: Steve Buzonas Date: Thu, 12 May 2016 21:35:25 -0400 Subject: [PATCH 3/7] fixed issue where detatched head would give null output --- src/Composer/Command/StatusCommand.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 506fdbb5e..b946355c4 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -175,10 +175,12 @@ EOT foreach ($vcsVersionChanges as $path => $changes) { if ($input->getOption('verbose')) { - $currentVersion = $changes['current']['version']; - $previousVersion = $changes['previous']['version']; + // If we don't can't find a version, use the ref instead. + $currentVersion = $changes['current']['version'] ?: $changes['current']['ref']; + $previousVersion = $changes['previous']['version'] ?: $changes['previous']['ref']; if ($io->isVeryVerbose()) { + // Output the ref regardless of whether or not it's being used as the version $currentVersion .= sprintf(' (%s)', $changes['current']['ref']); $previousVersion .= sprintf(' (%s)', $changes['previous']['ref']); } From d0c009edf2a97607be163ba2b92e55f3f8afef2a Mon Sep 17 00:00:00 2001 From: Steve Buzonas Date: Thu, 12 May 2016 21:55:34 -0400 Subject: [PATCH 4/7] fix issue where path repositories would be matched oddly --- src/Composer/Command/StatusCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index b946355c4..d8763c8e8 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -82,10 +82,9 @@ EOT // list packages foreach ($installedRepo->getCanonicalPackages() as $package) { $downloader = $dm->getDownloaderForInstalledPackage($package); + $targetDir = $im->getInstallPath($package); if ($downloader instanceof ChangeReportInterface) { - $targetDir = $im->getInstallPath($package); - if (is_link($targetDir)) { $errors[$targetDir] = $targetDir . ' is a symbolic link.'; } @@ -111,6 +110,7 @@ EOT $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir); if ($previousRef && $currentVersion['commit'] !== $previousRef) { + var_dump($previousRef, $currentVersion['commit'], $package->getName(), $package->getInstallationSource()); $vcsVersionChanges[$targetDir] = array( 'previous' => array( 'version' => $package->getVersion(), From 64afdcc2aac46710fef9b0671b83594aff8f58f1 Mon Sep 17 00:00:00 2001 From: Steve Buzonas Date: Wed, 1 Jun 2016 21:47:19 -0400 Subject: [PATCH 5/7] remove var_dump --- src/Composer/Command/StatusCommand.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index d8763c8e8..2fcfcec22 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -110,7 +110,6 @@ EOT $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir); if ($previousRef && $currentVersion['commit'] !== $previousRef) { - var_dump($previousRef, $currentVersion['commit'], $package->getName(), $package->getInstallationSource()); $vcsVersionChanges[$targetDir] = array( 'previous' => array( 'version' => $package->getVersion(), From 7b6e06c686ab26ea566ab26bb5b5be7af372819e Mon Sep 17 00:00:00 2001 From: Steve Buzonas Date: Fri, 3 Jun 2016 17:57:23 -0400 Subject: [PATCH 6/7] normalize version to provide consistent output from package version vs vcs version --- src/Composer/Command/StatusCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 2fcfcec22..aab1a952d 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -116,7 +116,7 @@ EOT 'ref' => $previousRef ), 'current' => array( - 'version' => $currentVersion['version'], + 'version' => $parser->normalize($currentVersion['version']), 'ref' => $currentVersion['commit'], ), ); From 576721946c72bdeb3bbc480f8330fb8b06f42f24 Mon Sep 17 00:00:00 2001 From: Steve Buzonas Date: Fri, 3 Jun 2016 19:05:04 -0400 Subject: [PATCH 7/7] show pretty version in both scenarios --- src/Composer/Command/StatusCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index aab1a952d..f31c49889 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -112,11 +112,11 @@ EOT if ($previousRef && $currentVersion['commit'] !== $previousRef) { $vcsVersionChanges[$targetDir] = array( 'previous' => array( - 'version' => $package->getVersion(), + 'version' => $package->getPrettyVersion(), 'ref' => $previousRef ), 'current' => array( - 'version' => $parser->normalize($currentVersion['version']), + 'version' => $currentVersion['version'], 'ref' => $currentVersion['commit'], ), );