diff --git a/src/Adapter/Adapter.php b/src/Adapter/Adapter.php index b865db57..bdf44aec 100644 --- a/src/Adapter/Adapter.php +++ b/src/Adapter/Adapter.php @@ -369,4 +369,13 @@ public function getReleaseAssets($id); * @throws AdapterException when deleting of release failed */ public function removeRelease($id); + + /** + * Deletes the remote source branch used in a pull request. + * + * @param int $id + * + * @return mixed + */ + public function removePullRequestSourceBranch($id); } diff --git a/src/Command/PullRequest/PullRequestCloseCommand.php b/src/Command/PullRequest/PullRequestCloseCommand.php index 85a454af..d788635f 100644 --- a/src/Command/PullRequest/PullRequestCloseCommand.php +++ b/src/Command/PullRequest/PullRequestCloseCommand.php @@ -30,12 +30,13 @@ protected function configure() ->setDescription('Closes a pull request') ->addArgument('pr_number', InputArgument::REQUIRED, 'Pull Request number to be closed') ->addOption('message', 'm', InputOption::VALUE_REQUIRED, 'Closing comment') + ->addOption('remove-source-branch', null, InputOption::VALUE_REQUIRED, 'Remove remote source branch after closing own pull request', 'no') ->setHelp( <<%command.name% command closes a Pull Request for either the current or the given organization and repository: - $ gush %command.name% 12 -m"let's try to keep it low profile guys." + $ gush %command.name% 12 -m"let's try to keep it low profile guys." --remove-source-branch=yes EOF ) @@ -47,11 +48,16 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $prNumber = $input->getArgument('pr_number'); - $closingComment = $input->getOption('message'); - $adapter = $this->getAdapter(); + $prNumber = $input->getArgument('pr_number'); + $pr = $adapter->getPullRequest($prNumber); + $authenticatedUser = $this->getParameter($input, 'authentication')['username']; + $removeSourceBranch = $input->getOption('remove-source-branch'); + if ('yes' === $removeSourceBranch && $pr['user'] !== $authenticatedUser) { + throw new UserException(sprintf('`--remove-source-branch` option cannot be used with pull requests that aren\'t owned by the authenticated user (%s)', $authenticatedUser)); + } + $closingComment = $input->getOption('message'); $adapter->closePullRequest($prNumber); if ($input->getOption('message')) { @@ -61,6 +67,17 @@ protected function execute(InputInterface $input, OutputInterface $output) $url = $adapter->getPullRequest($prNumber)['url']; $this->getHelper('gush_style')->success("Closed {$url}"); + // Post close options + if ($pr['user'] === $authenticatedUser) { + if ('yes' !== $removeSourceBranch) { + $removeSourceBranch = $this->getHelper('gush_style')->choice('Delete source branch?', ['yes', 'no'], 'no'); + } + if ('yes' === $removeSourceBranch) { + $adapter->removePullRequestSourceBranch($pr['number']); + $this->getHelper('gush_style')->note(sprintf('Remote source branch %s:%s has been removed.', $pr['head']['user'], $pr['head']['ref'])); + } + } + return self::COMMAND_SUCCESS; } } diff --git a/src/Command/PullRequest/PullRequestMergeCommand.php b/src/Command/PullRequest/PullRequestMergeCommand.php index aefaf354..14645af8 100644 --- a/src/Command/PullRequest/PullRequestMergeCommand.php +++ b/src/Command/PullRequest/PullRequestMergeCommand.php @@ -42,6 +42,8 @@ protected function configure() ->addOption('force-squash', null, InputOption::VALUE_NONE, 'Force squashing the PR, even if there are multiple authors (this will implicitly use --squash)') ->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') + ->addOption('remove-source-branch', null, InputOption::VALUE_NONE, 'Remove remote source branch after merging own pull request') + ->addOption('no-remove-source-branch', null, InputOption::VALUE_NONE, 'Don\'t remove remote source branch after merging own pull request') ->setHelp( <<<'EOF' The %command.name% command merges the given pull request: @@ -99,6 +101,14 @@ protected function configure() which has precedence to the predefined configuration. The whole pat configuration will be ignored and no pat will be placed if the pull request is authored by yourself! + +If you are the author of the pull request, you'll be prompted if the remote source branch will be removed after a successful merge. +Also, you can pass your choice for this action with the --remove-source-branch and --no-remove-source-branch +options: + + $ gush %command.name% --remove-source-branch + + $ gush %command.name% --no-remove-source-branch EOF ) ; @@ -141,6 +151,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $targetLabel = sprintf('Target: %s/%s', $targetRemote, $targetBranch); } + $authenticatedUser = $this->getParameter($input, 'authentication')['username']; + $removeSourceBranch = $input->getOption('remove-source-branch'); + if ($removeSourceBranch && $pr['user'] !== $authenticatedUser) { + throw new UserException(sprintf('`--remove-source-branch` option cannot be used with pull requests that aren\'t owned by the authenticated user (%s)', $authenticatedUser)); + } + $styleHelper->title(sprintf('Merging pull-request #%d - %s', $prNumber, $pr['title'])); $styleHelper->text([sprintf('Source: %s/%s', $sourceRemote, $sourceBranch), $targetLabel]); @@ -197,15 +213,18 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->addClosedPullRequestNote($pr, $mergeCommit, $squash, $input->getOption('switch')); } - if ($pr['user'] !== $this->getParameter($input, 'authentication')['username']) { - $patComment = $this->givePatToPullRequestAuthor($pr, $input->getOption('pat')); - if ($patComment) { - $styleHelper->note(sprintf('Pat given to @%s at %s.', $pr['user'], $patComment)); + $styleHelper->success([$mergeNote, $pr['url']]); + + // Post merge options + if ($pr['user'] === $authenticatedUser) { + if ($removeSourceBranch || (!$input->getOption('no-remove-source-branch') && 'yes' === $this->getHelper('gush_style')->choice('Delete source branch?', ['yes', 'no'], 'no'))) { + $adapter->removePullRequestSourceBranch($pr['number']); + $styleHelper->note(sprintf('Remote source branch %s:%s has been removed.', $sourceRemote, $sourceBranch)); } + } elseif ($patComment = $this->givePatToPullRequestAuthor($pr, $input->getOption('pat'))) { + $styleHelper->note(sprintf('Pat given to @%s at %s.', $pr['user'], $patComment)); } - $styleHelper->success([$mergeNote, $pr['url']]); - return self::COMMAND_SUCCESS; } catch (CannotSquashMultipleAuthors $e) { $styleHelper->error([ @@ -415,4 +434,13 @@ private function givePatToPullRequestAuthor(array $pr, $pat) return $this->getAdapter()->createComment($pr['number'], $patMessage); } } + + protected function initialize(InputInterface $input, OutputInterface $output) + { + parent::initialize($input, $output); + + if ($input->getOption('remove-source-branch') && $input->getOption('no-remove-source-branch')) { + throw new UserException('Options `--remove-source-branch` and `--no-remove-source-branch` cannot be used toghether'); + } + } } diff --git a/src/ThirdParty/Bitbucket/BitbucketRepoAdapter.php b/src/ThirdParty/Bitbucket/BitbucketRepoAdapter.php index 5f84a4bc..8c355401 100644 --- a/src/ThirdParty/Bitbucket/BitbucketRepoAdapter.php +++ b/src/ThirdParty/Bitbucket/BitbucketRepoAdapter.php @@ -13,6 +13,7 @@ use Gush\Adapter\BaseAdapter; use Gush\Exception\AdapterException; +use Gush\Exception\UnsupportedOperationException; use Herrera\Version\Parser; use Herrera\Version\Validator as VersionValidator; @@ -519,4 +520,10 @@ protected function adaptPullRequestStructure(array $pr) ], ]; } + + public function removePullRequestSourceBranch($id) + { + /** @link https://developer.atlassian.com/static/rest/bitbucket-server/4.7.1/bitbucket-branch-rest.html#idp45632 REST Resources Provided By: Bitbucket Server - Branch */ + throw new UnsupportedOperationException('Bitbucket client "gentle/bitbucket-api" doesn\'t support references.'); + } } diff --git a/src/ThirdParty/Github/GitHubAdapter.php b/src/ThirdParty/Github/GitHubAdapter.php index ca02dbfd..fa19582e 100644 --- a/src/ThirdParty/Github/GitHubAdapter.php +++ b/src/ThirdParty/Github/GitHubAdapter.php @@ -625,6 +625,14 @@ public function createReleaseAssets($id, $name, $contentType, $content) return $asset['id']; } + public function removePullRequestSourceBranch($id) + { + $api = $this->client->api('git_data')->references(); + $pr = $this->getPullRequest($id); + + return $api->remove($pr['user'], $pr['head']['repo'], 'heads/'.$pr['head']['ref']); + } + protected function adaptIssueStructure(array $issue) { return [ diff --git a/src/ThirdParty/Gitlab/Adapter/GitLabRepoAdapter.php b/src/ThirdParty/Gitlab/Adapter/GitLabRepoAdapter.php index 08b26512..12ef6195 100644 --- a/src/ThirdParty/Gitlab/Adapter/GitLabRepoAdapter.php +++ b/src/ThirdParty/Gitlab/Adapter/GitLabRepoAdapter.php @@ -278,4 +278,12 @@ public function createReleaseAssets($id, $name, $contentType, $content) { throw new UnsupportedOperationException('Releases are not supported by Gitlab.'); } + + public function removePullRequestSourceBranch($id) + { + $api = $this->client->api('repo'); + $pr = $this->getPullRequest($id); + + return $api->deleteBranch($this->getCurrentProject()->id, $pr['head']['ref']); + } } diff --git a/tests/Fixtures/Adapter/TestAdapter.php b/tests/Fixtures/Adapter/TestAdapter.php index 7c3536bd..7a5a3b15 100644 --- a/tests/Fixtures/Adapter/TestAdapter.php +++ b/tests/Fixtures/Adapter/TestAdapter.php @@ -544,6 +544,11 @@ public function createReleaseAssets($id, $name, $contentType, $content) return self::RELEASE_ASSET_NUMBER; } + public function removePullRequestSourceBranch($id) + { + return []; + } + /** * {@inheritdoc} */