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 cd866b18..a7906886 100644 --- a/src/Helper/GitHelper.php +++ b/src/Helper/GitHelper.php @@ -615,13 +615,6 @@ public function squashCommits($base, $branchName, $ignoreMultipleAuthors = false $base.'..'.$branchName, ]))[0]; - $currentBaseHeadCommit = $this->processHelper->runCommand(['git', 'rev-parse', $base]); - $lastKnownCommonCommit = $this->processHelper->runCommand(['git', 'merge-base', '--fork-point', $base, $branchName]); - - if ($currentBaseHeadCommit !== $lastKnownCommonCommit) { - throw new MergeWorkflowException(sprintf('Failed while trying to perform merge against "%s", history is out of sync.', $base)); - } - // 0=author anything higher then 0 is the full body $commitData = StringUtil::splitLines( $this->processHelper->runCommand( diff --git a/src/Operation/RemoteMergeOperation.php b/src/Operation/RemoteMergeOperation.php index 2bc4969b..b85ad677 100644 --- a/src/Operation/RemoteMergeOperation.php +++ b/src/Operation/RemoteMergeOperation.php @@ -31,6 +31,8 @@ class RemoteMergeOperation private $performed = false; private $fastForward = false; private $withLog = false; + private $rebase = false; + private $guardSync = false; public function __construct(GitHelper $gitHelper, FilesystemHelper $filesystemHelper) { @@ -80,6 +82,8 @@ public function setMergeMessage($message, $withLog = false) public function useFastForward($fastForward = true) { $this->fastForward = (bool) $fastForward; + + return $this; } public function performMerge() @@ -143,6 +147,21 @@ public function pushToRemote() $this->gitHelper->pushToRemote($this->targetRemote, $target); } + public function rebase(bool $rebase = false) + { + $this->rebase = $rebase; + $this->guardSync = !$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; @@ -171,7 +190,31 @@ private function createSourceBranch() } if ($this->squash) { - $this->gitHelper->squashCommits($this->targetBase, $sourceBranch, $this->forceSquash); + $this->gitHelper->squashCommits($this->targetBase, $sourceBranch, $this->forceSquash, $this->guardSync); + } + + $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); + } + + // Retrieve the commits again + $currentBaseHeadCommit = $this->processHelper->runCommand(['git', 'rev-parse', $this->targetBase]); + $lastKnownCommonCommit = $this->processHelper->runCommand(['git', 'merge-base', '--fork-point', $this->targetBase, $sourceBranch]); + } + + if ($this->guardSync && $currentBaseHeadCommit !== $lastKnownCommonCommit) { + throw new MergeWorkflowException(sprintf('Failed while trying to perform merge against "%s", history is out of sync.', $this->targetBase)); + } } // Allow a callback to allow late commits list composition 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(