diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php new file mode 100644 index 000000000..879ae7578 --- /dev/null +++ b/src/Composer/Command/StatusCommand.php @@ -0,0 +1,78 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Downloader\VcsDownloader; + +/** + * @author Tiago Ribeiro + * @author Rui Marinho + */ +class StatusCommand extends Command +{ + protected function configure() + { + $this + ->setName('status') + ->setDescription('Show a list of locally modified packages') + ->setHelp(<<getComposer(); + $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); + + $dm = $composer->getDownloadManager(); + $im = $composer->getInstallationManager(); + + $errors = array(); + + // list packages + foreach ($installedRepo->getPackages() as $package) { + $downloader = $dm->getDownloaderForInstalledPackage($package); + + if ($downloader instanceof VcsDownloader) { + $targetDir = $im->getInstallPath($package); + + if ($downloader->hasLocalChanges($targetDir)) { + $errors[] = $targetDir; + } + } + } + + // output errors/warnings + if (!$errors) { + $output->writeln('No local changes'); + } else { + $output->writeln('You have changes in the following packages:'); + } + + foreach ($errors as $error) { + $output->writeln($error); + } + + return $errors ? 1 : 0; + } +} diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index b58a7ab58..82bfa96b0 100755 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -131,6 +131,7 @@ class Application extends BaseApplication $commands[] = new Command\ShowCommand(); $commands[] = new Command\RequireCommand(); $commands[] = new Command\DumpAutoloadCommand(); + $commands[] = new Command\StatusCommand(); if ('phar:' === substr(__FILE__, 0, 5)) { $commands[] = new Command\SelfUpdateCommand(); diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 06cf6fcd1..a5a101186 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -63,6 +63,19 @@ class GitDownloader extends VcsDownloader $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate()); } + /** + * {@inheritDoc} + */ + public function hasLocalChanges($path) + { + $command = sprintf('cd %s && git status --porcelain --untracked-files=no', escapeshellarg($path)); + if (0 !== $this->process->execute($command, $output)) { + throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + } + + return (bool) trim($output); + } + protected function updateToCommit($path, $reference, $branch, $date) { $template = 'git checkout %s && git reset --hard %1$s'; @@ -124,21 +137,6 @@ class GitDownloader extends VcsDownloader throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } - /** - * {@inheritDoc} - */ - protected function enforceCleanDirectory($path) - { - $command = sprintf('cd %s && git status --porcelain --untracked-files=no', escapeshellarg($path)); - if (0 !== $this->process->execute($command, $output)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } - - if (trim($output)) { - throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes'); - } - } - /** * Runs a command doing attempts for each protocol supported by github. * diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 7dc845a98..a2b4fe9df 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -52,11 +52,10 @@ class HgDownloader extends VcsDownloader /** * {@inheritDoc} */ - protected function enforceCleanDirectory($path) + public function hasLocalChanges($path) { $this->process->execute(sprintf('cd %s && hg st', escapeshellarg($path)), $output); - if (trim($output)) { - throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes'); - } + + return (bool) trim($output); } } diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index 63f654221..2b4d77fee 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -48,12 +48,11 @@ class SvnDownloader extends VcsDownloader /** * {@inheritDoc} */ - protected function enforceCleanDirectory($path) + public function hasLocalChanges($path) { $this->process->execute('svn status --ignore-externals', $output, $path); - if (preg_match('{^ *[^X ] +}m', $output)) { - throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes:'."\n\n".rtrim($output)); - } + + return preg_match('{^ *[^X ] +}m', $output); } /** diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index d7c9f20c5..45ee17768 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -83,6 +83,18 @@ abstract class VcsDownloader implements DownloaderInterface } } + /** + * Guarantee that no changes have been made to the local copy + * + * @throws \RuntimeException if the directory is not clean + */ + protected function enforceCleanDirectory($path) + { + if ($this->hasLocalChanges($path)) { + throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.'); + } + } + /** * Downloads specific package into specific folder. * @@ -101,9 +113,10 @@ abstract class VcsDownloader implements DownloaderInterface abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path); /** - * Checks that no changes have been made to the local copy + * Checks for changes to the local copy * - * @throws \RuntimeException if the directory is not clean + * @param string $path package directory + * @return boolean whether package has local changes */ - abstract protected function enforceCleanDirectory($path); + abstract public function hasLocalChanges($path); }