23
23
uses : actions/github-script@v7
24
24
with :
25
25
script : |
26
- // globals github, context, core are injected by github-script
26
+ // github, context, core are injected by github-script
27
27
28
28
// ──────────── 1. MANUAL ROSTERS ────────────
29
29
const jumpApprovers = ['jacobcreech', 'topointon-jump', '0x0ece'];
33
33
const pr = context.payload.pull_request;
34
34
if (!pr) { core.setFailed('No pull_request context'); return; }
35
35
36
- // 2. fetch every review on this PR
36
+ // 2. fetch *all* reviews on this PR
37
37
const { data: reviews } = await github.rest.pulls.listReviews({
38
38
owner: context.repo.owner,
39
39
repo: context.repo.repo,
@@ -46,38 +46,55 @@ jobs:
46
46
reviews.forEach(r =>
47
47
core.info(`${r.user.login} -> ${r.state} @ ${r.submitted_at}`));
48
48
49
- // 3. reduce to effective state per reviewer
50
- const states = {}; // login → {approved, changesRequested}
49
+ // 3. determine effective state per reviewer
50
+ // We replay events chronologically so DISMISSED or REQUEST_CHANGES
51
+ // can override an earlier APPROVED.
52
+ reviews.sort((a, b) => new Date(a.submitted_at) - new Date(b.submitted_at));
53
+
54
+ const status = {}; // login → {approved, blocked}
51
55
for (const r of reviews) {
52
56
const u = r.user.login;
53
- states[u] = states[u] || { approved: false, changesRequested: false };
54
- if (r.state === 'APPROVED') states[u].approved = true;
55
- if (r.state === 'REQUEST_CHANGES') states[u].changesRequested = true;
57
+ status[u] = status[u] || { approved: false, blocked: false };
58
+
59
+ switch (r.state) {
60
+ case 'APPROVED':
61
+ status[u].approved = true;
62
+ status[u].blocked = false;
63
+ break;
64
+ case 'REQUEST_CHANGES':
65
+ status[u].approved = false;
66
+ status[u].blocked = true; // blocks until new approval
67
+ break;
68
+ case 'DISMISSED':
69
+ status[u].approved = false; // previous approval is now void
70
+ break;
71
+ default:
72
+ // COMMENTED etc – ignore
73
+ }
56
74
}
57
75
58
76
// DEBUG 2: effective map
59
77
core.info('=== Effective state per reviewer ===');
60
- Object.entries(states ).forEach(([u, s]) =>
61
- core.info(`${u}: approved=${s.approved}, changesRequested =${s.changesRequested }`));
78
+ Object.entries(status ).forEach(([u, s]) =>
79
+ core.info(`${u}: approved=${s.approved}, blocked =${s.blocked }`));
62
80
63
- // 4. build final approved-and- not-blocked set
64
- const approved = Object.entries(states )
65
- .filter(([_, s]) => s.approved && !s.changesRequested )
81
+ // 4. final approved list (approved && not blocked)
82
+ const approved = Object.entries(status )
83
+ .filter(([_, s]) => s.approved && !s.blocked )
66
84
.map(([u]) => u);
67
85
68
- // DEBUG 3: final list
69
86
core.info(`Approved reviewers counted: ${approved.join(', ') || 'none'}`);
70
87
71
88
// 5. org-level checks
72
89
const hasJump = jumpApprovers.some(u => approved.includes(u));
73
90
const hasAnza = anzaApprovers.some(u => approved.includes(u));
74
91
75
- // prepare helpful failure message
76
- const missing = [];
77
- if (!hasJump) missing.push(
78
- `Jump approval missing. Any of: ${jumpApprovers.join(', ')}`);
79
- if (!hasAnza) missing.push(
80
- `Anza approval missing. Any of: ${anzaApprovers.join(', ')}`);
92
+ // helpful failure message
93
+ const missingMsgs = [];
94
+ if (!hasJump)
95
+ missingMsgs.push( `Jump approval missing – need one of: ${jumpApprovers.join(', ')}`);
96
+ if (!hasAnza)
97
+ missingMsgs.push( `Anza approval missing – need one of: ${anzaApprovers.join(', ')}`);
81
98
82
99
// PR-check summary
83
100
core.summary
88
105
])
89
106
.write();
90
107
91
- if (missing .length) {
92
- core.setFailed(missing .join(' | '));
108
+ if (missingMsgs .length) {
109
+ core.setFailed(missingMsgs .join(' | '));
93
110
} else {
94
111
core.notice('All required approvals present; merge allowed.');
95
112
}
0 commit comments