dotfiles/bin/git-close-pull-request

157 lines
4.3 KiB
PHP
Executable file

#!/usr/bin/env php
<?php
/**
* Usage: git close-pull-request
*
* 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 = getenv('TARGET_BRANCH');
if (!$this->targetBranch) {
die('No target branch specified. Aborting.');
}
$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 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));
}
}
(new ClosesPullRequests())->__invoke();