Oliver Davies
01a30f5168
Rather than using an environment variable, use -t or --target to specify the target branch to merge into (e.g. master, develop, next).
195 lines
5.3 KiB
PHP
Executable file
195 lines
5.3 KiB
PHP
Executable file
#!/usr/bin/env php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Usage: git close-pull-request -t <target>
|
|
*
|
|
* 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();
|