octokit-plugin-create-pull-request
Version:
Octokit plugin to create a pull request with multiple file changes
199 lines (198 loc) • 5.38 kB
JavaScript
import { createTree } from "./create-tree.js";
import { createCommit } from "./create-commit.js";
async function composeCreatePullRequest(octokit, {
owner,
repo,
title,
body,
base,
head,
createWhenEmpty,
changes: changesOption,
draft = false,
labels = [],
forceFork = false,
update = false
}) {
if (head === base) {
throw new Error(
'[octokit-plugin-create-pull-request] "head" cannot be the same value as "base"'
);
}
const changes = Array.isArray(changesOption) ? changesOption : [changesOption];
if (changes.length === 0)
throw new Error(
'[octokit-plugin-create-pull-request] "changes" cannot be an empty array'
);
const state = { octokit, owner, repo };
const { data: repository, headers } = await octokit.request(
"GET /repos/{owner}/{repo}",
{
owner,
repo
}
);
const isUser = !!headers["x-oauth-scopes"];
if (!repository.permissions) {
throw new Error(
"[octokit-plugin-create-pull-request] Missing authentication"
);
}
if (!base) {
base = repository.default_branch;
}
state.ownerOrFork = owner;
if (forceFork || isUser && !repository.permissions.push) {
const user = await octokit.request("GET /user");
const forks = await octokit.request("GET /repos/{owner}/{repo}/forks", {
owner,
repo
});
const hasFork = forks.data.find(
/* v8 ignore next - fork owner can be null, but we don't test that */
(fork) => fork.owner && fork.owner.login === user.data.login
);
if (!hasFork) {
await octokit.request("POST /repos/{owner}/{repo}/forks", {
owner,
repo
});
}
state.ownerOrFork = user.data.login;
}
const {
data: [latestCommit]
} = await octokit.request("GET /repos/{owner}/{repo}/commits", {
owner,
repo,
sha: base,
per_page: 1
});
state.latestCommitSha = latestCommit.sha;
state.latestCommitTreeSha = latestCommit.commit.tree.sha;
const baseCommitTreeSha = latestCommit.commit.tree.sha;
for (const change of changes) {
let treeCreated = false;
if (change.files && Object.keys(change.files).length) {
const latestCommitTreeSha = await createTree(
state,
change
);
if (latestCommitTreeSha) {
state.latestCommitTreeSha = latestCommitTreeSha;
treeCreated = true;
}
}
if (treeCreated || change.emptyCommit !== false) {
state.latestCommitSha = await createCommit(
state,
treeCreated,
change
);
}
}
const hasNoChanges = baseCommitTreeSha === state.latestCommitTreeSha;
if (hasNoChanges && createWhenEmpty === false) {
return null;
}
const branchInfo = await octokit.graphql(
`
query getPullRequestsForBranch($owner: String!, $repo: String!, $head: String!) {
repository(name: $repo, owner: $owner) {
ref(qualifiedName: $head) {
associatedPullRequests(first: 1, states: OPEN) {
edges {
node {
id
number
url
}
}
}
}
}
}`,
{
owner: state.ownerOrFork,
repo,
head
}
);
const branchExists = !!branchInfo.repository.ref;
const existingPullRequest = branchInfo.repository.ref?.associatedPullRequests?.edges?.[0]?.node;
if (existingPullRequest && !update) {
throw new Error(
`[octokit-plugin-create-pull-request] Pull request already exists: ${existingPullRequest.url}. Set update=true to enable updating`
);
}
if (branchExists) {
await octokit.request("PATCH /repos/{owner}/{repo}/git/refs/{ref}", {
owner: state.ownerOrFork,
repo,
sha: state.latestCommitSha,
ref: `heads/${head}`,
force: true
});
} else {
await octokit.request("POST /repos/{owner}/{repo}/git/refs", {
owner: state.ownerOrFork,
repo,
sha: state.latestCommitSha,
ref: `refs/heads/${head}`
});
}
const pullRequestOptions = {
owner,
repo,
head: `${state.ownerOrFork}:${head}`,
base,
title,
body,
draft
};
let res;
if (existingPullRequest) {
res = await octokit.request(
"PATCH /repos/{owner}/{repo}/pulls/{pull_number}",
{
pull_number: existingPullRequest.number,
...pullRequestOptions
}
);
} else {
res = await octokit.request(
"POST /repos/{owner}/{repo}/pulls",
pullRequestOptions
);
}
if (labels.length) {
try {
const labelRes = await octokit.request(
"POST /repos/{owner}/{repo}/issues/{number}/labels",
{
owner,
repo,
number: res.data.number,
labels
}
);
if (labelRes.data.length > labels.length) {
octokit.log.warn(
"The pull request already contains more labels than the ones provided. This could be due to the presence of previous labels."
);
}
} catch (error) {
if (error.status === 403) {
octokit.log.warn(
"You do not have permissions to apply labels to this pull request. However, the pull request has been successfully created without the requested labels."
);
return res;
}
if (error.status !== 403) throw error;
}
}
return res;
}
export {
composeCreatePullRequest
};