name: Blocked/Stacked Pull Requests Automation on: pull_request_target: types: - opened - edited workflow_dispatch: inputs: pr_id: description: Local Pull Request number to work on required: true type: number jobs: blocked_status: name: Check Blocked Status runs-on: ubuntu-latest permissions: issues: write pull-requests: write statuses: write checks: write steps: - name: Setup From Pull Request Event if: github.event_name != 'workflow_dispatch' id: pr_event_setup env: REPO_L: ${{ github.event.pull_request.base.repo.name }} OWNER_L: ${{ github.event.pull_request.base.repo.owner.login }} REPO_URL_L: $ {{ github.event.pull_request.base.repo.html_url }} PR_HEAD_SHA_L: ${{ github.event.pull_request.head.sha }} PR_NUMBER_L: ${{ github.event.pull_request.number }} PR_HEAD_LABEL_L: ${{ github.event.pull_request.head.label }} PR_BODY_L: ${{ github.event.pull_request.body }} PR_LABLES_L: "${{ github.event.pull_request.labels }}" run: | # setup env for the rest of the workflow { echo "REPO=$REPO_L" echo "OWNER=$OWNER_L" echo "REPO_URL=$REPO_URL_L" echo "PR_NUMBER=$PR_NUMBER_L" echo "PR_HEAD_SHA=$PR_HEAD_SHA_L" echo "PR_HEAD_LABEL=$PR_HEAD_LABEL_L" echo "PR_BODY=$PR_BODY_L" echo "PR_LABELS=$(jq 'reduce .[].name as $l ([]; . + [$l])' <<< "$PR_LABELS_L" )" } >> "$GITHUB_ENV" - name: Setup From Dispatch Event if: github.event_name == 'workflow_dispatch' id: dispatch_event_setup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} OWNER_REPO_L: ${{ github.repository }} OWNER_L: ${{ github.repository_owner }} REPO_URL_L: $ {{ github.repositoryUrl }} PR_NUMBER_L: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow owner_prefix="$OWNER_L/" REPO_L="${OWNER_REPO_L#"$owner_prefix"}" PR_L=$( gh api \ -H "Accept: application/vnd.github.text+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER_L/$REPO_L/pulls/$PR_NUMBER_L" ) PR_HEAD_SHA_L=$(jq -r '.head.sha' <<< "$PR_L") PR_HEAD_LABEL_L=$(jq -r '.head.label' <<< "$PR_L") PR_BODY_L=$(jq -r '.body_text' <<< "$PR_L") PR_LABELS_L=$(jq '.labels' <<< "$PR_L") { echo "REPO=$REPO_L" echo "OWNER=$OWNER_L" echo "REPO_URL=$REPO_URL_L" echo "PR_NUMBER=$PR_NUMBER_L" echo "PR_HEAD_SHA=$PR_HEAD_SHA_L" echo "PR_HEAD_LABEL=$PR_HEAD_LABEL_L" echo "PR_BODY=$PR_BODY_L" echo "PR_LABELS=$(jq 'reduce .[].name as $l ([]; . + [$l])' <<< "$PR_LABELS_L" )" } >> "$GITHUB_ENV" - name: Find Blocked/Stacked PRs in body id: pr_ids run: | PRS=$( jq ' . as $body | ( $body | scan("blocked (?(?by)|(?on)):? #(?[0-9]+)") | map({ "type": "Blocked on", "number": ( . | tonumber ) }) ) as $bprs | ( $body | scan("stacked on:? #(?[0-9]+)") | map({ "type": "Stacked on", "number": ( . | tonumber ) }) ) as $sprs | ($bprs + $sprs) as $prs | { "blocking": $prs, "numBlocking": ( $prs | length), } ' <<< "$PR_BODY" ) echo "prs=$PRS" >> "$GITHUB_OUTPUT" - name: Collect Blocked PR Data id: blocked_data if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | BLOCKED_PR_DATA=$( while read -r PR ; do gh api \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$PR")" \ | jq --arg type "$(jq -r '.type' <<< "$PR")" \ ' . | { "type": $type, "number": .number, "merged": .merged, "labels": (reduce .labels[].name as $l ([]; . + [$l])), "basePrUrl": .html_url, "baseRepoName": .head.repo.name, "baseRepoOwner": .head.repo.owner.login, "baseRepoUrl": .head.repo.html_url, "baseSha": .head.sha, "baseRefName": .head.ref, } ' done < <(jq -c '.blocking[]' <<< "${{steps.pr_ids.outputs.prs}}") | jq -s ) echo "state=$BLOCKED_PR_DATA" >> "$GITHUB_OUTPUT" echo "all_merged=$(jq 'all(.[].merged; .)' <<< "$BLOCKED_PR_DATA")" - name: Apply Blocked Label if Missing id: label_blocked if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && !contains(fromJSON(env.PR_LABELS), 'blocked') && !fromJSON(steps.blocked_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels" \ -f "labels[]=blocked" - name: Remove 'blocked' Label if All Dependencies Are Merged id: unlabel_blocked if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocked_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api \ --method DELETE \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER/$REPO/issues/$PR_NUMBER/labels/blocked" - name: Apply 'blocking' Label to Dependencies if Missing id: label_blocking if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # label pr dependencies with 'blocking' if not already while read -r PR_DATA ; do if jq -e 'all(.labels[]; . != "blocking")' <<< "$PR_DATA" > /dev/null ; then PR=$(jq -r '.number' <<< "$PR_DATA") gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/$OWNER/$REPO/issues/$PR/labels" \ -f "labels[]=blocking" fi done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") - name: Apply Blocking PR Status Check id: blocked_check if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # create commit Status, overwrites previous identical context while read -r PR_DATA ; do DESC=$( jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$PR_DATA" ) gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/${OWNER}/${REPO}/statuses/${PR_HEAD_SHA}" \ -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$PR_DATA")" \ -f "target_url=$(jq -r '.basePrUrl' <<< "$PR_DATA" )" \ -f "description=$DESC" \ -f "context=continuous-integration/blocked-pr-check:$(jq '.number' <<< "$PR_DATA")" done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") - name: Context Comment id: blocked_comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true run: | COMMENT_PATH="$(pwd)/temp_comment_file.txt" touch "$COMMENT_PATH" echo "" > "$COMMENT_PATH" while read -r PR_DATA ; do BASE_PR=$(jq '.number' <<< "$PR_DATA") BASE_REF_NAME=$(jq '.baseRefName' <<< "$PR_DATA") COMPARE_URL="$REPO_URL/compare/$BASE_REF_NAME...$PR_HEAD_LABEL" STATUS=$(jq 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$PR_DATA") TYPE=$(jq -r '.type' <<< "$PR_DATA") echo " - $TYPE #$BASE_PR $STATUS [(compare)]($COMPARE_URL)" >> "$COMMENT_PATH" done < <(jq -c '.[]' <<< "${{steps.blocked_data.outputs.state}}") echo "file_path=${COMMENT_PATH}" >> "$GITHUB_OUTPUT" - name: 💬 PR Comment if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true uses: spicyparrot/pr-comment-action@v1.0.0 with: comment: "### PR Dependencies :pushpin:" comment_path: ${{ steps.blocked_comment.outputs.file_path }} comment_id: "block_pr_dependencies"