Pārlūkot izejas kodu

Merge remote-tracking branch 'curry684/rewrite-depends'

Jordi Boggiano 9 gadi atpakaļ
vecāks
revīzija
f681dbc51e
32 mainītis faili ar 442 papildinājumiem un 169 dzēšanām
  1. 50 17
      doc/03-cli.md
  2. 1 1
      src/Composer/Command/AboutCommand.php
  3. 1 1
      src/Composer/Command/ArchiveCommand.php
  4. 2 2
      src/Composer/Command/BaseCommand.php
  5. 190 0
      src/Composer/Command/BaseDependencyCommand.php
  6. 1 1
      src/Composer/Command/ClearCacheCommand.php
  7. 1 1
      src/Composer/Command/ConfigCommand.php
  8. 1 1
      src/Composer/Command/CreateProjectCommand.php
  9. 17 102
      src/Composer/Command/DependsCommand.php
  10. 1 1
      src/Composer/Command/DiagnoseCommand.php
  11. 1 1
      src/Composer/Command/DumpAutoloadCommand.php
  12. 1 1
      src/Composer/Command/GlobalCommand.php
  13. 1 1
      src/Composer/Command/HomeCommand.php
  14. 1 1
      src/Composer/Command/InitCommand.php
  15. 1 1
      src/Composer/Command/InstallCommand.php
  16. 1 1
      src/Composer/Command/LicensesCommand.php
  17. 55 0
      src/Composer/Command/ProhibitsCommand.php
  18. 1 1
      src/Composer/Command/RemoveCommand.php
  19. 1 1
      src/Composer/Command/RunScriptCommand.php
  20. 1 1
      src/Composer/Command/ScriptAliasCommand.php
  21. 1 1
      src/Composer/Command/SearchCommand.php
  22. 1 1
      src/Composer/Command/SelfUpdateCommand.php
  23. 1 1
      src/Composer/Command/ShowCommand.php
  24. 1 1
      src/Composer/Command/StatusCommand.php
  25. 1 1
      src/Composer/Command/SuggestsCommand.php
  26. 1 1
      src/Composer/Command/UpdateCommand.php
  27. 1 1
      src/Composer/Command/ValidateCommand.php
  28. 26 24
      src/Composer/Console/Application.php
  29. 8 0
      src/Composer/Package/Link.php
  30. 1 1
      src/Composer/Repository/ArrayRepository.php
  31. 70 0
      src/Composer/Repository/BaseRepository.php
  32. 1 1
      src/Composer/Repository/CompositeRepository.php

+ 50 - 17
doc/03-cli.md

@@ -328,34 +328,67 @@ to limit output to suggestions made by those packages only.
 ## depends
 
 The `depends` command tells you which other packages depend on a certain
-package. You can specify which link types (`require`, `require-dev`)
-should be included in the listing. By default both are used.
+package. As with installation `require-dev` relationships are only considered
+for the root package.
 
 ```sh
-php composer.phar depends --link-type=require monolog/monolog
+php composer.phar depends doctrine/lexer
+ doctrine/annotations v1.2.7 requires doctrine/lexer (1.*)
+ doctrine/common      v2.6.1 requires doctrine/lexer (1.*)
+```
+
+You can optionally specify a version constraint after the package to limit the
+search.
+
+Add the `--tree` or `-t` flag to show a recursive tree of why the package is
+depended upon, for example:
+
+```sh
+php composer.phar depends psr/log -t
+psr/log 1.0.0 Common interface for logging libraries
+|- aboutyou/app-sdk 2.6.11 (requires psr/log 1.0.*)
+|  `- __root__ (requires aboutyou/app-sdk ^2.6)
+|- monolog/monolog 1.17.2 (requires psr/log ~1.0)
+|  `- laravel/framework v5.2.16 (requires monolog/monolog ~1.11)
+|     `- __root__ (requires laravel/framework ^5.2)
+`- symfony/symfony v3.0.2 (requires psr/log ~1.0)
+   `- __root__ (requires symfony/symfony ^3.0)
+```
+
+### Options
+
+* **--recursive (-r):** Recursively resolves up to the root package.
+* **--tree (-t):** Prints the results as a nested tree, implies -r.
 
-nrk/monolog-fluent requires monolog/monolog (~1.8)
-poc/poc requires monolog/monolog (^1.6)
-propel/propel requires monolog/monolog (1.*)
-symfony/monolog-bridge requires monolog/monolog (>=1.2)
-symfony/symfony requires monolog/monolog (~1)
+## prohibits
+
+The `prohibits` command tells you which packages are blocking a given package
+from being installed. Specify a version constraint to verify whether upgrades
+can be performed in your project, and if not why not. See the following
+example:
+
+```sh
+php composer.phar prohibits symfony/symfony 3.1
+ laravel/framework v5.2.16 requires symfony/var-dumper (2.8.*|3.0.*)
 ```
 
-If you want, for example, find any installed package that is **not** allowing
-Symfony version 3 or one of its components, you can run the following command:
+Note that you can also specify platform requirements, for example to check
+whether you can upgrade your server to PHP 8.0:
 
 ```sh
