Skip to content

Commit d00527f

Browse files
committed
Add workflow to auto-merge PRs
1 parent 56fd584 commit d00527f

File tree

1 file changed

+145
-0
lines changed

1 file changed

+145
-0
lines changed

.github/workflows/auto-merge.yml

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
name: Auto-merge PRs
2+
3+
on:
4+
schedule:
5+
- cron: '*/15 * * * *'
6+
workflow_dispatch:
7+
workflow_call:
8+
9+
permissions:
10+
pull-requests: write
11+
contents: write
12+
13+
jobs:
14+
auto-merge:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Check and merge eligible PRs
19+
uses: actions/github-script@v7
20+
with:
21+
script: |
22+
const { owner, repo } = context.repo;
23+
24+
// Get the default branch
25+
const { data: repository } = await github.rest.repos.get({ owner, repo });
26+
const defaultBranch = repository.default_branch;
27+
28+
core.info(`Checking PRs against ${owner}/${repo}:${defaultBranch}`);
29+
30+
// Get all open PRs against the default branch
31+
const { data: pullRequests } = await github.rest.pulls.list({
32+
owner,
33+
repo,
34+
state: 'open',
35+
base: defaultBranch,
36+
per_page: 100
37+
});
38+
39+
core.info(`Found ${pullRequests.length} open PRs`);
40+
41+
// Process each PR
42+
for (const pr of pullRequests) {
43+
core.startGroup(`PR #${pr.number} (${pr.html_url}): ${pr.title}`);
44+
45+
try {
46+
// Check if PR has 'auto-merge' label
47+
const hasAutoMergeLabel = pr.labels.some(label => label.name === 'auto-merge');
48+
if (!hasAutoMergeLabel) {
49+
core.info(`❌ Missing 'auto-merge' label`);
50+
continue;
51+
}
52+
core.info(`✅ Has 'auto-merge' label`);
53+
54+
// Check if PR has been open for at least 2 days
55+
const createdAt = new Date(pr.created_at);
56+
const now = new Date();
57+
const daysSinceCreation = (now - createdAt) / (1000 * 60 * 60 * 24);
58+
59+
if (daysSinceCreation < 2) {
60+
core.info(`❌ PR opened ${daysSinceCreation.toFixed(2)} days ago (needs 2+ days)`);
61+
continue;
62+
}
63+
core.info(`✅ PR opened ${daysSinceCreation.toFixed(2)} days ago`);
64+
65+
// Get the most recent commit
66+
const { data: commits } = await github.rest.pulls.listCommits({
67+
owner,
68+
repo,
69+
pull_number: pr.number,
70+
per_page: 100
71+
});
72+
73+
if (commits.length === 0) {
74+
core.info(`❌ No commits found`);
75+
continue;
76+
}
77+
78+
const latestCommit = commits[commits.length - 1];
79+
const latestCommitDate = new Date(latestCommit.commit.committer.date);
80+
core.info(`Latest commit: ${latestCommit.sha.substring(0, 7)} at ${latestCommitDate.toISOString()}`);
81+
82+
// Get reviews for this PR
83+
const { data: reviews } = await github.rest.pulls.listReviews({
84+
owner,
85+
repo,
86+
pull_number: pr.number,
87+
per_page: 100
88+
});
89+
90+
const latestApproval = reviews
91+
.filter(review => review.state === 'APPROVED')
92+
.sort((a, b) => new Date(b.submitted_at) - new Date(a.submitted_at))[0];
93+
94+
if (!latestApproval) {
95+
core.info(`❌ No approvals found`);
96+
continue;
97+
}
98+
99+
const latestApprovalDate = new Date(latestApproval.submitted_at);
100+
core.info(`Latest approval: ${latestApproval.user.login} at ${latestApprovalDate.toISOString()}`);
101+
102+
if (latestApprovalDate < latestCommitDate) {
103+
core.info(`❌ Latest approval is older than the latest commit`);
104+
continue;
105+
}
106+
core.info(`✅ Has valid approval after latest commit`);
107+
108+
// Check if PR is mergeable
109+
// We need to fetch the PR again to get the mergeable state
110+
const { data: prDetails } = await github.rest.pulls.get({
111+
owner,
112+
repo,
113+
pull_number: pr.number
114+
});
115+
116+
if (prDetails.mergeable === false) {
117+
core.info(`❌ PR has merge conflicts`);
118+
continue;
119+
}
120+
121+
if (prDetails.mergeable_state === 'blocked') {
122+
core.info(`❌ PR is blocked (required checks may not have passed)`);
123+
continue;
124+
}
125+
126+
core.info(`✅ PR is mergeable (state: ${prDetails.mergeable_state})`);
127+
128+
// All conditions met - merge the PR
129+
try {
130+
await github.rest.pulls.merge({
131+
owner,
132+
repo,
133+
pull_number: pr.number,
134+
merge_method: 'squash'
135+
});
136+
core.notice(`🚀 Successfully merged PR #${pr.number} (${pr.html_url})`);
137+
} catch (error) {
138+
core.error(`❌ Failed to merge PR #${pr.number} (${pr.html_url}): ${error.message}`);
139+
}
140+
} finally {
141+
core.endGroup();
142+
}
143+
}
144+
145+
core.info('Auto-merge check complete');

0 commit comments

Comments
 (0)