|
| 1 | +name: Contributor Verification |
| 2 | + |
| 3 | +# This workflow serves two purposes: |
| 4 | +# * Verifies all commit authors/committers have signed the ThinkParQ CLA. |
| 5 | +# * Verified all commits are made using expected names+email addresses to avoid a contributor |
| 6 | +# accidentally leaking their private information (i.e., forgetting to use a GitHub noreply email). |
| 7 | + |
| 8 | +on: |
| 9 | + pull_request: |
| 10 | + types: [opened, synchronize] |
| 11 | + |
| 12 | +jobs: |
| 13 | + verify: |
| 14 | + runs-on: ubuntu-latest |
| 15 | + steps: |
| 16 | + - name: Check out code |
| 17 | + uses: actions/checkout@v3 |
| 18 | + with: |
| 19 | + fetch-depth: 0 # Ensure we have the full commit history for this PR |
| 20 | + - name: Verify the creator of this PR has signed the ThinkParQ contributor license agreement (CLA) |
| 21 | + env: |
| 22 | + APPROVED_CONTRIBUTORS: ${{ vars.APPROVED_CONTRIBUTORS }} |
| 23 | + run: | |
| 24 | + PR_USER="${{ github.event.pull_request.user.login }}" |
| 25 | + echo "Pull request created by '$PR_USER'" |
| 26 | +
|
| 27 | + # APPROVED_CONTRIBUTORS is expected as a space-separated list (name1, name2, ...) |
| 28 | + ALLOWED_USERS="$APPROVED_CONTRIBUTORS" |
| 29 | + IS_ALLOWED=false |
| 30 | +
|
| 31 | + for user in $ALLOWED_USERS; do |
| 32 | + if [ "$user" = "$PR_USER" ]; then |
| 33 | + IS_ALLOWED=true |
| 34 | + break |
| 35 | + fi |
| 36 | + done |
| 37 | +
|
| 38 | + if [ "$IS_ALLOWED" = "false" ]; then |
| 39 | + echo "::error::User '$PR_USER' has not yet signed the ThinkParQ contributor license agreement. Please contact [email protected] to get started." |
| 40 | + exit 1 |
| 41 | + else |
| 42 | + echo "::notice::User '$PR_USER' has signed the ThinkParQ contributor license agreement." |
| 43 | + fi |
| 44 | + - name: Verify all commits were made by known committers using their expected names and emails |
| 45 | + env: |
| 46 | + # Fine to print the list of approved committers in the logs because it only contains a |
| 47 | + # list of names and emails that should be allowed in commits. |
| 48 | + APPROVED_COMMITTERS: ${{ vars.APPROVED_COMMITTERS }} |
| 49 | + run: | |
| 50 | + # Determine base branch for this PR |
| 51 | + BASE_REF="${{ github.event.pull_request.base.ref }}" |
| 52 | + echo "Base branch is $BASE_REF" |
| 53 | +
|
| 54 | + # Gather the commits that are unique to this PR |
| 55 | + COMMITS=$(git log "origin/$BASE_REF..HEAD" --pretty=format:"%H") |
| 56 | +
|
| 57 | + if [ -z "$COMMITS" ]; then |
| 58 | + echo "No new commits found (maybe this PR is empty?)." |
| 59 | + exit 0 |
| 60 | + fi |
| 61 | +
|
| 62 | + echo "Analyzing commits in this PR:" |
| 63 | + echo "$COMMITS" |
| 64 | +
|
| 65 | + # Parse the JSON from $APPROVED_COMMITTERS using 'jq' |
| 66 | + # Expected JSON structure is { "Name1": "Email1", "Name2": "Email2", ... } |
| 67 | +
|
| 68 | + EXIT_CODE=0 |
| 69 | +
|
| 70 | + for c in $COMMITS; do |
| 71 | + AUTH_NAME=$(git show -s --format="%an" "$c") |
| 72 | + AUTH_EMAIL=$(git show -s --format="%ae" "$c") |
| 73 | + COMM_NAME=$(git show -s --format="%cn" "$c") |
| 74 | + COMM_EMAIL=$(git show -s --format="%ce" "$c") |
| 75 | +
|
| 76 | + # Mask both emails so they won't appear in cleartext logs |
| 77 | + echo "::add-mask::$AUTH_EMAIL" |
| 78 | + echo "::add-mask::$COMM_EMAIL" |
| 79 | +
|
| 80 | + echo "Checking commit $c by $AUTH_NAME / committer $COMM_NAME" |
| 81 | +
|
| 82 | + # Lookup the expected email for the AUTHOR name |
| 83 | + EXPECTED_AUTHOR_EMAIL=$(echo "${APPROVED_COMMITTERS}" | jq -r ".\"$AUTH_NAME\"") |
| 84 | + if [ "$EXPECTED_AUTHOR_EMAIL" = "null" ] || [ -z "$EXPECTED_AUTHOR_EMAIL" ]; then |
| 85 | + echo "::error::Author name '$AUTH_NAME' is not an approved name. Did they forget to set the right Git user.name?" |
| 86 | + EXIT_CODE=1 |
| 87 | + else |
| 88 | + # Compare actual email to the expected email |
| 89 | + if [ "$AUTH_EMAIL" != "$EXPECTED_AUTHOR_EMAIL" ]; then |
| 90 | + echo "::error::Author '$AUTH_NAME' used an unapproved email. Did they forget to set the right Git user.email?" |
| 91 | + EXIT_CODE=1 |
| 92 | + fi |
| 93 | + fi |
| 94 | +
|
| 95 | + # Lookup the expected email for the COMMITTER name |
| 96 | + EXPECTED_COMMITTER_EMAIL=$(echo "${APPROVED_COMMITTERS}" | jq -r ".\"$COMM_NAME\"") |
| 97 | + if [ "$EXPECTED_COMMITTER_EMAIL" = "null" ] || [ -z "$EXPECTED_COMMITTER_EMAIL" ]; then |
| 98 | + echo "::error::Committer name '$COMM_NAME' is not an approved name. Did they forget to set the right Git user.name?" |
| 99 | + EXIT_CODE=1 |
| 100 | + else |
| 101 | + if [ "$COMM_EMAIL" != "$EXPECTED_COMMITTER_EMAIL" ]; then |
| 102 | + echo "::error::Committer '$COMM_NAME' used an unapproved email. Did they forget to set the right Git user.email" |
| 103 | + EXIT_CODE=1 |
| 104 | + fi |
| 105 | + fi |
| 106 | + done |
| 107 | +
|
| 108 | + if [ "$EXIT_CODE" -ne 0 ]; then |
| 109 | + echo "::error::One or more commits failed the policy check." |
| 110 | + exit $EXIT_CODE |
| 111 | + fi |
| 112 | +
|
| 113 | + echo "::notice::All commits were made by known committers with their expected names and emails." |
0 commit comments