Skip to content

Commit 146f11a

Browse files
committed
feat: add Smart Auto Merge workflow for automated pull request merging
1 parent db75800 commit 146f11a

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

.github/workflows/merge-bot.yml

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
name: Smart Auto Merge
2+
3+
on:
4+
pull_request_review:
5+
types: [submitted]
6+
check_suite:
7+
types: [completed]
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
13+
jobs:
14+
auto-merge:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Smart Merge
19+
uses: actions/github-script@v7
20+
with:
21+
script: |
22+
23+
const pr = context.payload.pull_request;
24+
if (!pr || pr.state !== "open") return;
25+
26+
const owner = context.repo.owner;
27+
const repo = context.repo.repo;
28+
29+
// ---------------------------
30+
// 1️⃣ Check approvals
31+
// ---------------------------
32+
const reviews = await github.rest.pulls.listReviews({
33+
owner, repo,
34+
pull_number: pr.number
35+
});
36+
37+
const approvals = reviews.data.filter(r => r.state === "APPROVED");
38+
if (approvals.length < 1) {
39+
core.info("Not enough approvals");
40+
return;
41+
}
42+
43+
// ---------------------------
44+
// 2️⃣ Check CI status
45+
// ---------------------------
46+
const combined = await github.rest.repos.getCombinedStatusForRef({
47+
owner, repo,
48+
ref: pr.head.sha
49+
});
50+
51+
if (combined.data.state !== "success") {
52+
core.info("Checks not successful");
53+
return;
54+
}
55+
56+
// ---------------------------
57+
// 3️⃣ Get all commits (for authorship)
58+
// ---------------------------
59+
const commits = await github.rest.pulls.listCommits({
60+
owner, repo,
61+
pull_number: pr.number
62+
});
63+
64+
const authorMap = new Map();
65+
66+
for (const c of commits.data) {
67+
if (c.author) {
68+
authorMap.set(
69+
c.author.login,
70+
`${c.author.id}+${c.author.login}@users.noreply.github.com`
71+
);
72+
}
73+
}
74+
75+
// Ensure PR creator included
76+
authorMap.set(
77+
pr.user.login,
78+
`${pr.user.id}+${pr.user.login}@users.noreply.github.com`
79+
);
80+
81+
// ---------------------------
82+
// 4️⃣ Build Conventional Commit title
83+
// ---------------------------
84+
// Assumes PR title already follows conventional format.
85+
const commitTitle = pr.title;
86+
87+
// ---------------------------
88+
// 5️⃣ Build trailers
89+
// ---------------------------
90+
let trailers = "";
91+
92+
// Co-authors
93+
for (const [login, email] of authorMap.entries()) {
94+
trailers += `Co-authored-by: ${login} <${email}>\n`;
95+
}
96+
97+
// Reviewer trailers
98+
for (const review of approvals) {
99+
if (review.user) {
100+
trailers += `Reviewed-by: ${review.user.login} <${review.user.id}+${review.user.login}@users.noreply.github.com>\n`;
101+
}
102+
}
103+
104+
const commitMessage = `
105+
${pr.body || ""}
106+
107+
${trailers}
108+
`;
109+
110+
// ---------------------------
111+
// 6️⃣ Squash Merge via API
112+
// ---------------------------
113+
await github.rest.pulls.merge({
114+
owner,
115+
repo,
116+
pull_number: pr.number,
117+
merge_method: "squash",
118+
commit_title: commitTitle,
119+
commit_message: commitMessage.trim()
120+
});
121+
122+
core.info(`PR #${pr.number} merged successfully`);

0 commit comments

Comments
 (0)