1+ # This workflow checks that the PR has been approved by 2+ members of the committee listed in `pull_requests.toml`.
2+ #
3+ # Run this action when a pull request review is submitted / dismissed.
4+ # Note that an approval can be dismissed, and this can affect the job status.
5+ # We currently trust that contributors won't make significant changes to their PRs after approval, so we accept
6+ # changes after approval.
7+ #
8+ # We still need to run this in the case of a merge group, since it is a required step. In that case, the workflow
9+ # is a NOP.
110name : Check PR Approvals
2-
3- # For now, the workflow gets triggered only when a review is submitted
4- # This technically means, a PR with zero approvals can be merged by the rules of this workflow alone
5- # To protect against that scenario, we can turn on number of approvals required to 2 in the github settings
6- # of the repository
711on :
12+ merge_group :
813 pull_request_review :
9- types : [submitted]
14+ types : [submitted, dismissed ]
1015
1116jobs :
1217 check-approvals :
1318 runs-on : ubuntu-latest
1419 steps :
1520 - name : Checkout repository
1621 uses : actions/checkout@v2
22+ if : ${{ github.event_name != 'merge_group' }}
1723
1824 - name : Install TOML parser
1925 run : npm install @iarna/toml
26+ if : ${{ github.event_name != 'merge_group' }}
2027
2128 - name : Check PR Relevance and Approvals
2229 uses : actions/github-script@v6
30+ if : ${{ github.event_name != 'merge_group' }}
2331 with :
2432 script : |
2533 const fs = require('fs');
4452 pull_number = context.issue.number;
4553 }
4654
55+ console.log(`owner is ${owner}`);
56+ console.log(`pull_number is ${pull_number}`);
4757
4858 // Get parsed data
59+ let requiredApprovers;
4960 try {
5061 const tomlContent = fs.readFileSync('.github/pull_requests.toml', 'utf8');
5162 console.log('TOML content:', tomlContent);
@@ -62,38 +73,63 @@ jobs:
6273 return;
6374 }
6475
65- // Get all reviews
66- const reviews = await github.rest.pulls.listReviews({
67- owner,
68- repo,
69- pull_number
70- });
76+ // Get all reviews with pagination
77+ async function getAllReviews() {
78+ let allReviews = [];
79+ let page = 1;
80+ let page_limit = 100;
81+
82+ while (page < page_limit) {
83+ const response = await github.rest.pulls.listReviews({
84+ owner,
85+ repo,
86+ pull_number,
87+ per_page: 100,
88+ page
89+ });
90+
91+ allReviews = allReviews.concat(response.data);
92+
93+ if (response.data.length < 100) {
94+ break;
95+ }
96+
97+ page++;
98+ }
99+
100+ if (page == page_limit) {
101+ console.log(`WARNING: Reached page limit of ${page_limit} while fetching reviews data; approvals count may be less than the real total.`)
102+ }
103+
104+ return allReviews;
105+ }
106+
107+ const reviews = await getAllReviews();
71108
72109 // Example: approvers = ["celina", "zyad"]
73110 const approvers = new Set(
74- reviews.data
111+ reviews
75112 .filter(review => review.state === 'APPROVED')
76113 .map(review => review.user.login)
77114 );
78115
79116 const requiredApprovals = 2;
80- const currentCountfromCommittee = Array.from(approvers)
81- .filter(approver => requiredApprovers.includes(approver))
82- .length;
83-
84- // TODO: Improve logging and messaging to the user
85- console.log('PR Approvers:', Array.from(approvers));
86- console.log('Required Approvers:', requiredApprovals);
117+ const committeeApprovers = Array.from(approvers)
118+ .filter(approver => requiredApprovers.includes(approver));
119+ const currentCountfromCommittee = committeeApprovers.length;
87120
88121 // Core logic that checks if the approvers are in the committee
89- const checkName = 'PR Approval Status';
90- const conclusion = (approvers.size >= requiredApprovals && currentCountfromCommittee >= 2) ? 'success' : 'failure';
91- const output = {
92- title: checkName,
93- summary: `PR has ${approvers.size} total approvals and ${requiredApprovals} required approvals.`,
94- text: `Approvers: ${Array.from(approvers).join(', ')}\nRequired Approvers: ${requiredApprovers.join(', ')}`
95- };
122+ const conclusion = (currentCountfromCommittee >= 2) ? 'success' : 'failure';
96123
124+ console.log('PR Approval Status');
125+ console.log(`PR has ${approvers.size} total approvals and ${currentCountfromCommittee} required approvals from the committee.`);
126+
127+ console.log(`Committee Members: [${requiredApprovers.join(', ')}]`);
128+ console.log(`Committee Approvers: ${committeeApprovers.length === 0 ? 'NONE' : `[${committeeApprovers.join(', ')}]`}`);
129+ console.log(`All Approvers: ${approvers.size === 0 ? 'NONE' : `[${Array.from(approvers).join(', ')}]`}`);
130+
97131 if (conclusion === 'failure') {
98- core.setFailed(`PR needs at least ${requiredApprovals} total approvals and 2 required approvals. Current approvals: ${approvers.size}, Required approvals: ${requiredApprovals}`);
132+ core.setFailed(`PR needs 2 approvals from committee members, but it has ${currentCountfromCommittee}`);
133+ } else {
134+ core.info('PR approval check passed successfully.');
99135 }
0 commit comments