99 detect-conflicts :
1010 name : Check Open PRs for Conflicts
1111 runs-on : ubuntu-latest
12- permissions :
13- contents : read
14- pull-requests : read
15- checks : write
12+ permissions :
13+ contents : read
14+ pull-requests : read
15+ checks : write
1616
1717 steps :
1818 - name : Checkout repository
2626 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
2727 run : |
2828 set -euo pipefail
29-
29+
3030 gh pr list \
3131 --base "${{ github.ref_name }}" \
3232 --state open \
@@ -46,63 +46,174 @@ jobs:
4646 FAILED=false
4747
4848 while IFS= read -r PR; do
49+ echo "========================================"
4950 echo "Checking PR #$PR"
51+ echo "========================================"
5052
51- DATA=$(gh pr view "$PR" --json headRefName,headRefOid,baseRefName,title)
53+ # ── Fetch PR metadata ──────────────────────────────────────────
54+ DATA=$(gh pr view "$PR" --json headRefName,headRefOid,baseRefName,title,files)
5255
5356 HEAD_SHA=$(echo "$DATA" | jq -r '.headRefOid')
57+ HEAD_REF=$(echo "$DATA" | jq -r '.headRefName')
5458 BASE_REF=$(echo "$DATA" | jq -r '.baseRefName')
59+ TITLE=$(echo "$DATA" | jq -r '.title')
60+
61+ # Files actually touched by the PR (paths only)
62+ PR_FILES=$(echo "$DATA" | jq -r '.files[].path')
63+ TOTAL_FILES=$(echo "$PR_FILES" | grep -c . || true)
64+
65+ echo " Title : $TITLE"
66+ echo " Base ref : $BASE_REF"
67+ echo " Head ref : $HEAD_REF"
68+ echo " Head SHA : $HEAD_SHA"
69+ echo " PR files : $TOTAL_FILES"
5570
5671 git fetch origin "$BASE_REF"
5772 git fetch origin "$HEAD_SHA"
5873
5974 BASE_SHA=$(git rev-parse "origin/$BASE_REF")
75+ echo " Base SHA : $BASE_SHA"
6076
77+ # ── Run merge-tree ─────────────────────────────────────────────
6178 set +e
62- OUTPUT =$(git merge-tree "$BASE_SHA" "$HEAD_SHA" 2>&1)
63- CODE =$?
79+ MERGE_OUTPUT =$(git merge-tree "$BASE_SHA" "$HEAD_SHA" 2>&1)
80+ MERGE_CODE =$?
6481 set -e
6582
66- HAS_CONFLICT=false
83+ # ── Collect ALL conflicted paths from merge-tree output ────────
84+ # Lines like: "CONFLICT (content): Merge conflict in path/to/file"
85+ ALL_CONFLICTED_FILES=""
86+ if [ -n "$MERGE_OUTPUT" ]; then
87+ ALL_CONFLICTED_FILES=$(echo "$MERGE_OUTPUT" \
88+ | grep -oP '(?<=Merge conflict in ).*' || true)
89+ fi
6790
68- if echo "$OUTPUT" | grep -q "CONFLICT" || [ "$CODE" -eq 1 ]; then
69- HAS_CONFLICT=true
91+ # ── Cross-reference: keep only files the PR actually changed ──
92+ RELEVANT_CONFLICTS=""
93+ if [ -n "$ALL_CONFLICTED_FILES" ]; then
94+ while IFS= read -r CF; do
95+ if echo "$PR_FILES" | grep -qxF "$CF"; then
96+ RELEVANT_CONFLICTS="${RELEVANT_CONFLICTS}${CF}"$'\n'
97+ fi
98+ done <<< "$ALL_CONFLICTED_FILES"
7099 fi
71100
72- CHECK_NAME="Merge Conflict Detector"
101+ RELEVANT_CONFLICTS=$(echo "$RELEVANT_CONFLICTS" | sed '/^$/d')
102+ CONFLICT_COUNT=$(echo "$RELEVANT_CONFLICTS" | grep -c . || true)
73103
74- if [ "$HAS_CONFLICT" = true ]; then
75- echo "❌ Conflict in PR #$PR"
104+ HAS_RELEVANT_CONFLICT=false
105+ if [ "$CONFLICT_COUNT" -gt 0 ]; then
106+ HAS_RELEVANT_CONFLICT=true
107+ fi
108+
109+ CHECK_NAME="Merge Conflict Detector"
76110
77- gh api repos/${{ github.repository }}/check-runs \
111+ # ── Build structured log + summary ────────────────────────────
112+ if [ "$HAS_RELEVANT_CONFLICT" = true ]; then
113+ echo "❌ PR #$PR has $CONFLICT_COUNT relevant conflict(s)"
114+
115+ # --- structured console log ---
116+ echo ""
117+ echo " [CONFLICT DETAILS]"
118+ echo " PR number : #$PR"
119+ echo " PR title : $TITLE"
120+ echo " Base ref : $BASE_REF ($BASE_SHA)"
121+ echo " Head ref : $HEAD_REF ($HEAD_SHA)"
122+ echo " Total files changed : $TOTAL_FILES"
123+ echo " Conflicted files : $CONFLICT_COUNT"
124+ while IFS= read -r CF; do
125+ echo " - $CF"
126+ done <<< "$RELEVANT_CONFLICTS"
127+ echo ""
128+
129+ # --- check-run summary (Markdown, no heredoc to avoid YAML pipe conflicts) ---
130+ CONFLICT_FILE_LIST=$(echo "$RELEVANT_CONFLICTS" | sed 's/^/ /')
131+ SUMMARY=$(printf '%s\n' \
132+ "## Merge Conflicts Detected" \
133+ "" \
134+ "| Field | Value |" \
135+ "|---|---|" \
136+ "| PR | #${PR} - ${TITLE} |" \
137+ "| Base | ${BASE_REF} @ ${BASE_SHA} |" \
138+ "| Head | ${HEAD_REF} @ ${HEAD_SHA} |" \
139+ "| Total files changed | ${TOTAL_FILES} |" \
140+ "| Conflicted files | ${CONFLICT_COUNT} |" \
141+ "" \
142+ "### Conflicted files" \
143+ '```' \
144+ "${CONFLICT_FILE_LIST}" \
145+ '```' \
146+ "" \
147+ "### Remediation" \
148+ "1. Pull the latest ${BASE_REF} into your branch:" \
149+ ' ```bash' \
150+ " git fetch origin" \
151+ " git merge origin/${BASE_REF}" \
152+ " # or" \
153+ " git rebase origin/${BASE_REF}" \
154+ ' ```' \
155+ "2. Resolve the conflicts listed above, then commit and push." \
156+ "3. This check will re-run automatically once your branch is updated." \
157+ )
158+
159+ gh api "repos/${{ github.repository }}/check-runs" \
78160 -X POST \
79161 -f name="$CHECK_NAME" \
80162 -f head_sha="$HEAD_SHA" \
81163 -f status="completed" \
82164 -f conclusion="failure" \
83- -f "output[title]=Merge conflicts detected" \
84- -f "output[summary]=Please update your branch. " \
165+ -f "output[title]=Merge conflicts detected in ${CONFLICT_COUNT} file(s) " \
166+ -f "output[summary]=$SUMMARY " \
85167 --silent
86168
87169 FAILED=true
88- else
89- echo "✅ Clean PR #$PR"
90170
91- gh api repos/${{ github.repository }}/check-runs \
171+ else
172+ echo "✅ PR #$PR — no relevant conflicts (merge-tree exit=$MERGE_CODE)"
173+
174+ # --- structured console log ---
175+ echo ""
176+ echo " [CLEAN DETAILS]"
177+ echo " PR number : #$PR"
178+ echo " PR title : $TITLE"
179+ echo " Base ref : $BASE_REF ($BASE_SHA)"
180+ echo " Head ref : $HEAD_REF ($HEAD_SHA)"
181+ echo " Total files changed : $TOTAL_FILES"
182+ echo " Conflicted files : 0"
183+ echo ""
184+
185+ # --- check-run summary (Markdown, no heredoc to avoid YAML pipe conflicts) ---
186+ SUMMARY=$(printf '%s\n' \
187+ "## No Merge Conflicts" \
188+ "" \
189+ "| Field | Value |" \
190+ "|---|---|" \
191+ "| PR | #${PR} - ${TITLE} |" \
192+ "| Base | ${BASE_REF} @ ${BASE_SHA} |" \
193+ "| Head | ${HEAD_REF} @ ${HEAD_SHA} |" \
194+ "| Total files changed | ${TOTAL_FILES} |" \
195+ "| Conflicted files | 0 |" \
196+ "" \
197+ "This PR merges cleanly into ${BASE_REF}." \
198+ )
199+
200+ gh api "repos/${{ github.repository }}/check-runs" \
92201 -X POST \
93202 -f name="$CHECK_NAME" \
94203 -f head_sha="$HEAD_SHA" \
95204 -f status="completed" \
96205 -f conclusion="success" \
97- -f "output[title]=No merge conflicts" \
98- -f "output[summary]=PR merges cleanly. " \
206+ -f "output[title]=No merge conflicts - ${TOTAL_FILES} file(s) checked " \
207+ -f "output[summary]=$SUMMARY " \
99208 --silent
100209 fi
101210
102211 done < pr_numbers.txt
103212
213+ echo "========================================"
104214 if [ "$FAILED" = true ]; then
105- echo "Some PRs have conflicts"
215+ echo "Result: one or more PRs have relevant conflicts"
216+ exit 0 # job stays green; failure signal lives on individual check-runs
106217 else
107- echo "All PRs clean"
218+ echo "Result: all PRs are clean"
108219 fi
0 commit comments