diff --git a/src/Command/PullRequest/PullRequestMergeCommand.php b/src/Command/PullRequest/PullRequestMergeCommand.php
index aefaf354..69253302 100644
--- a/src/Command/PullRequest/PullRequestMergeCommand.php
+++ b/src/Command/PullRequest/PullRequestMergeCommand.php
@@ -40,6 +40,8 @@ protected function configure()
->addOption('fast-forward', null, InputOption::VALUE_NONE, 'Merge pull-request using fast forward (no merge commit will be created)')
->addOption('squash', null, InputOption::VALUE_NONE, 'Squash the PR before merging')
->addOption('force-squash', null, InputOption::VALUE_NONE, 'Force squashing the PR, even if there are multiple authors (this will implicitly use --squash)')
+ ->addOption('rebase', null, InputOption::VALUE_NONE, 'Rebase the PR before merging')
+ ->addOption('ensure-sync', null, InputOption::VALUE_NONE, 'Ensure that the pull request history is up to date before merging')
->addOption('switch', null, InputOption::VALUE_REQUIRED, 'Switch the base of the pull request before merging')
->addOption('pat', null, InputOption::VALUE_REQUIRED, 'Give the PR\'s author a pat on the back after the merge')
->setHelp(
@@ -79,6 +81,16 @@ protected function configure()
$ gush %command.name% --fast-forward 12
+If you want to perform an automatic rebase against the base branch before merging, the --rebase option can be used
+in order to try that operation:
+
+ $ gush %command.name% --rebase 12
+
+A synchronization check against the base branch can be done before the merge, passing the --ensure-sync option; so
+if this check fails, the operation will be aborted:
+
+ $ gush %command.name% --ensure-sync 12
+
After the pull request is merged, you can give a pat on the back to its author using the --pat.
This option accepts the name of any configured pat's name:
@@ -173,6 +185,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
$mergeOperation->setTarget($targetRemote, $targetBranch);
$mergeOperation->setSource($sourceRemote, $sourceBranch);
$mergeOperation->squashCommits($squash, $input->getOption('force-squash'));
+ $mergeOperation->guardSync($input->getOption('ensure-sync'));
+ $mergeOperation->rebase($input->getOption('rebase'));
$mergeOperation->switchBase($input->getOption('switch'));
$mergeOperation->setMergeMessage($messageCallback);
$mergeOperation->useFastForward($input->getOption('fast-forward'));
diff --git a/src/Helper/GitHelper.php b/src/Helper/GitHelper.php
index 80bea7c0..e2258e5d 100644
--- a/src/Helper/GitHelper.php
+++ b/src/Helper/GitHelper.php
@@ -12,6 +12,7 @@
namespace Gush\Helper;
use Gush\Exception\CannotSquashMultipleAuthors;
+use Gush\Exception\MergeWorkflowException;
use Gush\Exception\UserException;
use Gush\Exception\WorkingTreeIsNotReady;
use Gush\Operation\RemoteMergeOperation;
@@ -342,7 +343,7 @@ public function clearTempBranches()
*/
public function createRemoteMergeOperation()
{
- return new RemoteMergeOperation($this, $this->filesystemHelper);
+ return new RemoteMergeOperation($this, $this->filesystemHelper, $this->processHelper);
}
/**
@@ -386,7 +387,7 @@ public function mergeBranch($base, $sourceBranch, $commitMessage, $fastForward =
'allow_failures' => false,
],
[
- 'line' => ['git', 'commit', '-F', $tmpName],
+ 'line' => ['git', 'commit', '--file', $tmpName],
'allow_failures' => false,
],
]
@@ -433,7 +434,7 @@ public function mergeBranchWithLog($base, $sourceBranch, $commitMessage, $source
$tmpName = $this->filesystemHelper->newTempFilename();
file_put_contents($tmpName, $commitMessage);
- $this->processHelper->runCommand(['git', 'commit', '-F', $tmpName]);
+ $this->processHelper->runCommand(['git', 'commit', '--file', $tmpName]);
return trim($this->processHelper->runCommand('git rev-parse HEAD'));
}
@@ -443,15 +444,7 @@ public function addNotes($notes, $commitHash, $ref)
$tmpName = $this->filesystemHelper->newTempFilename();
file_put_contents($tmpName, $notes);
- $commands = [
- 'git',
- 'notes',
- '--ref='.$ref,
- 'add',
- '-F',
- $tmpName,
- $commitHash,
- ];
+ $commands = ['git', 'notes', '--ref', $ref, 'add', '--file', $tmpName, $commitHash];
$this->processHelper->runCommand($commands, true);
}
@@ -596,21 +589,19 @@ public function squashCommits($base, $branchName, $ignoreMultipleAuthors = false
// Get commits only in the branch but not in base (in reverse order)
// we can't use --max-count here because that is applied before the reversing!
//
- // using git-log works better then finding the fork-point with git-merge-base
+ // using git-log works better than finding the fork-point with git-merge-base
// because this protects against edge cases were there is no valid fork-point
- $firstCommitHash = StringUtil::splitLines($this->processHelper->runCommand(
- [
- 'git',
- '--no-pager',
- 'log',
- '--oneline',
- '--no-color',
- '--format=%H',
- '--reverse',
- $base.'..'.$branchName,
- ]
- ))[0];
+ $firstCommitHash = StringUtil::splitLines($this->processHelper->runCommand([
+ 'git',
+ '--no-pager',
+ 'log',
+ '--oneline',
+ '--no-color',
+ '--format=%H',
+ '--reverse',
+ $base.'..'.$branchName,
+ ]))[0];
// 0=author anything higher then 0 is the full body
$commitData = StringUtil::splitLines(
@@ -631,13 +622,7 @@ public function squashCommits($base, $branchName, $ignoreMultipleAuthors = false
$message = implode("\n", $commitData);
$this->reset($base);
- $this->commit(
- $message,
- [
- 'a',
- '-author' => $author,
- ]
- );
+ $this->commit($message, ['all', 'author' => $author]);
}
public function syncWithRemote($remote, $branchName = null)
@@ -671,9 +656,17 @@ public function commit($message, array $options = [])
foreach ($options as $option => $value) {
if (is_int($option)) {
- $params[] = '-'.$value;
+ if (1 === strlen($value)) {
+ $params[] = '-'.$value;
+ } else {
+ $params[] = '--'.$value;
+ }
} else {
- $params[] = '-'.$option;
+ if (1 === strlen($option)) {
+ $params[] = '-'.$option;
+ } else {
+ $params[] = '--'.$option;
+ }
$params[] = $value;
}
}
@@ -681,7 +674,7 @@ public function commit($message, array $options = [])
$tmpName = $this->filesystemHelper->newTempFilename();
file_put_contents($tmpName, $message);
- $this->processHelper->runCommand(array_merge(['git', 'commit', '-F', $tmpName], $params));
+ $this->processHelper->runCommand(array_merge(['git', 'commit', '--file', $tmpName], $params));
}
/**
diff --git a/src/Operation/RemoteMergeOperation.php b/src/Operation/RemoteMergeOperation.php
index 2bc4969b..ef22fec8 100644
--- a/src/Operation/RemoteMergeOperation.php
+++ b/src/Operation/RemoteMergeOperation.php
@@ -13,11 +13,13 @@
use Gush\Helper\FilesystemHelper;
use Gush\Helper\GitHelper;
+use Gush\Helper\ProcessHelper;
class RemoteMergeOperation
{
private $gitHelper;
private $filesystemHelper;
+ private $processHelper;
private $sourceBranch;
private $sourceRemote;
@@ -31,11 +33,14 @@ class RemoteMergeOperation
private $performed = false;
private $fastForward = false;
private $withLog = false;
+ private $rebase = false;
+ private $guardSync = false;
- public function __construct(GitHelper $gitHelper, FilesystemHelper $filesystemHelper)
+ public function __construct(GitHelper $gitHelper, FilesystemHelper $filesystemHelper, ProcessHelper $processHelper)
{
$this->gitHelper = $gitHelper;
$this->filesystemHelper = $filesystemHelper;
+ $this->processHelper = $processHelper;
}
public function setSource($remote, $branch)
@@ -80,6 +85,8 @@ public function setMergeMessage($message, $withLog = false)
public function useFastForward($fastForward = true)
{
$this->fastForward = (bool) $fastForward;
+
+ return $this;
}
public function performMerge()
@@ -143,6 +150,20 @@ public function pushToRemote()
$this->gitHelper->pushToRemote($this->targetRemote, $target);
}
+ public function rebase(bool $rebase = false)
+ {
+ $this->rebase = $rebase;
+
+ return $this;
+ }
+
+ public function guardSync(bool $guardSync = false)
+ {
+ $this->guardSync = $guardSync;
+
+ return $this;
+ }
+
private function createBaseBranch()
{
$targetBranch = null !== $this->switchBase ? $this->switchBase : $this->targetBranch;
@@ -170,6 +191,24 @@ private function createSourceBranch()
$this->targetBranch = $this->switchBase;
}
+ $currentBaseHeadCommit = $this->processHelper->runCommand(['git', 'rev-parse', $this->targetBase]);
+ $lastKnownCommonCommit = $this->processHelper->runCommand(['git', 'merge-base', '--fork-point', $this->targetBase, $sourceBranch]);
+
+ if ($currentBaseHeadCommit !== $lastKnownCommonCommit) {
+ if ($this->rebase) {
+ try {
+ $this->processHelper->runCommand(['git', 'pull', '--rebase', $this->targetBase]);
+ } catch (\Exception $e) {
+ // Error, abort the rebase operation
+ $this->processHelper->runCommand(['git', 'rebase', '--abort'], true);
+
+ throw new MergeWorkflowException(sprintf('Git rebase failed while trying to synchronize history against "%s".', $this->targetBase), 0, $e);
+ }
+ } elseif ($this->guardSync) {
+ throw new MergeWorkflowException(sprintf('Failed while trying to perform merge against "%s", history is out of sync.', $this->targetBase));
+ }
+ }
+
if ($this->squash) {
$this->gitHelper->squashCommits($this->targetBase, $sourceBranch, $this->forceSquash);
}
diff --git a/tests/Command/PullRequest/PullRequestMergeCommandTest.php b/tests/Command/PullRequest/PullRequestMergeCommandTest.php
index aef6b1a2..7761135f 100644
--- a/tests/Command/PullRequest/PullRequestMergeCommandTest.php
+++ b/tests/Command/PullRequest/PullRequestMergeCommandTest.php
@@ -455,7 +455,7 @@ protected function getGitConfigHelper($notes = true)
return $helper;
}
- private function getLocalGitHelper($message = null, $squash = false, $forceSquash = false, $switch = null, $withComments = true, $fastForward = false)
+ private function getLocalGitHelper($message = null, $squash = false, $forceSquash = false, $switch = null, $withComments = true, $fastForward = false, $guardSync = false, $rebase = false)
{
$helper = parent::getGitHelper();
@@ -470,6 +470,8 @@ private function getLocalGitHelper($message = null, $squash = false, $forceSquas
$mergeOperation->setTarget('gushphp', 'base_ref')->shouldBeCalled();
$mergeOperation->setSource('cordoval', 'head_ref')->shouldBeCalled();
$mergeOperation->squashCommits($squash, $forceSquash)->shouldBeCalled();
+ $mergeOperation->guardSync($guardSync)->shouldBeCalled();
+ $mergeOperation->rebase($rebase)->shouldBeCalled();
$mergeOperation->switchBase($switch)->shouldBeCalled();
$mergeOperation->useFastForward($fastForward)->shouldBeCalled();
$mergeOperation->setMergeMessage(
diff --git a/tests/Helper/GitHelperTest.php b/tests/Helper/GitHelperTest.php
index d0f9f82b..bbe0492f 100644
--- a/tests/Helper/GitHelperTest.php
+++ b/tests/Helper/GitHelperTest.php
@@ -176,7 +176,7 @@ public function merges_remote_branch_in_clean_wc()
'allow_failures' => false,
],
[
- 'line' => ['git', 'commit', '-F', $tmpName],
+ 'line' => ['git', 'commit', '--file', $tmpName],
'allow_failures' => false,
],
]