#!/usr/bin/env php * * Run this from a branch which has an upstream remote branch, and an associated * pull request. * * The script will merge the branch into master, push master (which will * automatically close the pull request), and delete both the local and remote * branches. * * Based on a script by @christoomey. Translated into PHP. */ class ClosesPullRequests { private $targetBranch; private $localBranch; private $remoteBranch; private const CI_ERROR = 'error'; private const CI_PENDING = 'pending'; private const CI_SUCCESS = 'success'; public function __construct() { $this->localBranch = exec('git rev-parse --abbrev-ref HEAD'); $this->targetBranch = $this->getTargetBranchFromArgs(); $this->remoteBranch = exec('git rev-parse --abbrev-ref --symbolic-full-name @{u}'); $this->remoteBranch = str_replace('origin/', '', $this->remoteBranch); } public function __invoke(): void { $this->confirmCiStatusIsPassing(); // TODO: Check that the current branch has a tracking branch. $this->fetchOrigin(); $this->ensureFeatureBranchInSync(); $this->ensureTargetBranchInSync(); $this->checkoutTargetBranch(); $this->mergeLocalBranch(); $this->pushTargetBranch(); $this->deleteRemoteBranch(); $this->deleteLocalBranch(); } private function getTargetBranchFromArgs(): string { if (!$targetBranchName = $this->getArg(['-t', '--target'])) { die('Invalid target branch specified. Aborting.'); } return $targetBranchName; } private function confirmCiStatusIsPassing(): void { echo 'Confirming ci-status on PR is green...' . PHP_EOL; // TODO: Check for failures, or skip if there is no CI. $errors = [ self::CI_ERROR => 'Aborting: CI error', self::CI_PENDING => 'Aborting: CI pending', ]; if (array_key_exists($status = exec('hub ci-status'), $errors)) { die($errors[$status]); } } private function fetchOrigin(): void { print 'Fetching origin to confirm local and remote in sync...' . PHP_EOL; exec("git fetch origin"); } private function ensureTargetBranchInSync(): void { $this->ensureBranchInSyncWithUpstream( $this->targetBranch, $this->targetBranch ); } private function ensureFeatureBranchInSync(): void { $this->ensureBranchInSyncWithUpstream( $this->localBranch, $this->remoteBranch ); } private function ensureBranchInSyncWithUpstream( string $localBranch, string $remoteBranch ): void { echo sprintf( 'Ensuring that %s is in sync with its upstream...', $localBranch ) . PHP_EOL; $localCommitTip = $this->tipCommitOfBranch($localBranch); $remoteCommitTip = $this->tipCommitOfBranch(sprintf( 'origin/%s', $remoteBranch )); if ($localCommitTip != $remoteCommitTip) { die(sprintf( 'Branch %s was out of date, needs rebasing. Aborting.', $localBranch )); } } private function tipCommitOfBranch(string $branchName): string { return exec(sprintf('git rev-parse %s', $branchName)); } private function checkoutTargetBranch(): void { print sprintf('Checking out %s...' . PHP_EOL, $this->targetBranch); exec(sprintf('git checkout %s', $this->targetBranch)); } private function mergeLocalBranch(): void { echo sprintf( 'Merging %s into %s...' . PHP_EOL, $this->localBranch, $this->targetBranch ); exec(sprintf('git merge --ff-only %s', $this->localBranch)); } public function pushTargetBranch(): void { print(sprintf('Pushing updated %s branch...', $this->targetBranch)); exec(sprintf('git push origin %s', $this->targetBranch)); } public function deleteRemoteBranch(): void { echo 'Deleting remote branch...' . PHP_EOL; exec(sprintf('git push origin :%s', $this->remoteBranch)); } public function deleteLocalBranch(): void { echo 'Deleting local branch...' . PHP_EOL; exec(sprintf('git branch -d %s', $this->localBranch)); } private function getArg(array $flags): ?string { $args = $_SERVER['argv']; if (!$this->hasArg($flags)) { return null; } $foundArgs = array_intersect($args, $flags); // Find the position of the first matching argument within the arguments // array. $position = array_search(current($foundArgs), $args); // Return the value by finding the next value. For example: // [2 => '-t', 3 => 'master'] if (!array_key_exists($position + 1, $args)) { return null; } return $args[$position + 1]; } private function hasArg(array $flags): bool { $args = $_SERVER['argv']; return !empty(array_intersect($args, $flags)); } } (new ClosesPullRequests())->__invoke();