Auto-Merge #95
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Auto-Merge | |
| on: | |
| # Trigger when PR checks complete | |
| pull_request_review: | |
| types: [submitted] | |
| check_suite: | |
| types: [completed] | |
| # Default permissions: read-only | |
| # Write permissions are granted explicitly at job level | |
| permissions: | |
| contents: read | |
| jobs: | |
| auto-merge: | |
| name: Auto-Merge Approved PRs | |
| runs-on: ubuntu-latest | |
| # Only run on PRs targeting main - check appropriate event context | |
| # Skip for Dependabot PRs (they should never auto-merge) | |
| # Note: We let jobs with no valid PR pass the condition and handle it in the steps | |
| if: | | |
| github.actor != 'dependabot[bot]' && ( | |
| (github.event_name == 'pull_request_review' && github.event.pull_request.base.ref == 'main') || | |
| (github.event_name == 'check_suite') | |
| ) | |
| permissions: | |
| contents: write # Required to merge PRs | |
| pull-requests: write # Required to add comments and labels | |
| steps: | |
| - name: Get PR number | |
| id: pr | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request_review" ]; then | |
| echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT | |
| echo "base_ref=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT | |
| elif [ "${{ github.event_name }}" = "check_suite" ]; then | |
| # Get PR number from check suite (with null safety) | |
| PR_COUNT="${{ toJSON(github.event.check_suite.pull_requests) != '[]' && '1' || '0' }}" | |
| if [ "$PR_COUNT" = "1" ]; then | |
| PR_NUMBER="${{ github.event.check_suite.pull_requests[0].number }}" | |
| BASE_REF="${{ github.event.check_suite.pull_requests[0].base.ref }}" | |
| # Only proceed if targeting main | |
| if [ "$BASE_REF" = "main" ]; then | |
| echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT | |
| echo "base_ref=$BASE_REF" >> $GITHUB_OUTPUT | |
| else | |
| echo "PR #$PR_NUMBER targets $BASE_REF (not main), skipping" | |
| exit 0 | |
| fi | |
| else | |
| echo "No PR found in check_suite event, skipping" | |
| exit 0 | |
| fi | |
| else | |
| echo "Unsupported event type, skipping" | |
| exit 0 | |
| fi | |
| - name: Check if PR should auto-merge | |
| id: should-merge | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| run: | | |
| if [ -z "$PR_NUMBER" ]; then | |
| echo "No PR number, skipping" | |
| echo "should_merge=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Get PR details | |
| PR_JSON=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER) | |
| # Check if PR has auto-merge label | |
| HAS_LABEL=$(echo "$PR_JSON" | jq -r '.labels[]? | select(.name == "auto-merge") | .name' || echo "") | |
| # Check if PR is approved | |
| REVIEWS=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews) | |
| APPROVED=$(echo "$REVIEWS" | jq -r '[.[] | select(.state == "APPROVED")] | length') | |
| # Check if PR is mergeable | |
| MERGEABLE=$(echo "$PR_JSON" | jq -r '.mergeable') | |
| STATE=$(echo "$PR_JSON" | jq -r '.mergeable_state') | |
| DRAFT=$(echo "$PR_JSON" | jq -r '.draft') | |
| echo "📊 PR Status:" | |
| echo " PR #$PR_NUMBER" | |
| echo " Has auto-merge label: $HAS_LABEL" | |
| echo " Approved reviews: $APPROVED" | |
| echo " Mergeable: $MERGEABLE" | |
| echo " Mergeable state: $STATE" | |
| echo " Draft: $DRAFT" | |
| # Require: auto-merge label + approval + mergeable + not draft | |
| if [ "$HAS_LABEL" = "auto-merge" ] && \ | |
| [ "$APPROVED" -gt "0" ] && \ | |
| [ "$MERGEABLE" = "true" ] && \ | |
| [ "$STATE" = "clean" ] && \ | |
| [ "$DRAFT" = "false" ]; then | |
| echo "✅ PR ready for auto-merge" | |
| echo "should_merge=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "⏸️ PR not ready for auto-merge" | |
| echo "should_merge=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Auto-merge PR | |
| if: steps.should-merge.outputs.should_merge == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| run: | | |
| echo "🚀 Auto-merging PR #$PR_NUMBER" | |
| # Merge the PR (squash by default) | |
| gh pr merge $PR_NUMBER \ | |
| --squash \ | |
| --auto \ | |
| --delete-branch \ | |
| --subject "Auto-merge: PR #$PR_NUMBER" | |
| echo "✅ PR #$PR_NUMBER queued for auto-merge" | |
| - name: Comment on PR | |
| if: steps.should-merge.outputs.should_merge == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| run: | | |
| gh pr comment $PR_NUMBER --body "$(cat <<'EOF' | |
| 🤖 **Auto-Merge Enabled** | |
| This PR has been approved and all checks have passed. It will be automatically merged once any remaining required checks complete. | |
| If you need to make changes, remove the \`auto-merge\` label to prevent automatic merging. | |
| EOF | |
| )" |