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());
+ }
+ }
}