-php composer.phar depends symfony/symfony --with-replaces -im ^3.0
+php composer.phar prohibits php:8
+ doctrine/cache        v1.6.0 requires php (~5.5|~7.0)
+ doctrine/common       v2.6.1 requires php (~5.5|~7.0)
+ doctrine/instantiator 1.0.5  requires php (>=5.3,<8.0-DEV)
 ```
 
+As with `depends` you can request a recursive lookup, which will list all
+packages depending on the packages that cause the conflict.
+
 ### Options
 
-* **--link-type:** The link types to match on, can be specified multiple
-  times.
-* **--match-constraint (-m):** Filters the dependencies shown using this constraint.
-* **--invert-match-constraint (-i):** Turns --match-constraint around into a blacklist
-  instead of a whitelist.
-* **--with-replaces:** Search for replaced packages as well.
+* **--recursive (-r):** Recursively resolves up to the root package.
+* **--tree (-t):** Prints the results as a nested tree, implies -r.
 
 ## validate
 

+ 1 - 1
src/Composer/Command/AboutCommand.php

@@ -18,7 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class AboutCommand extends Command
+class AboutCommand extends BaseCommand
 {
     protected function configure()
     {

+ 1 - 1
src/Composer/Command/ArchiveCommand.php

@@ -30,7 +30,7 @@ use Symfony\Component\Console\Output\OutputInterface;
  *
  * @author Nils Adermann <naderman@naderman.de>
  */
-class ArchiveCommand extends Command
+class ArchiveCommand extends BaseCommand
 {
     protected function configure()
     {

+ 2 - 2
src/Composer/Command/Command.php → src/Composer/Command/BaseCommand.php

@@ -18,7 +18,7 @@ use Composer\IO\IOInterface;
 use Composer\IO\NullIO;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Command\Command as BaseCommand;
+use Symfony\Component\Console\Command\Command;
 
 /**
  * Base class for Composer commands
@@ -26,7 +26,7 @@ use Symfony\Component\Console\Command\Command as BaseCommand;
  * @author Ryan Weaver <ryan@knplabs.com>
  * @author Konstantin Kudryashov <ever.zet@gmail.com>
  */
-abstract class Command extends BaseCommand
+abstract class BaseCommand extends Command
 {
     /**
      * @var Composer

+ 190 - 0
src/Composer/Command/BaseDependencyCommand.php

@@ -0,0 +1,190 @@
+<?php
+
+/*
+ * 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\Command;
+
+use Composer\DependencyResolver\Pool;
+use Composer\Package\Link;
+use Composer\Package\PackageInterface;
+use Composer\Repository\ArrayRepository;
+use Composer\Repository\CompositeRepository;
+use Composer\Repository\PlatformRepository;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
+use Composer\Package\Version\VersionParser;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Base implementation for commands mapping dependency relationships.
+ *
+ * @author Niels Keurentjes <niels.keurentjes@omines.com>
+ */
+class BaseDependencyCommand extends BaseCommand
+{
+    const ARGUMENT_PACKAGE = 'package';
+    const ARGUMENT_CONSTRAINT = 'constraint';
+    const OPTION_RECURSIVE = 'recursive';
+    const OPTION_TREE = 'tree';
+
+    /**
+     * Set common options and arguments.
+     */
+    protected function configure()
+    {
+        $this->setDefinition(array(
+            new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect'),
+            new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::OPTIONAL, 'Optional version constraint', '*'),
+            new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'),
+            new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'),
+        ));
+    }
+
+    /**
+     * Execute the command.
+     *
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @param bool $inverted Whether to invert matching process (why-not vs why behaviour)
+     * @return int|null Exit code of the operation.
+     */
+    protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false)
+    {
+        // Emit command event on startup
+        $composer = $this->getComposer();
+        $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
+        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
+
+        // Prepare repositories and set up a pool
+        $platformOverrides = $composer->getConfig()->get('platform') ?: array();
+        $repository = new CompositeRepository(array(
+            new ArrayRepository(array($composer->getPackage())),
+            $composer->getRepositoryManager()->getLocalRepository(),
+            new PlatformRepository(array(), $platformOverrides),
+        ));
+        $pool = new Pool();
+        $pool->addRepository($repository);
+
+        // Parse package name and constraint
+        list($needle, $textConstraint) = array_pad(explode(':', $input->getArgument(self::ARGUMENT_PACKAGE)),
+                    2, $input->getArgument(self::ARGUMENT_CONSTRAINT));
+
+        // Find packages that are or provide the requested package first
+        $packages = $pool->whatProvides($needle);
+        if (empty($packages)) {
+            throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
+        }
+
+        // Include replaced packages for inverted lookups as they are then the actual starting point to consider
+        $needles = array($needle);
+        if ($inverted) {
+            foreach ($packages as $package) {
+                $needles = array_merge($needles, array_map(function (Link $link) {
+                    return $link->getTarget();
+                }, $package->getReplaces()));
+            }
+        }
+
+        // Parse constraint if one was supplied
+        if ('*' !== $textConstraint) {
+            $versionParser = new VersionParser();
+            $constraint = $versionParser->parseConstraints($textConstraint);
+        } else {
+            $constraint = null;
+        }
+
+        // Parse rendering options
+        $renderTree = $input->getOption(self::OPTION_TREE);
+        $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE);
+
+        // Resolve dependencies
+        $results = $repository->getDependents($needles, $constraint, $inverted, $recursive);
+        if (empty($results)) {
+            $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : '';
+            $this->getIO()->writeError(sprintf('<info>There is no installed package depending on "%s"%s</info>',
+                $needle, $extra));
+        } elseif ($renderTree) {
+            $root = $packages[0];
+            $this->getIO()->write(sprintf('<info>%s</info> %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription()));
+            $this->printTree($output, $results);
+        } else {
+            $this->printTable($output, $results);
+        }
+        return 0;
+    }
+
+    /**
+     * Assembles and prints a bottom-up table of the dependencies.
+     *
+     * @param OutputInterface $output
+     * @param array $results
+     */
+    protected function printTable(OutputInterface $output, $results)
+    {
+        $table = array();
+        $doubles = array();
+        do {
+            $queue = array();
+            $rows = array();
+            foreach($results as $result) {
+                /**
+                 * @var PackageInterface $package
+                 * @var Link $link
+                 */
+                list($package, $link, $children) = $result;
+                $unique = (string)$link;
+                if (isset($doubles[$unique])) {
+                    continue;
+                }
+                $doubles[$unique] = true;
+                $version = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion();
+                $rows[] = array($package->getPrettyName(), $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint()));
+                $queue = array_merge($queue, $children);
+            }
+            $results = $queue;
+            $table = array_merge($rows, $table);
+        } while(!empty($results));
+
+        // Render table
+        $renderer = new Table($output);
+        $renderer->setStyle('compact')->setRows($table)->render();
+    }
+
+    /**
+     * Recursively prints a tree of the selected results.
+     *
+     * @param OutputInterface $output
+     * @param array $results
+     * @param string $prefix
+     */
+    protected function printTree(OutputInterface $output, $results, $prefix = '')
+    {
+        $count = count($results);
+        $idx = 0;
+        foreach($results as $key => $result) {
+            /**
+             * @var PackageInterface $package
+             * @var Link $link
+             */
+            list($package, $link, $children) = $result;
+            $isLast = (++$idx == $count);
+            $versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $package->getPrettyVersion();
+            $packageText = rtrim(sprintf('%s %s', $package->getPrettyName(), $versionText));
+            $linkText = implode(' ', array($link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()));
+            $output->write(sprintf("%s%s %s (%s)\n", $prefix, $isLast ? '`-' : '|-', $packageText, $linkText));
+            $this->printTree($output, $children, $prefix . ($isLast ? '   ' : '|  '));
+        }
+    }
+}

