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