diff --git a/doc/04-schema.md b/doc/04-schema.md index 2742612a0..4a3927db6 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -608,7 +608,7 @@ The following options are supported: * **cache-files-dir:** Defaults to `$cache-dir/files`. Stores the zip archives of packages. * **cache-repo-dir:** Defaults to `$cache-dir/repo`. Stores repository metadata - for the `composer` type and the VCS repos of type `svn`, `github` and `*bitbucket`. + for the `composer` type and the VCS repos of type `svn`, `github` and `bitbucket`. * **cache-vcs-dir:** Defaults to `$cache-dir/vcs`. Stores VCS clones for loading VCS repository metadata for the `git`/`hg` types and to speed up installs. * **cache-files-ttl:** Defaults to `15552000` (6 months). Composer caches all @@ -622,6 +622,9 @@ The following options are supported: * **notify-on-install:** Defaults to `true`. Composer allows repositories to define a notification URL, so that they get notified whenever a package from that repository is installed. This option allows you to disable that behaviour. +* **discard-changes:** Defaults to `false` and can be any of `true`, `false` or + `stash`. This option allows you to set the default style of handling dirty + updates, specially useful for non-interactive mode. Example: diff --git a/res/composer-schema.json b/res/composer-schema.json index 2e9c4f039..f11ff824c 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -167,6 +167,10 @@ "cache-files-maxsize": { "type": ["string", "integer"], "description": "The cache max size for the files cache, defaults to \"300MiB\"." + }, + "discard-changes": { + "type": ["string"], + "description": "The default style of handling dirty updates, defaults to \"false\" and can be any of \"true\", \"false\" or \"stash\"." } } }, diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 13902ac67..1a679fcae 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -271,6 +271,10 @@ EOT function ($val) { return preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val) > 0; }, function ($val) { return $val; } ), + 'discard-changes' => array( + function ($val) { return in_array($val, array('true', 'false', 'stash')); }, + function ($val) { return $val; } + ), ); $multiConfigValues = array( 'github-protocols' => array( diff --git a/src/Composer/Config.php b/src/Composer/Config.php index f36905390..5b6fc72e8 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -33,6 +33,7 @@ class Config 'cache-ttl' => 15552000, // 6 months 'cache-files-ttl' => null, // fallback to cache-ttl 'cache-files-maxsize' => '300MiB', + 'discard-changes' => 'false', ); public static $defaultRepositories = array( @@ -174,6 +175,15 @@ class Config case 'home': return rtrim($this->process($this->config[$key]), '/\\'); + case 'discard-changes': + if (!in_array($this->config[$key], array('true', 'false', 'stash'))) { + throw new \RuntimeException( + "Invalid value of 'discard-changes' from your config: {$this->config[$key]}" + ); + } + + return $this->config[$key]; + default: if (!isset($this->config[$key])) { return null; diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index de465f3c4..ed9f2ca44 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -90,14 +90,29 @@ class GitDownloader extends VcsDownloader */ protected function cleanChanges($path, $update) { - if (!$this->io->isInteractive()) { - return parent::cleanChanges($path, $update); - } - if (!$changes = $this->getLocalChanges($path)) { return; } + $discardChanges = $this->config->get('discard-changes'); + if (!$this->io->isInteractive()) { + switch ($discardChanges) { + case 'true': + return $this->discardChanges($path); + + case 'stash': + if (!$update) { + return parent::cleanChanges($path, $update); + } + + return $this->stashChanges($path); + + case 'false': + default: + return parent::cleanChanges($path, $update); + } + } + $changes = array_map(function ($elem) { return ' '.$elem; }, preg_split('{\s*\r?\n\s*}', $changes)); @@ -110,9 +125,7 @@ class GitDownloader extends VcsDownloader while (true) { switch ($this->io->ask(' Discard changes [y,n,v,'.($update ? 's,' : '').'?]? ', '?')) { case 'y': - if (0 !== $this->process->execute('git reset --hard', $output, $path)) { - throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput()); - } + $this->discardChanges($path); break 2; case 's': @@ -120,11 +133,7 @@ class GitDownloader extends VcsDownloader goto help; } - if (0 !== $this->process->execute('git stash', $output, $path)) { - throw new \RuntimeException("Could not stash changes\n\n:".$this->process->getErrorOutput()); - } - - $this->hasStashedChanges = true; + $this->stashChanges($path); break 2; case 'n': @@ -369,4 +378,28 @@ class GitDownloader extends VcsDownloader return $output; } + + /** + * @param $path + * @throws \RuntimeException + */ + protected function discardChanges($path) + { + if (0 !== $this->process->execute('git reset --hard', $output, $path)) { + throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput()); + } + } + + /** + * @param $path + * @throws \RuntimeException + */ + protected function stashChanges($path) + { + if (0 !== $this->process->execute('git stash', $output, $path)) { + throw new \RuntimeException("Could not stash changes\n\n:".$this->process->getErrorOutput()); + } + + $this->hasStashedChanges = true; + } } diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index 48f6da741..9547dfae0 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -88,14 +88,23 @@ class SvnDownloader extends VcsDownloader */ protected function cleanChanges($path, $update) { - if (!$this->io->isInteractive()) { - return parent::cleanChanges($path, $update); - } - if (!$changes = $this->getLocalChanges($path)) { return; } + $discardChanges = $this->config->get('discard-changes'); + if (!$this->io->isInteractive()) { + switch ($discardChanges) { + case 'true': + return $this->discardChanges($path); + + case 'false': + case 'stash': + default: + return parent::cleanChanges($path, $update); + } + } + $changes = array_map(function ($elem) { return ' '.$elem; }, preg_split('{\s*\r?\n\s*}', $changes)); @@ -108,9 +117,7 @@ class SvnDownloader extends VcsDownloader while (true) { switch ($this->io->ask(' Discard changes [y,n,v,?]? ', '?')) { case 'y': - if (0 !== $this->process->execute('svn revert -R .', $output, $path)) { - throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput()); - } + $this->discardChanges($path); break 2; case 'n': @@ -150,4 +157,11 @@ class SvnDownloader extends VcsDownloader return $output; } + + protected function discardChanges($path) + { + if (0 !== $this->process->execute('svn revert -R .', $output, $path)) { + throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput()); + } + } }