-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat/added pr conflict detection gha #6369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 4 commits
59354de
b38d80a
321c215
258998a
886fa55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,9 +2,304 @@ name: PR Target Workflow | |||||||
|
|
||||||||
| on: | ||||||||
| pull_request_target: | ||||||||
| types: [opened, reopened, synchronize, ready_for_review] | ||||||||
| push: | ||||||||
| branches: [develop] | ||||||||
|
|
||||||||
| permissions: | ||||||||
| contents: read | ||||||||
| pull-requests: write | ||||||||
| checks: write | ||||||||
|
|
||||||||
| jobs: | ||||||||
| PR-Greeting: | ||||||||
| uses: PalisadoesFoundation/.github/.github/workflows/pr-target-policy.yml@main | ||||||||
| secrets: | ||||||||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||
| detect-merge-conflicts: | ||||||||
| name: Detect Merge Conflicts in PRs | ||||||||
| runs-on: ubuntu-latest | ||||||||
|
|
||||||||
| steps: | ||||||||
| - name: Checkout repository | ||||||||
| uses: actions/checkout@v4 | ||||||||
| with: | ||||||||
| fetch-depth: 0 | ||||||||
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | ||||||||
|
|
||||||||
| - name: Log environment | ||||||||
| run: | | ||||||||
| echo "[SETUP] ========================================" | ||||||||
| echo "[SETUP] Workflow triggered by: ${{ github.event_name }}" | ||||||||
| echo "[SETUP] Repository: ${{ github.repository }}" | ||||||||
| echo "[SETUP] Branch: ${{ github.ref }}" | ||||||||
| echo "[SETUP] SHA: ${{ github.sha }}" | ||||||||
|
|
||||||||
| if [ "${{ github.event_name }}" = "pull_request_target" ]; then | ||||||||
| echo "[SETUP] PR Number: ${{ github.event.pull_request.number }}" | ||||||||
| echo "[SETUP] PR Title: ${{ github.event.pull_request.title }}" | ||||||||
| echo "[SETUP] Base Branch: ${{ github.event.pull_request.base.ref }}" | ||||||||
| echo "[SETUP] Base SHA: ${{ github.event.pull_request.base.sha }}" | ||||||||
| echo "[SETUP] Head Branch: ${{ github.event.pull_request.head.ref }}" | ||||||||
| echo "[SETUP] Head SHA: ${{ github.event.pull_request.head.sha }}" | ||||||||
| fi | ||||||||
|
|
||||||||
| echo "[SETUP] Current Git Ref: $(git rev-parse HEAD)" | ||||||||
| echo "[SETUP] Git Version: $(git --version)" | ||||||||
| echo "[SETUP] ========================================" | ||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
|
||||||||
| - name: Get open PRs for base branch | ||||||||
| if: github.event_name == 'push' | ||||||||
| id: get-prs | ||||||||
| env: | ||||||||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||
| run: | | ||||||||
| echo "[FETCH] ========================================" | ||||||||
| echo "[FETCH] Fetching open PRs targeting: ${{ github.ref_name }}" | ||||||||
|
|
||||||||
| # Get all open PRs targeting this branch | ||||||||
| gh pr list \ | ||||||||
| --base "${{ github.ref_name }}" \ | ||||||||
| --state open \ | ||||||||
| --json number \ | ||||||||
| --jq '.[].number' > pr_numbers.txt | ||||||||
|
|
||||||||
| PR_COUNT=$(wc -l < pr_numbers.txt | tr -d ' ') | ||||||||
| echo "[FETCH] Found ${PR_COUNT} open PR(s) targeting ${{ github.ref_name }}" | ||||||||
|
|
||||||||
| if [ "${PR_COUNT}" -eq 0 ]; then | ||||||||
| echo "[FETCH] No open PRs to check" | ||||||||
| echo "has_prs=false" >> $GITHUB_OUTPUT | ||||||||
| else | ||||||||
| echo "has_prs=true" >> $GITHUB_OUTPUT | ||||||||
| echo "[FETCH] PRs to check:" | ||||||||
| cat pr_numbers.txt | while read pr; do | ||||||||
| echo "[FETCH] - PR #${pr}" | ||||||||
| done | ||||||||
| fi | ||||||||
| echo "[FETCH] ========================================" | ||||||||
|
|
||||||||
| - name: Check single PR for conflicts | ||||||||
| if: github.event_name == 'pull_request_target' | ||||||||
| env: | ||||||||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||
| run: | | ||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
| echo "[CHECK] ========================================" | ||||||||
| echo "[CHECK] Checking PR #${{ github.event.pull_request.number }}" | ||||||||
|
|
||||||||
| # Fetch base branch | ||||||||
| echo "[CHECK] Fetching base branch: ${{ github.event.pull_request.base.ref }}" | ||||||||
| git fetch origin ${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }} | ||||||||
|
|
||||||||
| # Get PR changed files | ||||||||
| echo "[CHECK] Fetching changed files in PR" | ||||||||
| gh pr view ${{ github.event.pull_request.number }} \ | ||||||||
| --json files \ | ||||||||
| --jq '.files[].path' > pr_files.txt | ||||||||
|
|
||||||||
| FILE_COUNT=$(wc -l < pr_files.txt | tr -d ' ') | ||||||||
| echo "[CHECK] Files changed: ${FILE_COUNT}" | ||||||||
|
|
||||||||
| if [ "${FILE_COUNT}" -eq 0 ]; then | ||||||||
| echo "[CHECK] No files changed - exiting with success" | ||||||||
| echo "[CHECK] ========================================" | ||||||||
| exit 0 | ||||||||
| fi | ||||||||
|
|
||||||||
| echo "[CHECK] Changed files:" | ||||||||
| cat pr_files.txt | while read file; do | ||||||||
| echo "[CHECK] - ${file}" | ||||||||
| done | ||||||||
|
|
||||||||
| # Calculate merge-base | ||||||||
| echo "[CHECK] Calculating merge-base" | ||||||||
| MERGE_BASE=$(git merge-base ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}) | ||||||||
| echo "[CHECK] Merge-base: ${MERGE_BASE}" | ||||||||
|
|
||||||||
| # Run git merge-tree to detect conflicts | ||||||||
| echo "[CHECK] Running git merge-tree to detect conflicts" | ||||||||
| set +e | ||||||||
| MERGE_OUTPUT=$(git merge-tree ${MERGE_BASE} ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} 2>&1) | ||||||||
| MERGE_EXIT_CODE=$? | ||||||||
| set -e | ||||||||
|
|
||||||||
| echo "[CHECK] Merge-tree exit code: ${MERGE_EXIT_CODE}" | ||||||||
|
|
||||||||
| # Parse conflicts from merge-tree output | ||||||||
| touch all_conflicts.txt | ||||||||
|
|
||||||||
| # Look for CONFLICT markers | ||||||||
| echo "${MERGE_OUTPUT}" | grep "^CONFLICT" | \ | ||||||||
| sed -E 's/^CONFLICT \([^)]+\): Merge conflict in (.+)$/\1/' | \ | ||||||||
| grep -v "^CONFLICT" >> all_conflicts.txt || true | ||||||||
|
|
||||||||
| # Also check for "changed in both" indicators | ||||||||
| echo "${MERGE_OUTPUT}" | grep -E "^\+<<<<<|^Auto-merging" | \ | ||||||||
| grep -oP "(?<=Auto-merging )\S+" >> all_conflicts.txt || true | ||||||||
|
Comment on lines
+137
to
+139
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. False positive detection from
Remove this logic to avoid false positives: 🐛 Proposed fix # Look for CONFLICT markers
echo "${MERGE_OUTPUT}" | grep "^CONFLICT" | \
sed -E 's/^CONFLICT \([^)]+\): Merge conflict in (.+)$/\1/' | \
grep -v "^CONFLICT" >> all_conflicts.txt || true
-
- # Also check for "changed in both" indicators
- echo "${MERGE_OUTPUT}" | grep -E "^\+<<<<<|^Auto-merging" | \
- grep -oP "(?<=Auto-merging )\S+" >> all_conflicts.txt || true📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||
|
|
||||||||
| # Remove duplicates and empty lines | ||||||||
| sort -u all_conflicts.txt | grep -v "^$" > all_conflicts_unique.txt || touch all_conflicts_unique.txt | ||||||||
| mv all_conflicts_unique.txt all_conflicts.txt | ||||||||
|
Comment on lines
+129
to
+143
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Short answer — format and marker types
Sources 🌐 Web query:
💡 Result: Summary — exit codes Output format (default) |
||||||||
|
|
||||||||
| TOTAL_CONFLICTS=$(wc -l < all_conflicts.txt | tr -d ' ') | ||||||||
| echo "[CHECK] Total files with potential conflicts: ${TOTAL_CONFLICTS}" | ||||||||
|
|
||||||||
| if [ "${TOTAL_CONFLICTS}" -gt 0 ]; then | ||||||||
| echo "[CHECK] Conflicted files detected:" | ||||||||
| cat all_conflicts.txt | while read file; do | ||||||||
| echo "[CHECK] - ${file}" | ||||||||
| done | ||||||||
| fi | ||||||||
|
|
||||||||
| # Filter to only PR changed files | ||||||||
| touch pr_conflicts.txt | ||||||||
| if [ -f all_conflicts.txt ] && [ -s all_conflicts.txt ]; then | ||||||||
| while IFS= read -r conflict_file; do | ||||||||
| if grep -Fxq "${conflict_file}" pr_files.txt; then | ||||||||
| echo "${conflict_file}" >> pr_conflicts.txt | ||||||||
| fi | ||||||||
| done < all_conflicts.txt | ||||||||
| fi | ||||||||
|
|
||||||||
| FILTERED_COUNT=0 | ||||||||
| if [ -f pr_conflicts.txt ] && [ -s pr_conflicts.txt ]; then | ||||||||
| FILTERED_COUNT=$(wc -l < pr_conflicts.txt | tr -d ' ') | ||||||||
| fi | ||||||||
|
|
||||||||
| echo "[CHECK] Files with conflicts in this PR: ${FILTERED_COUNT}" | ||||||||
|
|
||||||||
| # Generate result | ||||||||
| echo "[RESULT] ========================================" | ||||||||
| echo "[RESULT] CONFLICT DETECTION SUMMARY" | ||||||||
| echo "[RESULT] ========================================" | ||||||||
| echo "[RESULT] PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}" | ||||||||
| echo "[RESULT] Base: ${{ github.event.pull_request.base.ref }} @ ${{ github.event.pull_request.base.sha }}" | ||||||||
| echo "[RESULT] Head: ${{ github.event.pull_request.head.ref }} @ ${{ github.event.pull_request.head.sha }}" | ||||||||
| echo "[RESULT] ----------------------------------------" | ||||||||
| echo "[RESULT] Total files changed in PR: ${FILE_COUNT}" | ||||||||
| echo "[RESULT] Files with conflicts in PR: ${FILTERED_COUNT}" | ||||||||
| echo "[RESULT] ========================================" | ||||||||
|
|
||||||||
| if [ "${FILTERED_COUNT}" -eq 0 ]; then | ||||||||
| echo "[RESULT] ✅ STATUS: PASSED" | ||||||||
| echo "[RESULT] No merge conflicts detected" | ||||||||
| echo "[RESULT] ========================================" | ||||||||
| exit 0 | ||||||||
| else | ||||||||
| echo "[RESULT] ❌ STATUS: FAILED" | ||||||||
| echo "[RESULT] Merge conflicts detected in ${FILTERED_COUNT} file(s):" | ||||||||
| cat pr_conflicts.txt | while read file; do | ||||||||
| echo "[RESULT] ⚠️ ${file}" | ||||||||
| done | ||||||||
| echo "[RESULT]" | ||||||||
| echo "[RESULT] ACTION REQUIRED:" | ||||||||
| echo "[RESULT] Please update your branch to resolve these conflicts:" | ||||||||
| echo "[RESULT] git fetch origin" | ||||||||
| echo "[RESULT] git checkout ${{ github.event.pull_request.head.ref }}" | ||||||||
| echo "[RESULT] git merge origin/${{ github.event.pull_request.base.ref }}" | ||||||||
|
Comment on lines
+199
to
+202
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Script injection risk with While this is inside an Pass it through an environment variable: 🔒 Proposed fixUpdate the step's - name: Check single PR for conflicts
if: github.event_name == 'pull_request_target'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
+ PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |Then update the references in the script: - echo "[RESULT] git checkout ${{ github.event.pull_request.head.ref }}"
- echo "[RESULT] git merge origin/${{ github.event.pull_request.base.ref }}"
+ echo "[RESULT] git checkout ${PR_HEAD_REF}"
+ echo "[RESULT] git merge origin/${PR_BASE_REF}"🤖 Prompt for AI Agents |
||||||||
| echo "[RESULT] ========================================" | ||||||||
| exit 1 | ||||||||
| fi | ||||||||
|
|
||||||||
| - name: Check all open PRs for conflicts | ||||||||
| if: github.event_name == 'push' && steps.get-prs.outputs.has_prs == 'true' | ||||||||
| env: | ||||||||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||
| run: | | ||||||||
| echo "[CHECK-ALL] ========================================" | ||||||||
| echo "[CHECK-ALL] Checking all open PRs for conflicts after merge to ${{ github.ref_name }}" | ||||||||
|
|
||||||||
| FAILED_PRS="" | ||||||||
| FAILED_COUNT=0 | ||||||||
|
|
||||||||
| while IFS= read -r pr_number; do | ||||||||
| echo "[CHECK-ALL] ----------------------------------------" | ||||||||
| echo "[CHECK-ALL] Checking PR #${pr_number}" | ||||||||
|
|
||||||||
| # Get PR details | ||||||||
| PR_DATA=$(gh pr view ${pr_number} --json headRefName,headRefOid,baseRefName,baseRefOid,title) | ||||||||
| PR_TITLE=$(echo "${PR_DATA}" | jq -r '.title') | ||||||||
| PR_HEAD_REF=$(echo "${PR_DATA}" | jq -r '.headRefName') | ||||||||
| PR_HEAD_SHA=$(echo "${PR_DATA}" | jq -r '.headRefOid') | ||||||||
| PR_BASE_REF=$(echo "${PR_DATA}" | jq -r '.baseRefName') | ||||||||
| PR_BASE_SHA=$(echo "${PR_DATA}" | jq -r '.baseRefOid') | ||||||||
|
|
||||||||
| echo "[CHECK-ALL] PR #${pr_number}: ${PR_TITLE}" | ||||||||
| echo "[CHECK-ALL] Base: ${PR_BASE_REF} @ ${PR_BASE_SHA}" | ||||||||
| echo "[CHECK-ALL] Head: ${PR_HEAD_REF} @ ${PR_HEAD_SHA}" | ||||||||
|
|
||||||||
| # Fetch branches | ||||||||
| git fetch origin ${PR_BASE_REF}:refs/remotes/origin/${PR_BASE_REF} 2>/dev/null || true | ||||||||
| git fetch origin ${PR_HEAD_REF}:refs/remotes/origin/${PR_HEAD_REF} 2>/dev/null || true | ||||||||
|
|
||||||||
| # Get PR files | ||||||||
| gh pr view ${pr_number} --json files --jq '.files[].path' > "pr_${pr_number}_files.txt" | ||||||||
| FILE_COUNT=$(wc -l < "pr_${pr_number}_files.txt" | tr -d ' ') | ||||||||
|
|
||||||||
| if [ "${FILE_COUNT}" -eq 0 ]; then | ||||||||
| echo "[CHECK-ALL] No files changed - skipping" | ||||||||
| continue | ||||||||
| fi | ||||||||
|
|
||||||||
| # Calculate merge-base | ||||||||
| MERGE_BASE=$(git merge-base origin/${PR_BASE_REF} origin/${PR_HEAD_REF} 2>/dev/null || echo "") | ||||||||
|
|
||||||||
| if [ -z "${MERGE_BASE}" ]; then | ||||||||
| echo "[CHECK-ALL] ⚠️ Could not calculate merge-base - skipping" | ||||||||
| continue | ||||||||
| fi | ||||||||
|
|
||||||||
| # Run merge-tree | ||||||||
| set +e | ||||||||
| MERGE_OUTPUT=$(git merge-tree ${MERGE_BASE} origin/${PR_BASE_REF} origin/${PR_HEAD_REF} 2>&1) | ||||||||
| set -e | ||||||||
|
|
||||||||
| # Parse conflicts | ||||||||
| touch "pr_${pr_number}_conflicts.txt" | ||||||||
| echo "${MERGE_OUTPUT}" | grep "^CONFLICT" | \ | ||||||||
| sed -E 's/^CONFLICT \([^)]+\): Merge conflict in (.+)$/\1/' | \ | ||||||||
| grep -v "^CONFLICT" >> "pr_${pr_number}_conflicts.txt" || true | ||||||||
|
|
||||||||
| sort -u "pr_${pr_number}_conflicts.txt" | grep -v "^$" > "pr_${pr_number}_conflicts_unique.txt" || touch "pr_${pr_number}_conflicts_unique.txt" | ||||||||
| mv "pr_${pr_number}_conflicts_unique.txt" "pr_${pr_number}_conflicts.txt" | ||||||||
|
Comment on lines
260
to
273
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Conflict parsing is correct here but inconsistent with single PR check. This step correctly parses only 🤖 Prompt for AI Agents |
||||||||
|
|
||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
| # Filter to PR files | ||||||||
| touch "pr_${pr_number}_filtered.txt" | ||||||||
| if [ -f "pr_${pr_number}_conflicts.txt" ] && [ -s "pr_${pr_number}_conflicts.txt" ]; then | ||||||||
| while IFS= read -r conflict_file; do | ||||||||
| if grep -Fxq "${conflict_file}" "pr_${pr_number}_files.txt"; then | ||||||||
| echo "${conflict_file}" >> "pr_${pr_number}_filtered.txt" | ||||||||
| fi | ||||||||
| done < "pr_${pr_number}_conflicts.txt" | ||||||||
| fi | ||||||||
|
|
||||||||
| CONFLICT_COUNT=0 | ||||||||
| if [ -f "pr_${pr_number}_filtered.txt" ] && [ -s "pr_${pr_number}_filtered.txt" ]; then | ||||||||
| CONFLICT_COUNT=$(wc -l < "pr_${pr_number}_filtered.txt" | tr -d ' ') | ||||||||
| fi | ||||||||
|
|
||||||||
| if [ "${CONFLICT_COUNT}" -gt 0 ]; then | ||||||||
| echo "[CHECK-ALL] ❌ PR #${pr_number} has ${CONFLICT_COUNT} conflicted file(s)" | ||||||||
| cat "pr_${pr_number}_filtered.txt" | while read file; do | ||||||||
| echo "[CHECK-ALL] ⚠️ ${file}" | ||||||||
| done | ||||||||
| FAILED_PRS="${FAILED_PRS}${pr_number}," | ||||||||
| FAILED_COUNT=$((FAILED_COUNT + 1)) | ||||||||
|
|
||||||||
| # Post comment on PR2 | ||||||||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||
| gh pr comment ${pr_number} --body "⚠️ **Merge Conflict Detected** | ||||||||
|
|
||||||||
| This PR has merge conflicts with the base branch \`${PR_BASE_REF}\` after recent changes were merged. | ||||||||
|
|
||||||||
| **Conflicted files:** | ||||||||
| $(cat "pr_${pr_number}_filtered.txt" | sed 's/^/- /') | ||||||||
|
|
||||||||
| **Action required:** Please update your branch to resolve these conflicts: | ||||||||
| \`\`\`bash | ||||||||
| git fetch origin | ||||||||
| git checkout ${PR_HEAD_REF} | ||||||||
| git merge origin/${PR_BASE_REF} | ||||||||
| # Resolve conflicts, then: | ||||||||
| git push | ||||||||
| \`\`\`" | ||||||||
| fi | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider adding concurrency control to prevent duplicate comments.
Multiple rapid pushes to
developcould trigger parallel workflow runs, each posting comments on the same conflicted PRs. Adding a concurrency group would cancel in-progress runs when a new push occurs.♻️ Proposed enhancement
detect-merge-conflicts: name: Detect Merge Conflicts in PRs runs-on: ubuntu-latest + concurrency: + group: conflict-check-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true steps:📝 Committable suggestion
🤖 Prompt for AI Agents