common/config/beachball/github-functions/pull-request.ts (70 lines of code) (raw):
import { spawnSync } from 'child_process';
import { Octokit } from '@octokit/rest';
import { github } from './github-instance';
import { IPullRequest, IRepoDetails } from './types';
import { repoDetails } from './constants';
export interface IGetPullRequestFromCommitParams {
github: Octokit;
repoDetails: IRepoDetails;
// Commit hash
commit: string;
// Provide this to have it included in the resulting IPullRequest
authorEmail?: string;
verbose?: boolean;
}
/**
* Get the pull request info corresponding to the given commit.
* (The `author.email` property is only present if `authorEmail` is provided.)
*/
async function getPullRequestForCommit(params: IGetPullRequestFromCommitParams): Promise<IPullRequest | undefined> {
const { github, repoDetails, commit, authorEmail, verbose } = params;
verbose && console.log(`Looking for the PR containing ${commit}...`);
try {
// Attempt to directly find the PR corresponding to the commit from the change file
const result = await github.repos.listPullRequestsAssociatedWithCommit({
commit_sha: commit,
...repoDetails
});
// Filter out unmerged PRs, in case the commit has been in multiple PRs but only one got merged
// (check merged_at because that's only set if the PR has been merged, whereas merge_commit_sha
// is set even for un-merged PRs, to the most recent intermediate merge)
const prs = result.data.filter((result) => !!result.merged_at);
if (prs.length > 1) {
// In case the commit was in PRs to multiple branches or something?
console.warn(`Multiple PRs found for ${commit}:`);
console.warn(prs.map((pr) => ` ${pr.url}`).join('\n'));
}
if (prs[0]) {
verbose && console.log(`Found matching PR #${prs[0].number}.\n`);
return processPullRequestApiResponse(prs[0], authorEmail);
}
} catch (ex) {
console.warn(`Error finding PR for ${commit}`, ex);
return;
}
console.warn(`Could not find a PR matching ${commit}.`);
}
/**
* Convert a GitHub API response to an IPullRequest.
* The `author.email` property is only present if `authorEmail` is provided.
*/
export function processPullRequestApiResponse(pr: any, authorEmail?: string): IPullRequest {
return {
number: pr.number,
url: pr.html_url,
author: {
email: authorEmail,
username: pr.user.login,
url: pr.user.html_url
}
};
}
export async function getPrNumber(commit: string): Promise<number | undefined> {
// Look for (presumably) the PR number at the end of the first line of the commit
try {
// Get the actual commit message which should contain the PR number
const logResult = spawnSync('git', ['log', '--pretty=format:%s', '-n', '1', commit]);
if (logResult.status === 0) {
const message = logResult.stdout.toString().trim();
const prMatch = message.split(/\r?\n/)[0].match(/\(#(\d+)\)$/m);
if (prMatch) {
return Number(prMatch[1]);
}
}
} catch (ex) {
console.warn(`Could not get commit message for ${commit} to find PR number (trying another method):`, ex);
}
// Or fetch from GitHub API
console.log(`Attempting to fetch pull request corresponding to ${commit}...`);
const pr = await getPullRequestForCommit({
commit: commit,
github,
repoDetails: repoDetails
});
if (pr) {
console.log('...success!'); // failure message is logged by getPullRequestForCommit
return pr.number;
}
}