diff --git a/doc/03-cli.md b/doc/03-cli.md index 46cb2fe8d..2933a2168 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -313,6 +313,53 @@ uninstalled. * **--apcu-autoloader-prefix:** Use a custom prefix for the APCu autoloader cache. Implicitly enables `--apcu-autoloader`. +## reinstall + +The `reinstall` command looks up installed packages by name, +uninstalls them and reinstalls them. This lets you do a clean install +of a package if you messed with its files, or if you wish to change +the installation type using --prefer-install. + +```sh +php composer.phar reinstall acme/foo acme/bar +``` + +You can specify more than one package name to reinstall, or use a +wildcard to select several packages at once: + +```sh +php composer.phar reinstall "acme/*" +``` + +### Options + +* **--prefer-install:** There are two ways of downloading a package: `source` + and `dist`. Composer uses `dist` by default. If you pass + `--prefer-install=source` (or `--prefer-source`) Composer will install from + `source` if there is one. This is useful if you want to make a bugfix to a + project and get a local git clone of the dependency directly. + To get the legacy behavior where Composer use `source` automatically for dev + versions of packages, use `--prefer-install=auto`. See also [config.preferred-install](06-config.md#preferred-install). + Passing this flag will override the config value. +* **--no-autoloader:** Skips autoloader generation. +* **--no-scripts:** Skips execution of scripts defined in `composer.json`. +* **--no-progress:** Removes the progress display that can mess with some + terminals or scripts which don't handle backspace characters. +* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster + autoloader. This is recommended especially for production, but can take + a bit of time to run so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize-autoloader`. +* **--apcu-autoloader:** Use APCu to cache found/not-found classes. +* **--apcu-autoloader-prefix:** Use a custom prefix for the APCu autoloader cache. + Implicitly enables `--apcu-autoloader`. +* **--ignore-platform-reqs:** ignore all platform requirements. This only + has an effect in the context of the autoloader generation for the + reinstall command. +* **--ignore-platform-req:** ignore a specific platform requirement. This only + has an effect in the context of the autoloader generation for the + reinstall command. + ## check-platform-reqs The check-platform-reqs command checks that your PHP and extensions versions diff --git a/src/Composer/Command/ReinstallCommand.php b/src/Composer/Command/ReinstallCommand.php new file mode 100644 index 000000000..c96965019 --- /dev/null +++ b/src/Composer/Command/ReinstallCommand.php @@ -0,0 +1,163 @@ + + * 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 Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\Transaction; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class ReinstallCommand extends BaseCommand +{ + protected function configure() + { + $this + ->setName('reinstall') + ->setDescription('Uninstalls and reinstalls the given package names') + ->setDefinition(array( + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'), + new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), + new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'List of package names to reinstall, can include a wildcard (*) to match any substring.'), + )) + ->setHelp( + <<reinstall command looks up installed packages by name, +uninstalls them and reinstalls them. This lets you do a clean install +of a package if you messed with its files, or if you wish to change +the installation type using --prefer-install. + +php composer.phar reinstall acme/foo "acme/bar-*" + +Read more at https://getcomposer.org/doc/03-cli.md#reinstall +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = $this->getIO(); + + $composer = $this->getComposer(true, $input->getOption('no-plugins')); + + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $packagesToReinstall = array(); + $packageNamesToReinstall = array(); + foreach ($input->getArgument('packages') as $pattern) { + $patternRegexp = BasePackage::packageNameToRegexp($pattern); + $matched = false; + foreach ($localRepo->getCanonicalPackages() as $package) { + if (preg_match($patternRegexp, $package->getName())) { + $matched = true; + $packagesToReinstall[] = $package; + $packageNamesToReinstall[] = $package->getName(); + } + } + + if (!$matched) { + $io->writeError('Pattern "' . $pattern . '" does not match any currently installed packages.'); + } + } + + if (!$packagesToReinstall) { + $io->writeError('Found no packages to reinstall, aborting.'); + return 1; + } + + $uninstallOperations = array(); + foreach ($packagesToReinstall as $package) { + $uninstallOperations[] = new UninstallOperation($package); + } + + // make sure we have a list of install operations ordered by dependency/plugins + $presentPackages = $localRepo->getPackages(); + $resultPackages = $presentPackages; + foreach ($presentPackages as $index => $package) { + if (in_array($package->getName(), $packageNamesToReinstall, true)) { + unset($presentPackages[$index]); + } + } + $transaction = new Transaction($presentPackages, $resultPackages); + $installOperations = $transaction->getOperations(); + + // reverse-sort the uninstalls based on the install order + $installOrder = array(); + foreach ($installOperations as $index => $op) { + if ($op instanceof InstallOperation && !$op->getPackage() instanceof AliasPackage) { + $installOrder[$op->getPackage()->getName()] = $index; + } + } + usort($uninstallOperations, function ($a, $b) use ($installOrder) { + return $installOrder[$b->getPackage()->getName()] - $installOrder[$a->getPackage()->getName()]; + }); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'reinstall', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $config = $composer->getConfig(); + list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input); + + $installationManager = $composer->getInstallationManager(); + $downloadManager = $composer->getDownloadManager(); + $package = $composer->getPackage(); + + $ignorePlatformReqs = $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false); + + $installationManager->setOutputProgress(!$input->getOption('no-progress')); + if ($input->getOption('no-plugins')) { + $installationManager->disablePlugins(); + } + + $downloadManager->setPreferSource($preferSource); + $downloadManager->setPreferDist($preferDist); + + $installationManager->execute($localRepo, $uninstallOperations, true, !$input->getOption('no-scripts')); + $installationManager->execute($localRepo, $installOperations, true, !$input->getOption('no-scripts')); + + if (!$input->getOption('no-autoloader')) { + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); + + $generator = $composer->getAutoloadGenerator(); + $generator->setClassMapAuthoritative($authoritative); + $generator->setApcu($apcu, $apcuPrefix); + $generator->setRunScripts(!$input->getOption('no-scripts')); + $generator->setIgnorePlatformRequirements($ignorePlatformReqs); + $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); + } + + return 0; + } +} diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 8b72eb31a..bdb8700aa 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -487,6 +487,7 @@ class Application extends BaseApplication new Command\OutdatedCommand(), new Command\CheckPlatformReqsCommand(), new Command\FundCommand(), + new Command\ReinstallCommand(), )); if (strpos(__FILE__, 'phar:') === 0) {