Warn about unpushed changes
`composer status` now prevents overwriting Git package if changes have not been pushed.pull/3633/head
parent
f1aa655e61
commit
d85bad29d6
|
@ -19,6 +19,8 @@ use Composer\Downloader\ChangeReportInterface;
|
||||||
use Composer\Plugin\CommandEvent;
|
use Composer\Plugin\CommandEvent;
|
||||||
use Composer\Plugin\PluginEvents;
|
use Composer\Plugin\PluginEvents;
|
||||||
use Composer\Script\ScriptEvents;
|
use Composer\Script\ScriptEvents;
|
||||||
|
use Composer\Downloader\VcsDownloader;
|
||||||
|
use Composer\Downloader\DvcsDownloaderInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Tiago Ribeiro <tiago.ribeiro@seegno.com>
|
* @author Tiago Ribeiro <tiago.ribeiro@seegno.com>
|
||||||
|
@ -60,6 +62,7 @@ EOT
|
||||||
$composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true);
|
$composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true);
|
||||||
|
|
||||||
$errors = array();
|
$errors = array();
|
||||||
|
$unpushedChanges = array();
|
||||||
|
|
||||||
// list packages
|
// list packages
|
||||||
foreach ($installedRepo->getPackages() as $package) {
|
foreach ($installedRepo->getPackages() as $package) {
|
||||||
|
@ -75,13 +78,19 @@ EOT
|
||||||
if ($changes = $downloader->getLocalChanges($package, $targetDir)) {
|
if ($changes = $downloader->getLocalChanges($package, $targetDir)) {
|
||||||
$errors[$targetDir] = $changes;
|
$errors[$targetDir] = $changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($downloader instanceof DvcsDownloaderInterface) {
|
||||||
|
if ($unpushed = $downloader->getUnpushedChanges($targetDir)) {
|
||||||
|
$unpushedChanges[$targetDir] = $unpushed;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// output errors/warnings
|
// output errors/warnings
|
||||||
if (!$errors) {
|
if (!$errors && !$unpushed) {
|
||||||
$this->getIO()->writeError('<info>No local changes</info>');
|
$this->getIO()->writeError('<info>No local changes</info>');
|
||||||
} else {
|
} elseif ($errors) {
|
||||||
$this->getIO()->writeError('<error>You have changes in the following dependencies:</error>');
|
$this->getIO()->writeError('<error>You have changes in the following dependencies:</error>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +106,22 @@ EOT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($unpushedChanges) {
|
||||||
|
$this->getIO()->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)));
|
||||||
|
$this->getIO()->write('<info>'.$path.'</info>:');
|
||||||
|
$this->getIO()->write($indentedChanges);
|
||||||
|
} else {
|
||||||
|
$this->getIO()->write($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($errors && !$input->getOption('verbose')) {
|
if ($errors && !$input->getOption('verbose')) {
|
||||||
$this->getIO()->writeError('Use --verbose (-v) to see modified files');
|
$this->getIO()->writeError('Use --verbose (-v) to see modified files');
|
||||||
}
|
}
|
||||||
|
@ -104,6 +129,6 @@ EOT
|
||||||
// Dispatch post-status-command
|
// Dispatch post-status-command
|
||||||
$composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true);
|
$composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true);
|
||||||
|
|
||||||
return $errors ? 1 : 0;
|
return ($errors || $unpushedChanges) ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ use Composer\Config;
|
||||||
/**
|
/**
|
||||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
*/
|
*/
|
||||||
class GitDownloader extends VcsDownloader
|
class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
{
|
{
|
||||||
private $hasStashedChanges = false;
|
private $hasStashedChanges = false;
|
||||||
private $gitUtil;
|
private $gitUtil;
|
||||||
|
@ -112,6 +112,29 @@ class GitDownloader extends VcsDownloader
|
||||||
return trim($output) ?: null;
|
return trim($output) ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getUnpushedChanges($path)
|
||||||
|
{
|
||||||
|
GitUtil::cleanEnv();
|
||||||
|
$path = $this->normalizePath($path);
|
||||||
|
if (!is_dir($path.'/.git')) {
|
||||||
|
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}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
@ -119,6 +142,11 @@ class GitDownloader extends VcsDownloader
|
||||||
{
|
{
|
||||||
GitUtil::cleanEnv();
|
GitUtil::cleanEnv();
|
||||||
$path = $this->normalizePath($path);
|
$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)) {
|
if (!$changes = $this->getLocalChanges($package, $path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,21 +250,29 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
$processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
|
$processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
|
||||||
$processExecutor->expects($this->at(0))
|
$processExecutor->expects($this->at(0))
|
||||||
->method('execute')
|
->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));
|
->will($this->returnValue(0));
|
||||||
$processExecutor->expects($this->at(1))
|
$processExecutor->expects($this->at(1))
|
||||||
->method('execute')
|
->method('execute')
|
||||||
->with($this->equalTo($this->winCompat("git remote -v")))
|
->with($this->equalTo($this->winCompat("git diff --name-status ..composer/")))
|
||||||
->will($this->returnValue(0));
|
->will($this->returnValue(0));
|
||||||
$processExecutor->expects($this->at(2))
|
$processExecutor->expects($this->at(2))
|
||||||
->method('execute')
|
->method('execute')
|
||||||
->with($this->equalTo($expectedGitUpdateCommand))
|
->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
|
||||||
->will($this->returnValue(0));
|
->will($this->returnValue(0));
|
||||||
$processExecutor->expects($this->at(3))
|
$processExecutor->expects($this->at(3))
|
||||||
->method('execute')
|
->method('execute')
|
||||||
->with($this->equalTo('git branch -r'))
|
->with($this->equalTo($this->winCompat("git remote -v")))
|
||||||
->will($this->returnValue(0));
|
->will($this->returnValue(0));
|
||||||
$processExecutor->expects($this->at(4))
|
$processExecutor->expects($this->at(4))
|
||||||
|
->method('execute')
|
||||||
|
->with($this->equalTo($expectedGitUpdateCommand))
|
||||||
|
->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')
|
->method('execute')
|
||||||
->with($this->equalTo($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --")), $this->equalTo(null), $this->equalTo($this->winCompat($tmpDir)))
|
->with($this->equalTo($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --")), $this->equalTo(null), $this->equalTo($this->winCompat($tmpDir)))
|
||||||
->will($this->returnValue(0));
|
->will($this->returnValue(0));
|
||||||
|
@ -293,13 +301,21 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
$processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
|
$processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
|
||||||
$processExecutor->expects($this->at(0))
|
$processExecutor->expects($this->at(0))
|
||||||
->method('execute')
|
->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));
|
->will($this->returnValue(0));
|
||||||
$processExecutor->expects($this->at(1))
|
$processExecutor->expects($this->at(1))
|
||||||
->method('execute')
|
->method('execute')
|
||||||
->with($this->equalTo($this->winCompat("git remote -v")))
|
->with($this->equalTo($this->winCompat("git diff --name-status ..composer/")))
|
||||||
->will($this->returnValue(0));
|
->will($this->returnValue(0));
|
||||||
$processExecutor->expects($this->at(2))
|
$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')
|
->method('execute')
|
||||||
->with($this->equalTo($expectedGitUpdateCommand))
|
->with($this->equalTo($expectedGitUpdateCommand))
|
||||||
->will($this->returnValue(1));
|
->will($this->returnValue(1));
|
||||||
|
|
Loading…
Reference in New Issue