|
6 | 6 | workflow_dispatch: |
7 | 7 |
|
8 | 8 | permissions: |
9 | | - pull-requests: read |
10 | | - issues: write |
| 9 | + pull-requests: write |
| 10 | + |
| 11 | +concurrency: |
| 12 | + group: label-pr-review-state |
| 13 | + cancel-in-progress: false |
11 | 14 |
|
12 | 15 | jobs: |
13 | 16 | reconcile: |
14 | 17 | runs-on: ubuntu-latest |
15 | 18 | steps: |
16 | 19 | - name: Reconcile PR review state labels |
17 | | - uses: actions/github-script@v7 |
| 20 | + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 |
18 | 21 | with: |
19 | 22 | script: | |
20 | 23 | const { owner, repo } = context.repo; |
21 | 24 | const stateLabels = ['awaiting-author', 'awaiting-review']; |
| 25 | + const failures = []; |
22 | 26 |
|
23 | 27 | const prs = await github.paginate(github.rest.pulls.list, { |
24 | 28 | owner, repo, state: 'open', per_page: 100, |
25 | 29 | }); |
26 | 30 |
|
27 | 31 | for (const pr of prs) { |
28 | | - const reviews = await github.paginate(github.rest.pulls.listReviews, { |
29 | | - owner, repo, pull_number: pr.number, per_page: 100, |
30 | | - }); |
| 32 | + try { |
| 33 | + const reviews = await github.paginate(github.rest.pulls.listReviews, { |
| 34 | + owner, repo, pull_number: pr.number, per_page: 100, |
| 35 | + }); |
31 | 36 |
|
32 | | - // Reviews are returned chronologically, so later entries replace |
33 | | - // each reviewer's earlier decision. |
34 | | - const latest = new Map(); |
35 | | - for (const r of reviews) { |
36 | | - if (r.state !== 'COMMENTED') { |
37 | | - latest.set(r.user.login, r); |
| 37 | + // Reviews are returned chronologically, so later entries replace |
| 38 | + // each reviewer's earlier decision. |
| 39 | + const latest = new Map(); |
| 40 | + for (const r of reviews) { |
| 41 | + if (r.state !== 'COMMENTED') { |
| 42 | + latest.set(r.user.login, r); |
| 43 | + } |
38 | 44 | } |
39 | | - } |
40 | 45 |
|
41 | | - const changeRequestReviewers = [...latest.entries()] |
42 | | - .filter(([, review]) => review.state === 'CHANGES_REQUESTED') |
43 | | - .map(([login]) => login); |
44 | | - const requestedReviewers = new Set( |
45 | | - pr.requested_reviewers.map(reviewer => reviewer.login), |
46 | | - ); |
| 46 | + const changeRequestReviewers = [...latest.entries()] |
| 47 | + .filter(([, review]) => review.state === 'CHANGES_REQUESTED') |
| 48 | + .map(([login]) => login); |
| 49 | + const requestedReviewers = new Set( |
| 50 | + pr.requested_reviewers.map(reviewer => reviewer.login), |
| 51 | + ); |
47 | 52 |
|
48 | | - let desiredLabel = null; |
49 | | - if (changeRequestReviewers.length > 0) { |
50 | | - desiredLabel = changeRequestReviewers.every( |
51 | | - reviewer => requestedReviewers.has(reviewer), |
52 | | - ) |
53 | | - ? 'awaiting-review' |
54 | | - : 'awaiting-author'; |
55 | | - } |
| 53 | + let desiredLabel = null; |
| 54 | + if (changeRequestReviewers.length > 0) { |
| 55 | + desiredLabel = changeRequestReviewers.every( |
| 56 | + reviewer => requestedReviewers.has(reviewer), |
| 57 | + ) |
| 58 | + ? 'awaiting-review' |
| 59 | + : 'awaiting-author'; |
| 60 | + } |
56 | 61 |
|
57 | | - const currentLabels = new Set(pr.labels.map(label => label.name)); |
58 | | - for (const label of stateLabels) { |
59 | | - if (label !== desiredLabel && currentLabels.has(label)) { |
60 | | - await github.rest.issues.removeLabel({ |
61 | | - owner, repo, issue_number: pr.number, name: label, |
| 62 | + const currentLabels = new Set(pr.labels.map(label => label.name)); |
| 63 | + for (const label of stateLabels) { |
| 64 | + if (label !== desiredLabel && currentLabels.has(label)) { |
| 65 | + await github.rest.issues.removeLabel({ |
| 66 | + owner, repo, issue_number: pr.number, name: label, |
| 67 | + }); |
| 68 | + } |
| 69 | + } |
| 70 | +
|
| 71 | + if (desiredLabel && !currentLabels.has(desiredLabel)) { |
| 72 | + await github.rest.issues.addLabels({ |
| 73 | + owner, repo, issue_number: pr.number, labels: [desiredLabel], |
62 | 74 | }); |
63 | 75 | } |
64 | | - } |
65 | 76 |
|
66 | | - if (desiredLabel && !currentLabels.has(desiredLabel)) { |
67 | | - await github.rest.issues.addLabels({ |
68 | | - owner, repo, issue_number: pr.number, labels: [desiredLabel], |
69 | | - }); |
| 77 | + if ( |
| 78 | + desiredLabel !== 'awaiting-author' && |
| 79 | + currentLabels.has('stale-awaiting-author') |
| 80 | + ) { |
| 81 | + await github.rest.issues.removeLabel({ |
| 82 | + owner, repo, issue_number: pr.number, |
| 83 | + name: 'stale-awaiting-author', |
| 84 | + }); |
| 85 | + } |
| 86 | + } catch (error) { |
| 87 | + failures.push(`#${pr.number}: ${error.message}`); |
| 88 | + core.error(`Failed to reconcile PR #${pr.number}: ${error.message}`); |
70 | 89 | } |
| 90 | + } |
71 | 91 |
|
72 | | - if ( |
73 | | - desiredLabel !== 'awaiting-author' && |
74 | | - currentLabels.has('stale-awaiting-author') |
75 | | - ) { |
76 | | - await github.rest.issues.removeLabel({ |
77 | | - owner, repo, issue_number: pr.number, |
78 | | - name: 'stale-awaiting-author', |
79 | | - }); |
80 | | - } |
| 92 | + if (failures.length > 0) { |
| 93 | + core.setFailed(`Failed to reconcile ${failures.length} PR(s): ${failures.join('; ')}`); |
81 | 94 | } |
0 commit comments