Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
21 changes: 21 additions & 0 deletions .github/workflows/re-trigger-approvals.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
on:
workflow_call:
inputs:
head_sha:
type: string
required: true

jobs:
re-trigger:
runs-on: github-ubuntu-latest-s
permissions:
actions: write
steps:
- env:
GH_TOKEN: ${{ github.token }}
HEAD_SHA: ${{ inputs.head_sha }}
run: |
run_id=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs?event=pull_request_target&head_sha=${HEAD_SHA}" \
--jq '.workflow_runs[] | select(.name == "Verified Approvals") | .id' \
| head -1)
[[ -n "${run_id}" ]] && gh api --method POST "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/rerun"
76 changes: 76 additions & 0 deletions .github/workflows/verified-approvals.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
name: Verified Approvals

on:
pull_request_target:
merge_group:

jobs:
verified-approvals:
name: verified-approvals
runs-on: github-ubuntu-latest-s
permissions:
pull-requests: read
steps:
- name: Check approvals
if: github.event_name != 'merge_group'
env:
GH_TOKEN: ${{ github.token }}
ORG_TOKEN: ${{ secrets.PR_APPROVALS }}
PR_NUMBER: ${{ github.event.pull_request.number }}
IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
ORG: SonarSource
run: |
is_external=false
# A PR is external if it comes from a fork OR contains commits from non-org members
if [[ "${IS_FORK}" == "true" ]]; then
echo "PR is from a fork: treating as external"
is_external=true
else
echo "PR is not from a fork: checking commit committers..."
# Check commit authors and committers (who wrote and who pushed) — bot accounts (type Bot) are excluded
mapfile -t commit_authors < <(
gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/commits" --paginate \
| jq -rs 'add // [] | [.[] | (.author, .committer) | select(. != null) | select(.type == "User") | .login] | unique | .[]'
)
for login in "${commit_authors[@]}"; do
if ! GH_TOKEN="${ORG_TOKEN}" gh api "orgs/${ORG}/members/${login}" --silent 2>/dev/null; then
echo "External contribution: commit by non-org member '${login}'"
is_external=true
break
fi
done
fi

if [[ "${is_external}" == "true" ]]; then
required=2
echo "External PR: requiring ${required} org-member approvals"
else
required=1
echo "Internal PR: requiring ${required} org-member approval(s)"
fi

# Collect logins with a net APPROVED state (latest review per user)
mapfile -t approved_logins < <(
gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/reviews" --paginate \
| jq -rs 'add // [] | [group_by(.user.login)[] | last | select(.state == "APPROVED") | .user.login] | .[]'
)
count=0
for login in "${approved_logins[@]}"; do
# Only count approvals from org members (requires read:org token)
if GH_TOKEN="${ORG_TOKEN}" gh api "orgs/${ORG}/members/${login}" --silent 2>/dev/null; then
echo " ${login}: org member ✓"
(( count++ )) || true
else
echo " ${login}: not an org member, ignored"
fi
done
echo "Org-member approvals: ${count} / ${required} required"

if (( count >= required )); then
echo "::notice ::Check passed: ${count} org-member approval(s)"
exit 0
else
echo "::error ::Check failed: ${count} org-member approval(s), ${required} required" >&2
exit 1
fi
Loading