diff --git a/.github/workflows/cla-check.yaml b/.github/workflows/cla-check.yaml index cf55824..312d195 100644 --- a/.github/workflows/cla-check.yaml +++ b/.github/workflows/cla-check.yaml @@ -22,15 +22,16 @@ jobs: check-cla: runs-on: ${{ inputs.runner }} steps: - # --- Step 1: Check Base Branch --- - - name: Checkout base branch and check for contributor status + - name: Checkout Base Branch uses: actions/checkout@v6 with: token: ${{ secrets.GITHUB_TOKEN }} ref: ${{ github.ref }} + path: base_branch - - name: Determine if contributor exists in base (new) + - name: Determine if contributor exists in base id: check_contributor_base + working-directory: ./base_branch run: | AUTHOR="${{ github.event.pull_request.user.login }}" if [ -f "CONTRIBUTORS.md" ]; then @@ -47,17 +48,16 @@ jobs: echo "🔴 CONTRIBUTORS.md file does not exist on base. Proceeding to check PR branch." fi - # --- Step 2: Check PR Branch --- - - name: Checkout PR branch and check for contributor status - # Always check PR branch to detect if contributor removed themselves + - name: Checkout PR Branch uses: actions/checkout@v6 with: token: ${{ secrets.GITHUB_TOKEN }} ref: ${{ github.event.pull_request.head.sha }} + path: pr_branch - name: Determine if contributor exists in PR branch id: check_contributor_pr - # Always check PR branch to detect if contributor removed themselves + working-directory: pr_branch run: | AUTHOR="${{ github.event.pull_request.user.login }}" if [ -f "CONTRIBUTORS.md" ]; then @@ -73,25 +73,53 @@ jobs: echo "🔴 CONTRIBUTORS.md file does not exist on PR branch." fi + - name: Check Merge Ref Exists + id: check_merge_ref + working-directory: ./base_branch + shell: bash + run: | + ref="refs/pull/${{ github.event.number }}/merge" + hash=$(git ls-remote origin $ref) + if [[ -z $hash ]]; then + echo "merge_ref_defined=false" >> $GITHUB_ENV + else + echo "merge_ref_defined=true" >> $GITHUB_ENV + fi + + - name: Checkout Merge Branch + if: env.merge_ref_defined == 'true' + id: checkout_merge + uses: actions/checkout@v6 + continue-on-error: true + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: "refs/pull/${{ github.event.number }}/merge" + path: merge_branch + # -- Check if CONTRIBUTORS.md was modified in this PR - name: Check if CONTRIBUTORS.md was modified in PR id: check_contributors_modified run: | - # Fetch the base branch - git fetch origin ${{ github.event.pull_request.base.ref }} + if [[ "$merge_ref_defined" == "false" ]]; then + echo "::warning::Merge conflicts mean the check for modification of a signed CONTRIBUTORS.md file was not completed. Allowing this to pass while conflict exists." + echo "modified=false" >> $GITHUB_OUTPUT + else + cd merge_branch - # Use the base SHA directly for comparison (works with forks) - BASE_SHA="${{ github.event.pull_request.base.sha }}" - HEAD_SHA="${{ github.event.pull_request.head.sha }}" + git fetch origin ${{ github.event.pull_request.base.ref }} - CHANGED_FILES=$(git diff --name-only $BASE_SHA..$HEAD_SHA) + # Use the base SHA directly for comparison (works with forks) + BASE_SHA="${{ github.event.pull_request.base.sha }}" - if echo "$CHANGED_FILES" | grep -q "^CONTRIBUTORS.md$"; then - echo "modified=true" >> $GITHUB_OUTPUT - echo "📝 CONTRIBUTORS.md file was modified in this PR." - else - echo "modified=false" >> $GITHUB_OUTPUT - echo "â„šī¸ CONTRIBUTORS.md file was NOT modified in this PR." + CHANGED_FILES=$(git diff --name-only $BASE_SHA) + + if echo "$CHANGED_FILES" | grep -q "^CONTRIBUTORS.md$"; then + echo "modified=true" >> $GITHUB_OUTPUT + echo "📝 CONTRIBUTORS.md file was modified in this PR." + else + echo "modified=false" >> $GITHUB_OUTPUT + echo "â„šī¸ CONTRIBUTORS.md file was NOT modified in this PR." + fi fi # -- Step 3: Manage PR Labels, Comments, and Final Status (Consolidated) @@ -111,30 +139,47 @@ jobs: const repo = context.repo.repo; const author = context.payload.pull_request.user.login; - // Check if contributor was on base but removed themselves from PR - const removedFromPr = signedOnBase && !signedOnPr && contributorsModified; - // CLA is met if: (1) signed on both base and PR, OR (2) signed on base and didn't modify CONTRIBUTORS.md - const cla_met = (signedOnBase && signedOnPr) || (signedOnBase && !contributorsModified); + // Check if contributor was on base but modified the file + // This may be valid but will require an admin to ok it + const invalidModify = signedOnBase && contributorsModified; - // Helper function to create or update a label with a specific color - async function ensureLabel(name, color, description) { + // CLA is met if: + // * Didn't Invalidly Modify the CONTRIBUTORS and + // * signed on either base or PR + const cla_met = !invalidModify && (signedOnBase || signedOnPr); + + // Helper function to create or update a label with a specific COLOUR + async function ensureLabel(name, COLOUR, description) { try { - await github.rest.issues.updateLabel({ owner, repo, name, color, description }); - console.log(`Updated label: ${name} with color ${color}`); + await github.rest.issues.updateLabel({ + owner: owner, + repo: repo, + name: name, + description: description, + color: COLOUR + }); + console.log(`Updated label: ${name} with COLOUR ${COLOUR}`); } catch (error) { // If update fails (label doesn't exist), create it try { - await github.rest.issues.createLabel({ owner, repo, name, color, description }); - console.log(`Created new label: ${name} with color ${color}`); + await github.rest.issues.createLabel({ + owner: owner, + repo: repo, + name: name, + description: description, + color: COLOUR + }); + console.log(`Created new label: ${name} with COLOUR ${COLOUR}`); } catch (createError) { console.log(`Error with label ${name}:`, createError.message); } } } - // Define desired colors and descriptions for consistency - const COLOR_SIGNED = '0052cc'; // Blue - const COLOR_REQUIRED = 'b60205'; // Red + // Define desired COLOURs and descriptions for consistency + const COLOUR_SIGNED = '0052cc'; // Blue + const COLOUR_REQUIRED = 'b60205'; // Red + const COLOUR_MODIFIED = 'f56f27'; // Orange // Helper function to delete old CLA-related comments from this bot async function deleteOldClaComments() { @@ -170,34 +215,33 @@ jobs: } } - console.log(`CLA Met: ${cla_met} (Base: ${signedOnBase}, PR: ${signedOnPr}, Modified: ${contributorsModified}, Removed: ${removedFromPr})`); + console.log(`CLA Met: ${cla_met} (Base: ${signedOnBase}, PR: ${signedOnPr}, Modified: ${contributorsModified}, Invalid Modify: ${invalidModify})`); - // Handle case where contributor removed themselves from CONTRIBUTORS file - if (removedFromPr) { - await ensureLabel('cla-required', COLOR_REQUIRED, 'CLA signature is required for this PR.'); - console.log('âš ī¸ Contributor was in base branch but removed from PR branch.'); + // Handle case where contributor modified the file when already on it + if (invalidModify) { + await ensureLabel('cla-modified', COLOUR_MODIFIED, 'The CLA has been modified as part of this PR - added by GA'); + console.log('âš ī¸ Contributor was in base branch but has modified the file.'); // Ensure labels are correct await Promise.allSettled([ github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'cla-signed' }), - github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['cla-required'] }) + github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['cla-modified'] }) ]); // Delete old CLA comments before posting new one await deleteOldClaComments(); // Post warning comment - const commentBody = `âš ī¸ Hello @${author}!\n\nYour CLA signature was found on the base branch, but you appear to have removed yourself from the _CONTRIBUTORS.md_ file in this PR.\n\nPlease ensure your entry remains in the _CONTRIBUTORS.md_ file. If you have already signed the CLA, you should not remove your details from the file.`; + const commentBody = `âš ī¸ Hello @${author}!\n\nYour CLA signature was found on the base branch, but you appear to have modified the _CONTRIBUTORS.md_ file in this PR.\n\nPlease do not edit the _CONTRIBUTORS.md_ file. If you have already signed the CLA, revert changes to the file and your signature will be picked up.`; await github.rest.issues.createComment({ owner, repo, issue_number, body: commentBody }); // Fail the GitHub Action run - console.error("âš ī¸ Contributor removed themselves from CONTRIBUTORS file."); + console.error("âš ī¸ Contributor edited the CONTRIBUTORS file when already on base."); process.exit(1); } if (cla_met) { - await ensureLabel('cla-signed', COLOR_SIGNED, 'This contributor has signed the CLA.'); // Different messages based on scenario if (signedOnBase && !contributorsModified) { @@ -209,7 +253,6 @@ jobs: // Use Promise.allSettled for robust label management await Promise.allSettled([ github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'cla-required' }), - github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['cla-signed'] }) ]); // Delete old CLA comments since CLA is satisfied @@ -217,7 +260,7 @@ jobs: } else if (!signedOnBase && signedOnPr) { // New contributor signing CLA for the first time - await ensureLabel('cla-signed', COLOR_SIGNED, 'This contributor has signed the CLA.'); + await ensureLabel('cla-signed', COLOUR_SIGNED, 'The CLA has been signed as part of this PR - added by GA'); console.log('✅ New contributor has signed the CLA in PR branch.'); await Promise.allSettled([ github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'cla-required' }), @@ -228,7 +271,7 @@ jobs: await deleteOldClaComments(); } else { - await ensureLabel('cla-required', COLOR_REQUIRED, 'CLA signature is required for this PR.'); + await ensureLabel('cla-required', COLOUR_REQUIRED, 'The CLA has not yet been signed by the author of this PR - added by GA'); console.log('❌ CLA condition NOT met. Adding required label and ensuring signed label is absent.'); // Ensure labels are correct