diff --git a/doc/03-cli.md b/doc/03-cli.md index 5c4638b7c..68f7ba762 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -198,6 +198,7 @@ If you do not specify a package, composer will prompt you to search for a packag ### Options * **--dev:** Add packages to `require-dev`. +* **--dry-run:** Simulate the command without actually doing anything. * **--prefer-source:** Install packages from `source` when available. * **--prefer-dist:** Install packages from `dist` when available. * **--no-progress:** Removes the progress display that can mess with some @@ -236,6 +237,7 @@ uninstalled. ### Options * **--dev:** Remove packages from `require-dev`. +* **--dry-run:** Simulate the command without actually doing anything. * **--no-progress:** Removes the progress display that can mess with some terminals or scripts which don't handle backspace characters. * **--no-update:** Disables the automatic update of the dependencies. diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index f5562e01e..b95af9f72 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -38,6 +38,7 @@ class RemoveCommand extends BaseCommand ->setDefinition(array( new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Packages that should be removed.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), @@ -92,26 +93,44 @@ EOT } } + $dryRun = $input->getOption('dry-run'); + $toRemove = array(); foreach ($packages as $package) { if (isset($composer[$type][$package])) { - $json->removeLink($type, $composer[$type][$package]); + if ($dryRun) { + $toRemove[$type][] = $composer[$type][$package]; + } else { + $json->removeLink($type, $composer[$type][$package]); + } } elseif (isset($composer[$altType][$package])) { $io->writeError('' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); if ($io->isInteractive()) { if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ', true)) { - $json->removeLink($altType, $composer[$altType][$package]); + if ($dryRun) { + $toRemove[$altType][] = $composer[$altType][$package]; + } else { + $json->removeLink($altType, $composer[$altType][$package]); + } } } } elseif (isset($composer[$type]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) { foreach ($matches as $matchedPackage) { - $json->removeLink($type, $matchedPackage); + if ($dryRun) { + $toRemove[$type][] = $matchedPackage; + } else { + $json->removeLink($type, $matchedPackage); + } } } elseif (isset($composer[$altType]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) { foreach ($matches as $matchedPackage) { $io->writeError('' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); if ($io->isInteractive()) { if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ', true)) { - $json->removeLink($altType, $matchedPackage); + if ($dryRun) { + $toRemove[$altType][] = $matchedPackage; + } else { + $json->removeLink($altType, $matchedPackage); + } } } } @@ -128,6 +147,21 @@ EOT $this->resetComposer(); $composer = $this->getComposer(true, $input->getOption('no-plugins')); + if ($dryRun) { + $rootPackage = $composer->getPackage(); + $links = array( + 'require' => $rootPackage->getRequires(), + 'require-dev' => $rootPackage->getDevRequires(), + ); + foreach ($toRemove as $type => $packages) { + foreach ($packages as $package) { + unset($links[$type][$package]); + } + } + $rootPackage->setRequires($links['require']); + $rootPackage->setDevRequires($links['require-dev']); + } + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); @@ -149,6 +183,7 @@ EOT ->setWhitelistTransitiveDependencies(!$input->getOption('no-update-with-dependencies')) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ->setRunScripts(!$input->getOption('no-scripts')) + ->setDryRun($dryRun) ; $status = $install->run(); diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 10506170f..a881e7de1 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -21,6 +21,8 @@ use Composer\Installer; use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; use Composer\Package\Version\VersionParser; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\BasePackage; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\CompositeRepository; @@ -48,6 +50,7 @@ class RequireCommand extends InitCommand ->setDefinition(array( new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), 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 even for dev versions.'), new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'), @@ -195,7 +198,7 @@ EOT } } - if (!$this->updateFileCleanly($this->json, $requirements, $requireKey, $removeKey, $sortPackages)) { + if (!$input->getOption('dry-run') && !$this->updateFileCleanly($this->json, $requirements, $requireKey, $removeKey, $sortPackages)) { $composerDefinition = $this->json->read(); foreach ($requirements as $package => $version) { $composerDefinition[$requireKey][$package] = $version; @@ -211,19 +214,35 @@ EOT } try { - return $this->doUpdate($input, $output, $io, $requirements); + return $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey); } catch (\Exception $e) { $this->revertComposerFile(false); throw $e; } } - private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements) + private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements, $requireKey, $removeKey) { // Update packages $this->resetComposer(); $composer = $this->getComposer(true, $input->getOption('no-plugins')); + if ($input->getOption('dry-run')) { + $rootPackage = $composer->getPackage(); + $links = array( + 'require' => $rootPackage->getRequires(), + 'require-dev' => $rootPackage->getDevRequires(), + ); + $loader = new ArrayLoader(); + $newLinks = $loader->parseLinks($rootPackage->getName(), $rootPackage->getPrettyVersion(), BasePackage::$supportedLinkTypes[$requireKey]['description'], $requirements); + $links[$requireKey] = array_merge($links[$requireKey], $newLinks); + foreach ($requirements as $package => $constraint) { + unset($links[$removeKey][$package]); + } + $rootPackage->setRequires($links['require']); + $rootPackage->setDevRequires($links['require-dev']); + } + $updateDevMode = !$input->getOption('update-no-dev'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); @@ -250,6 +269,7 @@ EOT ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ->setPreferStable($input->getOption('prefer-stable')) ->setPreferLowest($input->getOption('prefer-lowest')) + ->setDryRun($input->getOption('dry-run')) ; // if no lock is present, or the file is brand new, we do not do a