Browse Source

Merge remote-tracking branch 'greg0ire/improve-status-for-unpushed-branches'

Jordi Boggiano 9 years ago
parent
commit
9f4f8a9578

+ 28 - 3
src/Composer/Command/StatusCommand.php

@@ -19,6 +19,8 @@ use Composer\Downloader\ChangeReportInterface;
 use Composer\Plugin\CommandEvent;
 use Composer\Plugin\PluginEvents;
 use Composer\Script\ScriptEvents;
+use Composer\Downloader\VcsDownloader;
+use Composer\Downloader\DvcsDownloaderInterface;
 
 /**
  * @author Tiago Ribeiro <tiago.ribeiro@seegno.com>
@@ -61,6 +63,7 @@ EOT
 
         $errors = array();
         $io = $this->getIO();
+        $unpushedChanges = array();
 
         // list packages
         foreach ($installedRepo->getPackages() as $package) {
@@ -76,13 +79,19 @@ EOT
                 if ($changes = $downloader->getLocalChanges($package, $targetDir)) {
                     $errors[$targetDir] = $changes;
                 }
+
+                if ($downloader instanceof DvcsDownloaderInterface) {
+                    if ($unpushed = $downloader->getUnpushedChanges($targetDir)) {
+                        $unpushedChanges[$targetDir] = $unpushed;
+                    }
+                }
             }
         }
 
         // output errors/warnings
-        if (!$errors) {
+        if (!$errors && !$unpushed) {
             $io->writeError('<info>No local changes</info>');
-        } else {
+        } elseif ($errors) {
             $io->writeError('<error>You have changes in the following dependencies:</error>');
         }
 
@@ -98,6 +107,22 @@ EOT
             }
         }
 
+        if ($unpushedChanges) {
+            $io->writeError('<warning>You have unpushed changes on the current branch in the following dependencies:</warning>');
+
+            foreach ($unpushedChanges as $path => $changes) {
+                if ($input->getOption('verbose')) {
+                    $indentedChanges = implode("\n", array_map(function ($line) {
+                        return '    ' . ltrim($line);
+                    }, explode("\n", $changes)));
+                    $io->write('<info>'.$path.'</info>:');
+                    $io->write($indentedChanges);
+                } else {
+                    $io->write($path);
+                }
+            }
+        }
+
         if ($errors && !$input->getOption('verbose')) {
             $io->writeError('Use --verbose (-v) to see modified files');
         }
@@ -105,6 +130,6 @@ EOT
         // Dispatch post-status-command
         $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true);
 
-        return $errors ? 1 : 0;
+        return ($errors ? 1 : 0) + ($unpushedChanges ? 2 : 0);
     }
 }

+ 29 - 0
src/Composer/Downloader/DvcsDownloaderInterface.php

@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Downloader;
+
+/**
+ * DVCS Downloader interface.
+ *
+ * @author James Titcumb <james@asgrim.com>
+ */
+interface DvcsDownloaderInterface
+{
+    /**
+     * Checks for unpushed changes to a current branch
+     *
+     * @param  string      $path package directory
+     * @return string|null changes or null
+     */
+    public function getUnpushedChanges($path);
+}

+ 29 - 1
src/Composer/Downloader/GitDownloader.php

@@ -23,7 +23,7 @@ use Composer\Config;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class GitDownloader extends VcsDownloader
+class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
 {
     private $hasStashedChanges = false;
     private $hasDiscardedChanges = false;
@@ -112,6 +112,29 @@ class GitDownloader extends VcsDownloader
         return trim($output) ?: null;
     }
 
+    public function getUnpushedChanges($path)
+    {
+        GitUtil::cleanEnv();
+        $path = $this->normalizePath($path);
+        if (!$this->hasMetadataRepository($path)) {
+            return;
+        }
+
+        $command = 'git rev-parse --abbrev-ref HEAD';
+        if (0 !== $this->process->execute($command, $output, $path)) {
+            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
+        }
+
+        $branch = trim($output);
+
+        $command = sprintf('git diff --name-status %s..composer/%s', $branch, $branch);
+        if (0 !== $this->process->execute($command, $output, $path)) {
+            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
+        }
+
+        return trim($output) ?: null;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -119,6 +142,11 @@ class GitDownloader extends VcsDownloader
     {
         GitUtil::cleanEnv();
         $path = $this->normalizePath($path);
+
+        if (null !== $this->getUnpushedChanges($path)) {
+            throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch.');
+        }
+
         if (!$changes = $this->getLocalChanges($package, $path)) {
             return;
         }

+ 22 - 6
tests/Composer/Test/Downloader/GitDownloaderTest.php

@@ -267,21 +267,29 @@ class GitDownloaderTest extends TestCase
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
         $processExecutor->expects($this->at(0))
             ->method('execute')
-            ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
+            ->with($this->equalTo($this->winCompat("git rev-parse --abbrev-ref HEAD")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(1))
             ->method('execute')
-            ->with($this->equalTo($this->winCompat("git remote -v")))
+            ->with($this->equalTo($this->winCompat("git diff --name-status ..composer/")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(2))
             ->method('execute')
-            ->with($this->equalTo($expectedGitUpdateCommand))
+            ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(3))
             ->method('execute')
-            ->with($this->equalTo('git branch -r'))
+            ->with($this->equalTo($this->winCompat("git remote -v")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(4))
+            ->method('execute')
+            ->with($this->equalTo($this->winCompat($expectedGitUpdateCommand)), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir)))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(5))
+            ->method('execute')
+            ->with($this->equalTo('git branch -r'))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(6))
             ->method('execute')
             ->with($this->equalTo($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir)))
             ->will($this->returnValue(0));
@@ -309,13 +317,21 @@ class GitDownloaderTest extends TestCase
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
         $processExecutor->expects($this->at(0))
             ->method('execute')
-            ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
+            ->with($this->equalTo($this->winCompat("git rev-parse --abbrev-ref HEAD")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(1))
             ->method('execute')
-            ->with($this->equalTo($this->winCompat("git remote -v")))
+            ->with($this->equalTo($this->winCompat("git diff --name-status ..composer/")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(2))
+            ->method('execute')
+            ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(3))
+            ->method('execute')
+            ->with($this->equalTo($this->winCompat("git remote -v")))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(4))
             ->method('execute')
             ->with($this->equalTo($expectedGitUpdateCommand))
             ->will($this->returnValue(1));