+ 1 - 1
src/Composer/Command/ClearCacheCommand.php

@@ -20,7 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface;
 /**
  * @author David Neilsen <petah.p@gmail.com>
  */
-class ClearCacheCommand extends Command
+class ClearCacheCommand extends BaseCommand
 {
     protected function configure()
     {

+ 1 - 1
src/Composer/Command/ConfigCommand.php

@@ -27,7 +27,7 @@ use Composer\Json\JsonFile;
  * @author Joshua Estes <Joshua.Estes@iostudio.com>
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class ConfigCommand extends Command
+class ConfigCommand extends BaseCommand
 {
     /**
      * @var Config

+ 1 - 1
src/Composer/Command/CreateProjectCommand.php

@@ -46,7 +46,7 @@ use Composer\Package\Version\VersionParser;
  * @author Tobias Munk <schmunk@usrbin.de>
  * @author Nils Adermann <naderman@naderman.de>
  */
-class CreateProjectCommand extends Command
+class CreateProjectCommand extends BaseCommand
 {
     protected function configure()
     {

+ 17 - 102
src/Composer/Command/DependsCommand.php

@@ -12,43 +12,25 @@
 
 namespace Composer\Command;
 
-use Composer\DependencyResolver\Pool;
-use Composer\Package\Link;
-use Composer\Package\PackageInterface;
-use Composer\Repository\ArrayRepository;
-use Composer\Repository\CompositeRepository;
-use Composer\Repository\PlatformRepository;
-use Composer\Plugin\CommandEvent;
-use Composer\Plugin\PluginEvents;
-use Composer\Package\Version\VersionParser;
 use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 
 /**
- * @author Justin Rainbow <justin.rainbow@gmail.com>
- * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author Niels Keurentjes <niels.keurentjes@omines.com>
  */
-class DependsCommand extends Command
+class DependsCommand extends BaseDependencyCommand
 {
-    protected $linkTypes = array(
-        'require' => array('requires', 'requires'),
-        'require-dev' => array('devRequires', 'requires (dev)'),
-    );
-
+    /**
+     * Configure command metadata.
+     */
     protected function configure()
     {
+        parent::configure();
+
         $this
             ->setName('depends')
-            ->setDescription('Shows which packages depend on the given package')
-            ->setDefinition(array(
-                new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'),
-                new InputOption('link-type', '', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Link types to show (require, require-dev)', array_keys($this->linkTypes)),
-                new InputOption('match-constraint', 'm', InputOption::VALUE_REQUIRED, 'Filters the dependencies shown using this constraint', '*'),
-                new InputOption('invert-match-constraint', 'i', InputOption::VALUE_NONE, 'Turns --match-constraint around into a blacklist instead of whitelist'),
-                new InputOption('with-replaces', '', InputOption::VALUE_NONE, 'Search for replaced packages as well'),
-            ))
+            ->setAliases(array('why'))
+            ->setDescription('Shows which packages cause the given package to be installed')
             ->setHelp(<<<EOT
 Displays detailed information about where a package is referenced.
 
@@ -59,82 +41,15 @@ EOT
         ;
     }
 
+    /**
+     * Execute the function.
+     *
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @return int|null
+     */
     protected function execute(InputInterface $input, OutputInterface $output)
     {
-        $composer = $this->getComposer();
-
-        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'depends', $input, $output);
-        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
-
-        $platformOverrides = $composer->getConfig()->get('platform') ?: array();
-        $repo = new CompositeRepository(array(
-            new ArrayRepository(array($composer->getPackage())),
-            $composer->getRepositoryManager()->getLocalRepository(),
-            new PlatformRepository(array(), $platformOverrides),
-        ));
-        $needle = $input->getArgument('package');
-
-        $pool = new Pool();
-        $pool->addRepository($repo);
-
-        $packages = $pool->whatProvides($needle);
-        if (empty($packages)) {
-            throw new \InvalidArgumentException('Could not find package "'.$needle.'" in your project.');
-        }
-
-        $linkTypes = $this->linkTypes;
-
-        $types = array_map(function ($type) use ($linkTypes) {
-            $type = rtrim($type, 's');
-            if (!isset($linkTypes[$type])) {
-                throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', array_keys($linkTypes)));
-            }
-
-            return $type;
-        }, $input->getOption('link-type'));
-
-        $versionParser = new VersionParser();
-        $constraint = $versionParser->parseConstraints($input->getOption('match-constraint'));
-        $matchInvert = $input->getOption('invert-match-constraint');
-
-        $needles = array($needle);
-        if (true === $input->getOption('with-replaces')) {
-            foreach ($packages as $package) {
-                $needles = array_merge($needles, array_map(function (Link $link) {
-                    return $link->getTarget();
-                }, $package->getReplaces()));
-            }
-        }
-
-        $messages = array();
-        $outputPackages = array();
-        $io = $this->getIO();
-        /** @var PackageInterface $package */
-        foreach ($repo->getPackages() as $package) {
-            foreach ($types as $type) {
-                /** @var Link $link */
-                foreach ($package->{'get'.$linkTypes[$type][0]}() as $link) {
-                    foreach ($needles as $needle) {
-                        if ($link->getTarget() === $needle && ($link->getConstraint()->matches($constraint) ? !$matchInvert : $matchInvert)) {
-                            if (!isset($outputPackages[$package->getName()])) {
-                                $messages[] = '<info>'.$package->getPrettyName() . '</info> ' . $linkTypes[$type][1] . ' ' . $needle .' (<info>' . $link->getPrettyConstraint() . '</info>)';
-                                $outputPackages[$package->getName()] = true;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        if ($messages) {
-            sort($messages);
-            $io->write($messages);
-        } else {
-            $matchText = '';
-            if ($input->getOption('match-constraint') !== '*') {
-                $matchText = ' in versions '.($matchInvert ? 'not ' : '').'matching ' . $input->getOption('match-constraint');
-            }
-            $io->writeError('<info>There is no installed package depending on "'.$needle.'"'.$matchText.'.</info>');
-        }
+        return parent::doExecute($input, $output, false);
     }
 }

+ 1 - 1
src/Composer/Command/DiagnoseCommand.php

@@ -29,7 +29,7 @@ use Symfony\Component\Console\Output\OutputInterface;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class DiagnoseCommand extends Command
+class DiagnoseCommand extends BaseCommand
 {
     /** @var RemoteFileSystem */
     protected $rfs;

+ 1 - 1
src/Composer/Command/DumpAutoloadCommand.php

@@ -21,7 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class DumpAutoloadCommand extends Command
+class DumpAutoloadCommand extends BaseCommand
 {
     protected function configure()
     {

+ 1 - 1
src/Composer/Command/GlobalCommand.php

@@ -21,7 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class GlobalCommand extends Command
+class GlobalCommand extends BaseCommand
 {
     protected function configure()
     {

+ 1 - 1
src/Composer/Command/HomeCommand.php

@@ -26,7 +26,7 @@ use Symfony\Component\Console\Output\OutputInterface;
 /**
  * @author Robert Schönthal <seroscho@googlemail.com>
  */
-class HomeCommand extends Command
+class HomeCommand extends BaseCommand
 {
     /**
      * {@inheritDoc}

+ 1 - 1
src/Composer/Command/InitCommand.php

@@ -31,7 +31,7 @@ use Symfony\Component\Process\ExecutableFinder;
  * @author Justin Rainbow <justin.rainbow@gmail.com>
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class InitCommand extends Command
+class InitCommand extends BaseCommand
 {
     /** @var CompositeRepository */
     protected $repos;

+ 1 - 1
src/Composer/Command/InstallCommand.php

@@ -26,7 +26,7 @@ use Symfony\Component\Console\Output\OutputInterface;
  * @author Konstantin Kudryashov <ever.zet@gmail.com>
  * @author Nils Adermann <naderman@naderman.de>
  */
-class InstallCommand extends Command
+class InstallCommand extends BaseCommand
 {
     protected function configure()
     {

+ 1 - 1
src/Composer/Command/LicensesCommand.php

@@ -25,7 +25,7 @@ use Symfony\Component\Console\Output\OutputInterface;
 /**
  * @author Benoît Merlet <benoit.merlet@gmail.com>
  */
-class LicensesCommand extends Command
+class LicensesCommand extends BaseCommand
 {
     protected function configure()
     {

+ 55 - 0
src/Composer/Command/ProhibitsCommand.php

@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * 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\Command;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @author Niels Keurentjes <niels.keurentjes@omines.com>
+ */
+class ProhibitsCommand extends BaseDependencyCommand
+{
+    /**
+     * Configure command metadata.
+     */
+    protected function configure()
+    {
+        parent::configure();
+
+        $this
+            ->setName('prohibits')
+            ->setAliases(array('why-not'))
+            ->setDescription('Shows which packages prevent the given package from being installed')
+            ->setHelp(<<<EOT
+Displays detailed information about why a package cannot be installed.
+
+<info>php composer.phar prohibits composer/composer</info>
+
+EOT
+            )
+        ;
+    }
+
+    /**
+     * Execute the function.
+     *
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @return int|null
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        return parent::doExecute($input, $output, true);
+    }
+}

+ 1 - 1
src/Composer/Command/RemoveCommand.php

@@ -27,7 +27,7 @@ use Symfony\Component\Console\Output\OutputInterface;
  * @author Pierre du Plessis <pdples@gmail.com>
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class RemoveCommand extends Command
+class RemoveCommand extends BaseCommand
 {
     protected function configure()
     {

+ 1 - 1
src/Composer/Command/RunScriptCommand.php

@@ -23,7 +23,7 @@ use Symfony\Component\Console\Output\OutputInterface;
 /**
  * @author Fabien Potencier <fabien.potencier@gmail.com>
  */
-class RunScriptCommand extends Command
+class RunScriptCommand extends BaseCommand
 {
     /**
      * @var array Array with command events

+ 1 - 1
src/Composer/Command/ScriptAliasCommand.php

@@ -20,7 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class ScriptAliasCommand extends Command
+class ScriptAliasCommand extends BaseCommand
 {
     private $script;
 

+ 1 - 1
src/Composer/Command/SearchCommand.php

@@ -26,7 +26,7 @@ use Composer\Plugin\PluginEvents;
 /**
  * @author Robert Schönthal <seroscho@googlemail.com>
  */
-class SearchCommand extends Command
+class SearchCommand extends BaseCommand
 {
     protected $matches;
     protected $lowMatches = array();

+ 1 - 1
src/Composer/Command/SelfUpdateCommand.php

@@ -30,7 +30,7 @@ use Symfony\Component\Finder\Finder;
  * @author Kevin Ran <kran@adobe.com>
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class SelfUpdateCommand extends Command
+class SelfUpdateCommand extends BaseCommand
 {
     const HOMEPAGE = 'getcomposer.org';
     const OLD_INSTALL_EXT = '-old.phar';

+ 1 - 1
src/Composer/Command/ShowCommand.php

@@ -38,7 +38,7 @@ use Composer\Spdx\SpdxLicenses;
  * @author Jordi Boggiano <j.boggiano@seld.be>
  * @author Jérémy Romey <jeremyFreeAgent>
  */
-class ShowCommand extends Command
+class ShowCommand extends BaseCommand
 {
     protected $versionParser;
     protected $colors;

+ 1 - 1
src/Composer/Command/StatusCommand.php

@@ -24,7 +24,7 @@ use Composer\Script\ScriptEvents;
  * @author Tiago Ribeiro <tiago.ribeiro@seegno.com>
  * @author Rui Marinho <rui.marinho@seegno.com>
  */
-class StatusCommand extends Command
+class StatusCommand extends BaseCommand
 {
     protected function configure()
     {

+ 1 - 1
src/Composer/Command/SuggestsCommand.php

@@ -17,7 +17,7 @@ use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 
-class SuggestsCommand extends Command
+class SuggestsCommand extends BaseCommand
 {
     protected function configure()
     {

+ 1 - 1
src/Composer/Command/UpdateCommand.php

@@ -28,7 +28,7 @@ use Symfony\Component\Console\Question\Question;
  * @author Jordi Boggiano <j.boggiano@seld.be>
  * @author Nils Adermann <naderman@naderman.de>
  */
-class UpdateCommand extends Command
+class UpdateCommand extends BaseCommand
 {
     protected function configure()
     {

+ 1 - 1
src/Composer/Command/ValidateCommand.php

@@ -29,7 +29,7 @@ use Symfony\Component\Console\Output\OutputInterface;
  * @author Robert Schönthal <seroscho@googlemail.com>
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class ValidateCommand extends Command
+class ValidateCommand extends BaseCommand
 {
     /**
      * configure

+ 26 - 24
src/Composer/Console/Application.php

@@ -279,33 +279,35 @@ class Application extends BaseApplication
     }
 
     /**
-     * Initializes all the composer commands
+     * Initializes all the composer commands.
      */
     protected function getDefaultCommands()
     {
-        $commands = parent::getDefaultCommands();
-        $commands[] = new Command\AboutCommand();
-        $commands[] = new Command\ConfigCommand();
-        $commands[] = new Command\DependsCommand();
-        $commands[] = new Command\InitCommand();
-        $commands[] = new Command\InstallCommand();
-        $commands[] = new Command\CreateProjectCommand();
-        $commands[] = new Command\UpdateCommand();
-        $commands[] = new Command\SearchCommand();
-        $commands[] = new Command\ValidateCommand();
-        $commands[] = new Command\ShowCommand();
-        $commands[] = new Command\SuggestsCommand();
-        $commands[] = new Command\RequireCommand();
-        $commands[] = new Command\DumpAutoloadCommand();
-        $commands[] = new Command\StatusCommand();
-        $commands[] = new Command\ArchiveCommand();
-        $commands[] = new Command\DiagnoseCommand();
-        $commands[] = new Command\RunScriptCommand();
-        $commands[] = new Command\LicensesCommand();
-        $commands[] = new Command\GlobalCommand();
-        $commands[] = new Command\ClearCacheCommand();
-        $commands[] = new Command\RemoveCommand();
-        $commands[] = new Command\HomeCommand();
+        $commands = array_merge(parent::getDefaultCommands(), array(
+            new Command\AboutCommand(),
+            new Command\ConfigCommand(),
+            new Command\DependsCommand(),
+            new Command\ProhibitsCommand(),
+            new Command\InitCommand(),
+            new Command\InstallCommand(),
+            new Command\CreateProjectCommand(),
+            new Command\UpdateCommand(),
+            new Command\SearchCommand(),
+            new Command\ValidateCommand(),
+            new Command\ShowCommand(),
+            new Command\SuggestsCommand(),
+            new Command\RequireCommand(),
+            new Command\DumpAutoloadCommand(),
+            new Command\StatusCommand(),
+            new Command\ArchiveCommand(),
+            new Command\DiagnoseCommand(),
+            new Command\RunScriptCommand(),
+            new Command\LicensesCommand(),
+            new Command\GlobalCommand(),
+            new Command\ClearCacheCommand(),
+            new Command\RemoveCommand(),
+            new Command\HomeCommand(),
+        ));
 
         if ('phar:' === substr(__FILE__, 0, 5)) {
             $commands[] = new Command\SelfUpdateCommand();

+ 8 - 0
src/Composer/Package/Link.php

@@ -64,6 +64,14 @@ class Link
         $this->prettyConstraint = $prettyConstraint;
     }
 
+    /**
+     * @return string
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
     /**
      * @return string
      */

+ 1 - 1
src/Composer/Repository/ArrayRepository.php

@@ -24,7 +24,7 @@ use Composer\Semver\Constraint\Constraint;
  *
  * @author Nils Adermann <naderman@naderman.de>
  */
-class ArrayRepository implements RepositoryInterface
+class ArrayRepository extends BaseRepository
 {
     /** @var PackageInterface[] */
     protected $packages;

+ 70 - 0
src/Composer/Repository/BaseRepository.php

@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * 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\Repository;
+
+use Composer\Package\RootPackageInterface;
+use Composer\Semver\Constraint\ConstraintInterface;
+
+/**
+ * Common ancestor class for generic repository functionality.
+ *
+ * @author Niels Keurentjes <niels.keurentjes@omines.com>
+ */
+abstract class BaseRepository implements RepositoryInterface
+{
+    /**
+     * Returns a list of links causing the requested needle packages to be installed, as an associative array with the
+     * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship
+     * as values. If recursive lookup was requested a third value is returned containing an identically formed array up
+     * to the root package.
+     *
+     * @param string|string[] $needle The package name(s) to inspect.
+     * @param ConstraintInterface|null $constraint Optional constraint to filter by.
+     * @param bool $invert Whether to invert matches to discover reasons for the package *NOT* to be installed.
+     * @param bool $recurse Whether to recursively expand the requirement tree up to the root package.
+     * @return array An associative array of arrays as described above.
+     */
+    public function getDependents($needle, $constraint = null, $invert = false, $recurse = true)
+    {
+        $needles = is_array($needle) ? $needle : array($needle);
+        $results = array();
+
+        // Loop over all currently installed packages.
+        foreach ($this->getPackages() as $package) {
+            $links = $package->getRequires();
+
+            // Replacements are considered valid reasons for a package to be installed during forward resolution
+            if (!$invert) {
+                $links += $package->getReplaces();
+            }
+
+            // Require-dev is only relevant for the root package
+            if ($package instanceof RootPackageInterface) {
+                $links += $package->getDevRequires();
+            }
+
+            // Cross-reference all discovered links to the needles
+            foreach ($links as $link) {
+                foreach ($needles as $needle) {
+                    if ($link->getTarget() === $needle) {
+                        if (is_null($constraint) || (($link->getConstraint()->matches($constraint) === !$invert))) {
+                            $results[$link->getSource()] = array($package, $link, $recurse ? $this->getDependents($link->getSource(), null, false, true) : array());
+                        }
+                    }
+                }
+            }
+        }
+        ksort($results);
+        return $results;
+    }
+}

+ 1 - 1
src/Composer/Repository/CompositeRepository.php

@@ -19,7 +19,7 @@ use Composer\Package\PackageInterface;
  *
  * @author Beau Simensen <beau@dflydev.com>
  */
-class CompositeRepository implements RepositoryInterface
+class CompositeRepository extends BaseRepository
 {
     /**
      * List of repositories