Files
xget/.github/workflows/dependabot-auto-merge.yml
T

146 lines
4.5 KiB
YAML

name: Dependabot Auto Merge
on:
workflow_run:
workflows:
- CI
types:
- completed
concurrency:
group: dependabot-auto-merge-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
jobs:
merge:
name: Auto-merge Dependabot PRs
if: ${{ github.event.workflow_run.event == 'pull_request' }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Find matching Dependabot PR
id: pr
uses: actions/github-script@v9
with:
script: |
const branch = context.payload.workflow_run.head_branch;
const { owner, repo } = context.repo;
const { data: pulls } = await github.rest.pulls.list({
owner,
repo,
state: "open",
head: `${owner}:${branch}`,
per_page: 10,
});
const pr = pulls.find((item) => item.user?.login === "dependabot[bot]");
if (!pr) {
core.info(`No open Dependabot PR found for branch ${branch}.`);
core.setOutput("should_merge", "false");
return;
}
core.setOutput("should_merge", "true");
core.setOutput("number", String(pr.number));
- name: Merge PR when checks pass
if: steps.pr.outputs.should_merge == 'true'
uses: actions/github-script@v9
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pull_number = Number("${{ steps.pr.outputs.number }}");
const isSuccessful = (status) =>
status === "SUCCESS" ||
status === "SKIPPED" ||
status === "NEUTRAL";
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const getPullRequest = async () => {
const { data } = await github.rest.pulls.get({
owner,
repo,
pull_number,
});
return data;
};
let pr = await getPullRequest();
if (pr.user?.login !== "dependabot[bot]") {
core.info(`PR #${pull_number} is no longer a Dependabot PR.`);
return;
}
if (pr.draft) {
core.info(`PR #${pull_number} is still a draft.`);
return;
}
for (let attempt = 1; attempt <= 6 && pr.mergeable === null; attempt += 1) {
core.info(`PR #${pull_number} mergeability is still being computed; retry ${attempt}/6.`);
await wait(5000);
pr = await getPullRequest();
}
if (pr.mergeable !== true) {
core.info(`PR #${pull_number} is not currently mergeable; skipping auto-merge.`);
return;
}
const { data: checks } = await github.rest.checks.listForRef({
owner,
repo,
ref: pr.head.sha,
});
const requiredChecks = ["Lint", "Test and Coverage", "Type Check"];
const checkMap = new Map(checks.check_runs.map((run) => [run.name, run]));
const missing = requiredChecks.filter((name) => !checkMap.has(name));
if (missing.length > 0) {
core.info(`PR #${pull_number} is missing checks: ${missing.join(", ")}.`);
return;
}
const failed = requiredChecks.filter((name) => {
const run = checkMap.get(name);
return run.status !== "completed" || !isSuccessful(String(run.conclusion).toUpperCase());
});
if (failed.length > 0) {
core.info(`PR #${pull_number} still has pending or failing checks: ${failed.join(", ")}.`);
return;
}
try {
await github.rest.pulls.merge({
owner,
repo,
pull_number,
merge_method: "merge",
});
} catch (error) {
if (error.status === 405 || error.status === 409) {
core.info(`PR #${pull_number} could not be merged right now: ${error.message}`);
return;
}
throw error;
}
await github.rest.git.deleteRef({
owner,
repo,
ref: `heads/${pr.head.ref}`,
});
core.info(`Merged Dependabot PR #${pull_number} and deleted ${pr.head.ref}.`);