Skip to content

Commit 25fbd96

Browse files
authored
Refactor merge conflict detection workflow
Signed-off-by: cheese-cakee <farzanaman99@gmail.com>
1 parent f1966df commit 25fbd96

File tree

1 file changed

+100
-56
lines changed

1 file changed

+100
-56
lines changed
Lines changed: 100 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: PythonBot - Check Merge Conflicts
1+
name: Merge Conflict Bot
22

33
on:
44
pull_request_target:
@@ -10,9 +10,10 @@ on:
1010
permissions:
1111
contents: read
1212
pull-requests: write
13+
issues: write
14+
statuses: write
1315

1416
concurrency:
15-
# Use PR number if available, otherwise use the Commit SHA
1617
group: "check-conflicts-${{ github.event.pull_request.number || github.sha }}"
1718
cancel-in-progress: true
1819

@@ -25,63 +26,106 @@ jobs:
2526
uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3
2627
with:
2728
egress-policy: audit
28-
29+
2930
- name: Check for merge conflicts
30-
env:
31-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32-
run: |
33-
REPO="${{ github.repository }}"
34-
# 1. Determine which PRs to check
35-
if [ "${{ github.event_name }}" == "push" ]; then
36-
echo "Triggered by push to main. Fetching all open PRs..."
37-
PR_NUMBERS=$(gh pr list --repo $REPO --state open --json number --jq '.[].number')
38-
else
39-
echo "Triggered by PR update."
40-
PR_NUMBERS="${{ github.event.pull_request.number }}"
41-
fi
42-
43-
# 2. Loop through the list (works for 1 PR or 100 PRs)
44-
for PR_NUMBER in $PR_NUMBERS; do
45-
echo "---------------------------------------------------"
46-
echo "Checking merge status for PR #$PR_NUMBER in repository $REPO..."
47-
48-
49-
for i in {1..10}; do
50-
PR_JSON=$(gh api repos/$REPO/pulls/$PR_NUMBER)
51-
MERGEABLE_STATE=$(echo "$PR_JSON" | jq -r '.mergeable_state')
52-
53-
echo "Attempt $i: Current mergeable state: $MERGEABLE_STATE"
54-
55-
if [ "$MERGEABLE_STATE" != "unknown" ]; then
56-
break
57-
fi
58-
59-
echo "State is 'unknown', waiting 2 seconds..."
60-
sleep 2
61-
done
62-
63-
if [ "$MERGEABLE_STATE" = "dirty" ]; then
64-
COMMENT=$(cat <<EOF
65-
Hi, this is MergeConflictBot.
66-
Your pull request cannot be merged because it contains **merge conflicts**.
67-
68-
Please resolve these conflicts locally and push the changes.
31+
uses: actions/github-script@v7
32+
with:
33+
script: |
34+
const { owner, repo } = context.repo;
35+
const BOT_SIGNATURE = '';
6936
70-
To assist you, please read:
71-
- [Resolving Merge Conflicts](docs/sdk_developers/merge_conflicts.md)
72-
- [Rebasing Guide](docs/sdk_developers/rebasing.md)
37+
// --- HELPERS ---
38+
39+
// 1. Fetch PR with retry logic for "unknown" state
40+
async function getPrWithRetry(prNumber) {
41+
for (let i = 0; i < 10; i++) {
42+
const { data: pr } = await github.rest.pulls.get({
43+
owner, repo, pull_number: prNumber
44+
});
45+
46+
if (pr.mergeable_state !== 'unknown') return pr;
47+
48+
console.log(`PR #${prNumber} state is 'unknown'. Retrying (${i+1}/10)...`);
49+
await new Promise(r => setTimeout(r, 2000)); // Sleep 2s
50+
}
51+
// If still unknown after 20s, return the last state
52+
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
53+
return pr;
54+
}
7355
74-
Thank you for contributing!
56+
// Post Comment
57+
async function notifyUser(prNumber) {
58+
const { data: comments } = await github.rest.issues.listComments({
59+
owner, repo, issue_number: prNumber,
60+
});
7561
76-
From the Hiero Python SDK Team
77-
EOF
78-
)
62+
// Check if we already commented
63+
if (comments.some(c => c.body.includes(BOT_SIGNATURE))) {
64+
console.log(`Already commented on PR #${prNumber}. Skipping.`);
65+
return;
66+
}
7967
80-
gh pr view $PR_NUMBER --repo $REPO --json comments --jq '.comments[].body' | grep -F "MergeConflictBot" >/dev/null || \
81-
(gh pr comment $PR_NUMBER --repo $REPO --body "$COMMENT" && echo "Comment added to PR #$PR_NUMBER")
68+
const body = `Hi, this is MergeConflictBot.\nYour pull request cannot be merged because it contains **merge conflicts**.\n\nPlease resolve these conflicts locally and push the changes.\n\nTo assist you, please read:\n- [Resolving Merge Conflicts](docs/sdk_developers/merge_conflicts.md)\n- [Rebasing Guide](docs/sdk_developers/rebasing.md)\n\nThank you for contributing!\nFrom the Hiero Python SDK Team\n\n${BOT_SIGNATURE}`;
8269
83-
# REMOVED 'exit 1' here so the loop continues!
84-
else
85-
echo "No merge conflicts detected for PR #$PR_NUMBER (State: $MERGEABLE_STATE)."
86-
fi
87-
done
70+
await github.rest.issues.createComment({
71+
owner, repo, issue_number: prNumber, body: body
72+
});
73+
}
74+
75+
// Set Commit Status (Status API)
76+
async function setCommitStatus(sha, state, description) {
77+
await github.rest.repos.createCommitStatus({
78+
owner, repo, sha: sha, state: state,
79+
context: 'Merge Conflict Detector',
80+
description: description,
81+
target_url: `${process.env.GITHUB_SERVER_URL}/${owner}/${repo}/actions/runs/${context.runId}`
82+
});
83+
}
84+
85+
// --- MAIN LOGIC ---
86+
87+
let prsToCheck = [];
88+
89+
// Scenario A: Push to Main (Check all open PRs)
90+
if (context.eventName === 'push') {
91+
console.log("Triggered by Push to Main. Fetching all open PRs...");
92+
const { data: openPrs } = await github.rest.pulls.list({
93+
owner, repo, state: 'open', base: 'main'
94+
});
95+
prsToCheck = openPrs.map(pr => pr.number);
96+
}
97+
// Scenario B: PR Update (Check single PR)
98+
else {
99+
console.log("Triggered by PR update.");
100+
prsToCheck.push(context.payload.pull_request.number);
101+
}
102+
103+
let hasFailure = false;
104+
105+
for (const prNumber of prsToCheck) {
106+
console.log(`Checking PR #${prNumber}...`);
107+
const pr = await getPrWithRetry(prNumber);
108+
109+
if (pr.mergeable_state === 'dirty') {
110+
console.log(`Conflict detected in PR #${prNumber}`);
111+
112+
// Notify without spam
113+
await notifyUser(prNumber);
114+
115+
// Handle Failure based on Event
116+
if (context.eventName === 'push') {
117+
//Don't fail workflow, set Status API on the commit instead
118+
await setCommitStatus(pr.head.sha, 'failure', 'Conflicts detected with main');
119+
} else {
120+
//Fail the workflow run so PR gets a Red X immediately
121+
core.setFailed(`Merge conflicts detected in PR #${prNumber}.`);
122+
hasFailure = true;
123+
}
124+
} else {
125+
console.log(`PR #${prNumber} is clean.`);
126+
// Clean up: If we are on main, ensure we turn the status green if it was red
127+
if (context.eventName === 'push') {
128+
await setCommitStatus(pr.head.sha, 'success', 'No conflicts detected');
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)