Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .github/actions/prconflicts/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Detect conflicting PRs
description: Detects PRs that would conflict with the current PR and posts a comment

inputs:
github-token:
description: GitHub token with pull-requests write and contents read permissions
required: true
repository:
description: Repository in owner/repo format
required: true
pr-number:
description: Current PR number
required: true
head-ref:
description: Head branch of the current PR
required: true
base-ref:
description: Base branch of the current PR
required: true

runs:
using: composite
steps:
- name: Fetch base and current branch
shell: bash
run: |
git fetch origin ${{ inputs.base-ref }} ${{ inputs.head-ref }}
Comment thread
mchain0 marked this conversation as resolved.
Outdated

- name: Detect conflicts and update comment
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
REPO: ${{ inputs.repository }}
CURRENT_PR: ${{ inputs.pr-number }}
CURRENT_BRANCH: ${{ inputs.head-ref }}
BASE_BRANCH: ${{ inputs.base-ref }}
run: |
set -e

MARKER="<!-- conflict-check -->"

# Get files changed in current PR
current_files=$(gh api --method GET --paginate "repos/${REPO}/pulls/${CURRENT_PR}/files" --jq '.[].filename' | sort -u)
current_files_count=$(echo "$current_files" | wc -l)
Comment thread
mchain0 marked this conversation as resolved.
Outdated
echo "Current PR touches $current_files_count files"

# Get all open PRs targeting same base (paginated)
prs=$(gh api --method GET --paginate "repos/${REPO}/pulls?state=open&base=${BASE_BRANCH}" --jq '.[].number')
Comment thread
mchain0 marked this conversation as resolved.
Outdated
total_prs=$(echo "$prs" | wc -w)
Comment thread
mchain0 marked this conversation as resolved.
Outdated
echo "Found $total_prs open PRs targeting ${BASE_BRANCH}"

# Find PRs with overlapping files (fast API check)
candidates=()
for pr in $prs; do
[ "$pr" = "$CURRENT_PR" ] && continue

other_files=$(gh api --method GET --paginate "repos/${REPO}/pulls/${pr}/files" --jq '.[].filename' | sort -u)

# Check for file intersection
if comm -12 <(echo "$current_files") <(echo "$other_files") | grep -q .; then
candidates+=("$pr")
fi
done

echo "Found ${#candidates[@]} PRs with overlapping files, checking for conflicts..."

if [ ${#candidates[@]} -eq 0 ]; then
conflicts=()
else
# Fetch only the PR refs we need
fetch_refs=""
for pr in "${candidates[@]}"; do
fetch_refs="$fetch_refs +refs/pull/${pr}/head:refs/remotes/origin/pr/${pr}"
done
git fetch origin $fetch_refs

mkdir -p worktrees
conflicts=()

for pr in "${candidates[@]}"; do
other_branch="origin/pr/$pr"
dir="worktrees/$pr"

git worktree add -q --detach "$dir" "origin/${BASE_BRANCH}"

if git -C "$dir" merge --no-commit --no-ff "$other_branch" >/dev/null 2>&1; then
git -C "$dir" -c user.email="ci@local" -c user.name="CI" commit --no-edit -m "temp" >/dev/null 2>&1

if ! git -C "$dir" merge --no-commit --no-ff "origin/${CURRENT_BRANCH}" >/dev/null 2>&1; then
conflicts+=("#$pr")
fi
fi

git worktree remove -f "$dir"
done
fi

echo "Found ${#conflicts[@]} conflicting PRs"

if [ ${#conflicts[@]} -eq 0 ]; then
BODY="${MARKER}
✅ No conflicts with other open PRs targeting \`${BASE_BRANCH}\`"
else
BODY="${MARKER}
❌ Conflicts with:

$(printf '%s\n' "${conflicts[@]}")"
fi

COMMENT_ID=$(gh api --method GET "repos/${REPO}/issues/${CURRENT_PR}/comments" \
--jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" \
| head -n1)

if [ -n "$COMMENT_ID" ]; then
gh api --method PATCH "repos/${REPO}/issues/comments/${COMMENT_ID}" \
-f body="$BODY"
else
gh api --method POST "repos/${REPO}/issues/${CURRENT_PR}/comments" \
-f body="$BODY"
fi
29 changes: 29 additions & 0 deletions .github/workflows/pr-conflicts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Detect PR Conflicts

on:
pull_request:
types: [opened, synchronize, reopened]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
detect-conflicts:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
Comment thread
mchain0 marked this conversation as resolved.
Outdated

- uses: ./.github/actions/prconflicts
with:
github-token: ${{ github.token }}
repository: ${{ github.repository }}
pr-number: ${{ github.event.pull_request.number }}
head-ref: ${{ github.head_ref }}
base-ref: ${{ github.base_ref }}
Loading