Skip to content

Commit 5dc6980

Browse files
added-merge-conflict-detector-worflow (#39)
* added-merge-conflict-detector-worflow * separated-concerns * separated-concerns * separated-concerns * added main to branches
1 parent e98fd0e commit 5dc6980

1 file changed

Lines changed: 237 additions & 0 deletions

File tree

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
name: Merge Conflict Detector
2+
3+
on:
4+
workflow_call:
5+
6+
pull_request_target:
7+
types: [opened, reopened, synchronize, ready_for_review]
8+
push:
9+
branches: [main, develop]
10+
11+
12+
jobs:
13+
14+
detect-merge-conflicts:
15+
name: Check Open PRs for Conflicts
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- name: Checkout repository
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
24+
- name: Get open PRs targeting this branch
25+
id: get-prs
26+
env:
27+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28+
run: |
29+
echo "[FETCH] ========================================"
30+
echo "[FETCH] Fetching open PRs targeting: ${{ github.ref_name }}"
31+
32+
gh pr list \
33+
--base "${{ github.ref_name }}" \
34+
--state open \
35+
--json number \
36+
--jq '.[].number' > pr_numbers.txt
37+
38+
PR_COUNT=$(wc -l < pr_numbers.txt | tr -d ' ')
39+
echo "[FETCH] Found ${PR_COUNT} open PR(s)"
40+
41+
if [ "${PR_COUNT}" -eq 0 ]; then
42+
echo "[FETCH] No open PRs to check"
43+
echo "has_prs=false" >> $GITHUB_OUTPUT
44+
else
45+
echo "has_prs=true" >> $GITHUB_OUTPUT
46+
echo "[FETCH] PRs to check:"
47+
cat pr_numbers.txt | while read pr; do
48+
echo "[FETCH] - PR #${pr}"
49+
done
50+
fi
51+
echo "[FETCH] ========================================"
52+
53+
- name: Check single PR for merge conflicts
54+
if: github.event_name == 'pull_request_target'
55+
env:
56+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57+
run: |
58+
set -euo pipefail
59+
60+
echo "[CHECK] ========================================"
61+
echo "[CHECK] Checking PR #${{ github.event.pull_request.number }}"
62+
63+
BASE_REF="${{ github.event.pull_request.base.ref }}"
64+
PR_NUMBER="${{ github.event.pull_request.number }}"
65+
66+
echo "[CHECK] Fetching latest base branch: ${BASE_REF}"
67+
git fetch origin "${BASE_REF}"
68+
69+
BASE_SHA=$(git rev-parse "origin/${BASE_REF}")
70+
echo "[CHECK] Latest base SHA: ${BASE_SHA}"
71+
72+
PR_DATA=$(gh pr view "$PR_NUMBER" --json headRefName,headRefOid,title)
73+
74+
PR_HEAD_REF=$(echo "$PR_DATA" | jq -r '.headRefName')
75+
PR_HEAD_SHA=$(echo "$PR_DATA" | jq -r '.headRefOid')
76+
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
77+
78+
echo "[CHECK] Latest PR head: ${PR_HEAD_REF} @ ${PR_HEAD_SHA}"
79+
80+
# Fetch PR commit directly (works for forks)
81+
git fetch origin "${PR_HEAD_SHA}"
82+
83+
echo "[CHECK] Fetching changed files in PR"
84+
gh pr view "$PR_NUMBER" --json files --jq '.files[].path' > pr_files.txt
85+
86+
FILE_COUNT=$(wc -l < pr_files.txt | tr -d ' ')
87+
echo "[CHECK] Total files changed: ${FILE_COUNT}"
88+
89+
if [ "$FILE_COUNT" -eq 0 ]; then
90+
echo "[CHECK] No files changed — exiting successfully"
91+
exit 0
92+
fi
93+
94+
echo "[CHECK] Changed files:"
95+
sed 's/^/[CHECK] - /' pr_files.txt
96+
97+
echo "[CHECK] Running merge-tree conflict analysis"
98+
99+
set +e
100+
MERGE_OUTPUT=$(git merge-tree "$BASE_SHA" "$PR_HEAD_SHA" 2>&1)
101+
MERGE_EXIT_CODE=$?
102+
set -e
103+
104+
echo "[RESULT] ========================================"
105+
echo "[RESULT] CONFLICT DETECTION SUMMARY"
106+
echo "[RESULT] ========================================"
107+
printf "[RESULT] PR #%s: %s\n" "$PR_NUMBER" "$PR_TITLE"
108+
echo "[RESULT] Base: ${BASE_REF} @ ${BASE_SHA}"
109+
echo "[RESULT] Head: ${PR_HEAD_REF} @ ${PR_HEAD_SHA}"
110+
echo "[RESULT] ----------------------------------------"
111+
echo "[RESULT] Total files changed: ${FILE_COUNT}"
112+
echo "[RESULT] ========================================"
113+
114+
# Check for conflicts in merge-tree output
115+
if echo "$MERGE_OUTPUT" | grep -q "CONFLICT"; then
116+
echo "[RESULT] ❌ STATUS: FAILED"
117+
echo "[RESULT] Merge conflicts detected with latest base branch"
118+
echo "$MERGE_OUTPUT" | grep "CONFLICT" | sed 's/^/[RESULT] /'
119+
exit 1
120+
elif [ "$MERGE_EXIT_CODE" -ne 0 ]; then
121+
echo "[RESULT] ⚠️ WARNING: Merge-tree exited with code ${MERGE_EXIT_CODE}"
122+
echo "[RESULT] This may indicate a conflict or error"
123+
exit 1
124+
else
125+
echo "[RESULT] ✅ STATUS: PASSED"
126+
echo "[RESULT] No merge conflicts detected"
127+
exit 0
128+
fi
129+
130+
- name: Check all open PRs for conflicts
131+
if: steps.get-prs.outputs.has_prs == 'true' && github.event_name == 'push'
132+
env:
133+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
134+
run: |
135+
set -euo pipefail
136+
echo "[CHECK-ALL] ========================================"
137+
echo "[CHECK-ALL] Checking all open PRs after merge to ${{ github.ref_name }}"
138+
139+
FAILED=false
140+
CONFLICT_SUMMARY=""
141+
142+
while IFS= read -r pr_number; do
143+
echo "[CHECK-ALL] ----------------------------------------"
144+
echo "[CHECK-ALL] Checking PR #${pr_number}"
145+
146+
PR_DATA=$(gh pr view ${pr_number} --json headRefName,headRefOid,baseRefName,title)
147+
PR_HEAD_REF=$(echo "$PR_DATA" | jq -r '.headRefName')
148+
PR_HEAD_SHA=$(echo "$PR_DATA" | jq -r '.headRefOid')
149+
PR_BASE_REF=$(echo "$PR_DATA" | jq -r '.baseRefName')
150+
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
151+
152+
echo "[CHECK-ALL] PR #${pr_number}: ${PR_TITLE}"
153+
echo "[CHECK-ALL] Base: ${PR_BASE_REF}"
154+
echo "[CHECK-ALL] Head: ${PR_HEAD_REF} @ ${PR_HEAD_SHA}"
155+
156+
echo "[CHECK-ALL] Fetching latest branches..."
157+
git fetch origin ${PR_BASE_REF} >/dev/null 2>&1
158+
159+
# Fetch the PR head commit directly (works for forks)
160+
git fetch origin ${PR_HEAD_SHA} >/dev/null 2>&1
161+
162+
# Get latest base SHA
163+
LATEST_BASE_SHA=$(git rev-parse origin/${PR_BASE_REF})
164+
echo "[CHECK-ALL] Latest base SHA: ${LATEST_BASE_SHA}"
165+
echo "[CHECK-ALL] PR head SHA: ${PR_HEAD_SHA}"
166+
167+
# Use git merge-tree to check for conflicts
168+
echo "[CHECK-ALL] Running merge simulation..."
169+
set +e
170+
MERGE_OUTPUT=$(git merge-tree ${LATEST_BASE_SHA} ${PR_HEAD_SHA} 2>&1)
171+
MERGE_EXIT_CODE=$?
172+
set -e
173+
174+
CHECK_NAME="Merge Conflict Detector"
175+
HAS_CONFLICT=false
176+
177+
# Check for "CONFLICT" in the output (most reliable indicator)
178+
if echo "$MERGE_OUTPUT" | grep -q "CONFLICT"; then
179+
HAS_CONFLICT=true
180+
echo "[CHECK-ALL] ❌ Conflict detected via merge-tree output"
181+
echo "$MERGE_OUTPUT" | grep "CONFLICT" | sed 's/^/[CHECK-ALL] /'
182+
# Exit code 1 from merge-tree also indicates conflicts
183+
elif [ "$MERGE_EXIT_CODE" -eq 1 ]; then
184+
HAS_CONFLICT=true
185+
echo "[CHECK-ALL] ❌ Conflict detected via merge-tree exit code"
186+
else
187+
echo "[CHECK-ALL] ✅ No conflicts detected"
188+
fi
189+
190+
# Post check run status (suppress JSON output)
191+
if [ "$HAS_CONFLICT" = true ]; then
192+
echo "[CHECK-ALL] ❌ Posting failure status for PR #${pr_number}"
193+
194+
gh api repos/${{ github.repository }}/check-runs \
195+
-X POST \
196+
-f name="${CHECK_NAME}" \
197+
-f head_sha="${PR_HEAD_SHA}" \
198+
-f status="completed" \
199+
-f conclusion="failure" \
200+
-f "output[title]=Merge conflicts detected" \
201+
-f "output[summary]=This PR has merge conflicts with ${PR_BASE_REF}. Please update your branch." \
202+
--silent
203+
204+
FAILED=true
205+
CONFLICT_SUMMARY="${CONFLICT_SUMMARY}
206+
PR #${pr_number}: ${PR_TITLE}"
207+
else
208+
echo "[CHECK-ALL] ✅ Posting success status for PR #${pr_number}"
209+
210+
gh api repos/${{ github.repository }}/check-runs \
211+
-X POST \
212+
-f name="${CHECK_NAME}" \
213+
-f head_sha="${PR_HEAD_SHA}" \
214+
-f status="completed" \
215+
-f conclusion="success" \
216+
-f "output[title]=No merge conflicts" \
217+
-f "output[summary]=This PR cleanly merges into ${PR_BASE_REF}" \
218+
--silent
219+
fi
220+
221+
done < pr_numbers.txt
222+
223+
echo "[CHECK-ALL] ========================================"
224+
if [ "$FAILED" = true ]; then
225+
echo "[CHECK-ALL] ⚠️ CONFLICTS DETECTED"
226+
echo "[CHECK-ALL] The following PRs have merge conflicts:"
227+
echo "${CONFLICT_SUMMARY}"
228+
echo "[CHECK-ALL] ========================================"
229+
echo "[CHECK-ALL] Check runs have been posted to each PR"
230+
echo "[CHECK-ALL] ========================================"
231+
exit 0
232+
else
233+
echo "[CHECK-ALL] ✅ ALL PRs CLEAN"
234+
echo "[CHECK-ALL] All open PRs merge cleanly into ${{ github.ref_name }}"
235+
echo "[CHECK-ALL] ========================================"
236+
exit 0
237+
fi

0 commit comments

Comments
 (0)