forked from JhaSourav07/commitpulse
-
Notifications
You must be signed in to change notification settings - Fork 0
138 lines (123 loc) · 5 KB
/
Copy pathconflict-notifier.yml
File metadata and controls
138 lines (123 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
name: Merge Conflict Notifier
on:
schedule:
- cron: '*/30 * * * *' # Hourly/half-hourly fallback
push:
branches:
- main
pull_request_target:
types: [synchronize, reopened, edited]
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
check-conflicts:
runs-on: ubuntu-latest
steps:
- name: Check open PRs for merge conflicts
uses: actions/github-script@v7
with:
script: |
const label = 'needs-rebase';
// Ensure the label exists in the repo
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
});
} catch {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
color: 'e11d48',
description: 'This PR has merge conflicts and needs a rebase.',
});
}
// Fetch all open PRs
const { data: openPRs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
});
for (const pr of openPRs) {
// GitHub computes `mergeable` lazily — fetch each PR individually with retries to trigger and wait for computation
let fullPR = null;
for (let attempt = 1; attempt <= 3; attempt++) {
const { data } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
});
fullPR = data;
if (fullPR.mergeable !== null) {
break;
}
core.info(`PR #${pr.number}: mergeable is null, waiting 5 seconds (attempt ${attempt}/3)...`);
await new Promise(resolve => setTimeout(resolve, 5000));
}
const isConflicting = fullPR.mergeable === false;
const currentLabels = fullPR.labels.map(l => l.name);
const alreadyLabeled = currentLabels.includes(label);
if (isConflicting) {
// Apply label if not already applied
if (!alreadyLabeled) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [label],
});
}
// Check existing comments to enforce a 24-hour notification gap
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
per_page: 100,
});
const conflictComments = comments.filter(c =>
c.user?.login === 'github-actions[bot]' &&
c.body?.includes('merge conflicts with the main branch')
);
let shouldComment = false;
if (conflictComments.length === 0) {
shouldComment = true;
} else {
const lastComment = conflictComments[conflictComments.length - 1];
const lastCommentTime = new Date(lastComment.created_at).getTime();
const nowTime = new Date().getTime();
const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;
if (nowTime - lastCommentTime >= TWENTY_FOUR_HOURS_MS) {
shouldComment = true;
}
}
if (shouldComment) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: `⚠️ Hey @${fullPR.user.login}, this PR has merge conflicts with the \`main\` branch.
Please pull the latest changes and resolve the conflicts so we can review it!
\`\`\`bash
git fetch origin
git rebase origin/main
# resolve any conflicts, then:
git push --force-with-lease
\`\`\`
Once resolved, the \`needs-rebase\` label will be removed automatically on the next check. 🙌`,
});
}
} else if (!isConflicting && alreadyLabeled) {
// PR is no longer conflicting — remove the stale label
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label,
});
}
}