-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
support auto-update:opt-in label #23
base: main
Are you sure you want to change the base?
Conversation
After testing it works exactly as intended. Please consider merging and let me know if you need me to make changes |
Been using this for a week and it works great! Any feedback? |
It would be helpful if you could specify the label to look for in the action config, that way if a person wants it to run on all PRs, they can leave it blank, otherwise they can specify the label in their action yml and go from there. |
@StummeJ sure, feel free to implement such behavior |
I don't have the time, but just feedback if you want to see this grt mainlined |
Are you a maintainer of this project? If I hear it from a maintainer I will consider it when I have time. Otherwise I'm not interested in taking guesses what the code owners want |
✌️ |
Easy to add that label check @StummeJ. Took me like 15m action.yml name: Auto-update
author: Thibault Derousseaux <[email protected]>
description: Automatically keep pull requests with auto-merged enabled up to date with their base branch.
inputs:
github_token:
description: Token for the GitHub API.
default: ${{ github.token }}
required: false
required_label:
description: Label required to enable auto-merge.
default:
required: false
runs:
using: node16
main: dist/index.js
branding:
icon: refresh-cw
color: blue import { getInput, group, info, setFailed, warning } from "@actions/core";
import { context, getOctokit } from "@actions/github";
import type { GitHub } from "@actions/github/lib/utils.js";
import type { PaginatingEndpoints } from "@octokit/plugin-paginate-rest";
import type { PushEvent } from "@octokit/webhooks-definitions/schema.js";
import ensureError from "ensure-error";
const unupdatablePullRequestCommentBody =
"Cannot auto-update because of conflicts.";
type PullRequest =
PaginatingEndpoints["GET /repos/{owner}/{repo}/pulls"]["response"]["data"][number];
const handleUnupdatablePullRequest = async (
pullRequest: PullRequest,
{
octokit,
}: Readonly<{
octokit: InstanceType<typeof GitHub>;
}>,
): Promise<void> => {
try {
const {
head: {
repo: { full_name },
sha,
},
number,
} = pullRequest;
const [owner, repo] = full_name.split("/");
const {
data: { commit: lastCommit },
} = await octokit.request("GET /repos/{owner}/{repo}/commits/{ref}", {
owner,
ref: sha,
repo,
});
const lastCommitter = lastCommit.committer;
if (!lastCommitter) {
// noinspection ExceptionCaughtLocallyJS
throw new Error(`Missing committer on last commit ${sha}`);
}
const comments = await octokit.paginate(
"GET /repos/{owner}/{repo}/issues/{issue_number}/comments",
{
...context.repo,
issue_number: number,
since: lastCommitter.date,
},
);
const existingUnupdatablePullRequestComment = comments.find(
({ body }) => body === unupdatablePullRequestCommentBody,
);
if (existingUnupdatablePullRequestComment) {
info(
`Already commented since last commit: ${existingUnupdatablePullRequestComment.html_url}`,
);
return;
}
const { data: newComment } = await octokit.request(
"POST /repos/{owner}/{repo}/issues/{issue_number}/comments",
{
...context.repo,
body: unupdatablePullRequestCommentBody,
issue_number: number,
},
);
info(`Commented: ${newComment.html_url}`);
} catch (error: unknown) {
warning(ensureError(error));
}
};
const handlePullRequest = async (
pullRequest: PullRequest,
required_label: string | undefined,
{
eventPayload,
octokit,
}: Readonly<{
eventPayload: PushEvent;
octokit: InstanceType<typeof GitHub>;
}>,
): Promise<void> => {
if (
required_label &&
!pullRequest.auto_merge &&
!pullRequest.labels.some(({ name }) => name === `${required_label}`)
) {
info(
`Pull request #${pullRequest.number} does not have auto-merge enabled and does not have ${required_label} label`,
);
return;
}
if (pullRequest.base.sha === eventPayload.after) {
info(`Pull request #${pullRequest.number} is already up to date`);
return;
}
await group(
`Attempting to update pull request #${pullRequest.number}`,
async () => {
try {
await octokit.request(
"PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch",
{
...context.repo,
pull_number: pullRequest.number,
},
);
info("Updated!");
} catch (error: unknown) {
warning(ensureError(error));
await handleUnupdatablePullRequest(pullRequest, { octokit });
}
},
);
};
const run = async () => {
try {
const token = getInput("github_token", { required: true });
const required_label = getInput("required_label", { required: false });
const octokit = getOctokit(token);
if (context.eventName !== "push") {
// noinspection ExceptionCaughtLocallyJS
throw new Error(
`Expected to be triggered by a "push" event but received a "${context.eventName}" event`,
);
}
const eventPayload = context.payload as PushEvent;
// See https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#webhook-payload-object-34.
const base = eventPayload.ref.slice("refs/heads/".length);
info(`Fetching pull requests based on "${base}"`);
const pullRequests: readonly PullRequest[] = await octokit.paginate(
"GET /repos/{owner}/{repo}/pulls",
{
...context.repo,
base,
state: "open",
},
);
info(
`Fetched pull requests: ${JSON.stringify(
pullRequests.map((pullRequest) => pullRequest.number),
)}`,
);
for (const pullRequest of pullRequests) {
// PRs are handled sequentially to avoid breaking GitHub's log grouping feature.
// eslint-disable-next-line no-await-in-loop
await handlePullRequest(pullRequest, required_label, {
eventPayload,
octokit,
});
}
} catch (error: unknown) {
setFailed(ensureError(error));
}
};
void run(); |
Handles /issues/22
Testing now