Эх сурвалжийг харах

Fix uncommitted change detection, refs #3633

Jordi Boggiano 9 жил өмнө
parent
commit
be4d385942

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

@@ -81,7 +81,7 @@ EOT
                 }
 
                 if ($downloader instanceof DvcsDownloaderInterface) {
-                    if ($unpushed = $downloader->getUnpushedChanges($targetDir)) {
+                    if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) {
                         $unpushedChanges[$targetDir] = $unpushed;
                     }
                 }
@@ -123,8 +123,8 @@ EOT
             }
         }
 
-        if ($errors && !$input->getOption('verbose')) {
-            $io->writeError('Use --verbose (-v) to see modified files');
+        if (($errors || $unpushedChanges) && !$input->getOption('verbose')) {
+            $io->writeError('Use --verbose (-v) to see a list of files');
         }
 
         // Dispatch post-status-command

+ 6 - 3
src/Composer/Downloader/DvcsDownloaderInterface.php

@@ -12,6 +12,8 @@
 
 namespace Composer\Downloader;
 
+use Composer\Package\PackageInterface;
+
 /**
  * DVCS Downloader interface.
  *
@@ -22,8 +24,9 @@ interface DvcsDownloaderInterface
     /**
      * Checks for unpushed changes to a current branch
      *
-     * @param  string      $path package directory
-     * @return string|null changes or null
+     * @param  PackageInterface $package package directory
+     * @param  string           $path package directory
+     * @return string|null      changes or null
      */
-    public function getUnpushedChanges($path);
+    public function getUnpushedChanges(PackageInterface $package, $path);
 }

+ 35 - 4
src/Composer/Downloader/GitDownloader.php

@@ -112,7 +112,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
         return trim($output) ?: null;
     }
 
-    public function getUnpushedChanges($path)
+    public function getUnpushedChanges(PackageInterface $package, $path)
     {
         GitUtil::cleanEnv();
         $path = $this->normalizePath($path);
@@ -127,11 +127,41 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
 
         $branch = trim($output);
 
-        $command = sprintf('git diff --name-status %s..composer/%s', $branch, $branch);
+        // we are on a detached HEAD tag so no need to check for changes
+        if ($branch === 'HEAD') {
+            return;
+        }
+
+        // check that the branch exists in composer remote, if not then we should assume it is an unpushed branch
+        $command = sprintf('git rev-parse --verify composer/%s', $branch);
+        if (0 !== $this->process->execute($command, $output, $path)) {
+            $composerBranch = preg_replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $package->getPrettyVersion());
+            $branches = '';
+            if (0 === $this->process->execute('git branch -r', $output, $path)) {
+                $branches = $output;
+            }
+            // add 'v' in front of the branch if it was stripped when generating the pretty name
+            if (!preg_match('{^\s+composer/'.preg_quote($composerBranch).'$}m', $branches) && preg_match('{^\s+composer/v'.preg_quote($composerBranch).'$}m', $branches)) {
+                $composerBranch = 'v' . $composerBranch;
+            }
+        } else {
+            $composerBranch = $branch;
+        }
+
+        $command = sprintf('git diff --name-status composer/%s...%s', $composerBranch, $branch);
         if (0 !== $this->process->execute($command, $output, $path)) {
             throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
         }
 
+        if (trim($output)) {
+            // fetch from both to make sure we have up to date remotes
+            $this->process->execute('git fetch composer && git fetch origin', $output, $path);
+
+            if (0 !== $this->process->execute($command, $output, $path)) {
+                throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
+            }
+        }
+
         return trim($output) ?: null;
     }
 
@@ -143,8 +173,9 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
         GitUtil::cleanEnv();
         $path = $this->normalizePath($path);
 
-        if (null !== $this->getUnpushedChanges($path)) {
-            throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch.');
+        $unpushed = $this->getUnpushedChanges($package, $path);
+        if ($unpushed && ($this->io->isInteractive() || $this->config->get('discard-changes') !== true)) {
+            throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch: '."\n".$unpushed);
         }
 
         if (!$changes = $this->getLocalChanges($package, $path)) {

+ 35 - 15
tests/Composer/Test/Downloader/GitDownloaderTest.php

@@ -271,25 +271,29 @@ class GitDownloaderTest extends TestCase
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(1))
             ->method('execute')
-            ->with($this->equalTo($this->winCompat("git diff --name-status ..composer/")))
+            ->with($this->equalTo($this->winCompat("git rev-parse --verify composer/")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(2))
             ->method('execute')
-            ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
+            ->with($this->equalTo($this->winCompat("git diff --name-status composer/...")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(3))
             ->method('execute')
-            ->with($this->equalTo($this->winCompat("git remote -v")))
+            ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
             ->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)))
+            ->with($this->equalTo($this->winCompat("git remote -v")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(5))
             ->method('execute')
-            ->with($this->equalTo('git branch -r'))
+            ->with($this->equalTo($this->winCompat($expectedGitUpdateCommand)), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir)))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(6))
+            ->method('execute')
+            ->with($this->equalTo('git branch -r'))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(7))
             ->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));
@@ -321,17 +325,21 @@ class GitDownloaderTest extends TestCase
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(1))
             ->method('execute')
-            ->with($this->equalTo($this->winCompat("git diff --name-status ..composer/")))
+            ->with($this->equalTo($this->winCompat("git rev-parse --verify composer/")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(2))
             ->method('execute')
-            ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
+            ->with($this->equalTo($this->winCompat("git diff --name-status composer/...")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(3))
             ->method('execute')
-            ->with($this->equalTo($this->winCompat("git remote -v")))
+            ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(4))
+            ->method('execute')
+            ->with($this->equalTo($this->winCompat("git remote -v")))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(5))
             ->method('execute')
             ->with($this->equalTo($expectedGitUpdateCommand))
             ->will($this->returnValue(1));
@@ -356,33 +364,45 @@ 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 rev-parse --verify composer/")))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(2))
+            ->method('execute')
+            ->with($this->equalTo($this->winCompat("git diff --name-status composer/...")))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(3))
+            ->method('execute')
+            ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(4))
+           ->method('execute')
+            ->with($this->equalTo($this->winCompat("git remote -v")))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(5))
             ->method('execute')
             ->with($this->equalTo($expectedFirstGitUpdateCommand))
             ->will($this->returnValue(1));
-        $processExecutor->expects($this->at(4))
+        $processExecutor->expects($this->at(7))
             ->method('execute')
             ->with($this->equalTo($this->winCompat("git --version")))
             ->will($this->returnValue(0));
-        $processExecutor->expects($this->at(5))
+        $processExecutor->expects($this->at(8))
             ->method('execute')
             ->with($this->equalTo($this->winCompat("git remote -v")))
             ->will($this->returnValue(0));
-        $processExecutor->expects($this->at(6))
+        $processExecutor->expects($this->at(9))
             ->method('execute')
             ->with($this->equalTo($expectedSecondGitUpdateCommand))
             ->will($this->returnValue(0));
-        $processExecutor->expects($this->at(7))
+        $processExecutor->expects($this->at(11))
             ->method('execute')
             ->with($this->equalTo('git branch -r'))
             ->will($this->returnValue(0));
-        $processExecutor->expects($this->at(8))
+        $processExecutor->expects($this->at(13))
             ->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));