@@ -11,6 +11,38 @@ import (
11
11
"github.com/xlab/treeprint"
12
12
)
13
13
14
+ // deduplicateReviews returns a list of reviews with at most 1 review per
15
+ // author, where approval/changes requested reviews are preferred over comments
16
+ // and later reviews are preferred over earlier ones.
17
+ func deduplicateReviews (reviews []* github.PullRequestReview ) []* github.PullRequestReview {
18
+ added := make (map [string ]int )
19
+ result := make ([]* github.PullRequestReview , 0 , len (reviews ))
20
+ for _ , rev := range reviews {
21
+ idx , ok := added [rev .User .GetLogin ()]
22
+ switch utils .ReviewState (rev .GetState ()) {
23
+ case utils .ReviewStateApproved , utils .ReviewStateChangesRequested :
24
+ // this review changes the "approval state", and is more relevant,
25
+ // so substitute it with the previous one if it exists.
26
+ if ok {
27
+ result [idx ] = rev
28
+ } else {
29
+ result = append (result , rev )
30
+ added [rev .User .GetLogin ()] = len (result ) - 1
31
+ }
32
+ case utils .ReviewStateCommented :
33
+ // this review does not change the "approval state", so only append
34
+ // it if a previous review doesn't exist.
35
+ if ! ok {
36
+ result = append (result , rev )
37
+ added [rev .User .GetLogin ()] = len (result ) - 1
38
+ }
39
+ default :
40
+ panic (fmt .Sprintf ("invalid review state %q" , rev .GetState ()))
41
+ }
42
+ }
43
+ return result
44
+ }
45
+
14
46
// ReviewByUserRequirement asserts that there is a review by the given user,
15
47
// and if given that the review matches the desiredState.
16
48
type ReviewByUserRequirement struct {
@@ -28,6 +60,23 @@ func (r *ReviewByUserRequirement) IsSatisfied(pr *github.PullRequest, details tr
28
60
detail += fmt .Sprintf (" (with state %q)" , r .desiredState )
29
61
}
30
62
63
+ // Check if user already approved this PR.
64
+ reviews , err := r .gh .ListPRReviews (pr .GetNumber ())
65
+ if err != nil {
66
+ r .gh .Logger .Errorf ("unable to check if user %s already approved this PR: %v" , r .user , err )
67
+ return utils .AddStatusNode (false , detail , details )
68
+ }
69
+ reviews = deduplicateReviews (reviews )
70
+
71
+ for _ , review := range reviews {
72
+ if review .GetUser ().GetLogin () == r .user {
73
+ r .gh .Logger .Debugf ("User %s already reviewed PR %d with state %s" , r .user , pr .GetNumber (), review .GetState ())
74
+ result := r .desiredState == "" || review .GetState () == r .desiredState
75
+ return utils .AddStatusNode (result , detail , details )
76
+ }
77
+ }
78
+ r .gh .Logger .Debugf ("User %s has not reviewed PR %d yet" , r .user , pr .GetNumber ())
79
+
31
80
// If not a dry run, make the user a reviewer if he's not already.
32
81
if ! r .gh .DryRun {
33
82
requested := false
@@ -62,22 +111,6 @@ func (r *ReviewByUserRequirement) IsSatisfied(pr *github.PullRequest, details tr
62
111
}
63
112
}
64
113
65
- // Check if user already approved this PR.
66
- reviews , err := r .gh .ListPRReviews (pr .GetNumber ())
67
- if err != nil {
68
- r .gh .Logger .Errorf ("unable to check if user %s already approved this PR: %v" , r .user , err )
69
- return utils .AddStatusNode (false , detail , details )
70
- }
71
-
72
- for _ , review := range reviews {
73
- if review .GetUser ().GetLogin () == r .user {
74
- r .gh .Logger .Debugf ("User %s already reviewed PR %d with state %s" , r .user , pr .GetNumber (), review .GetState ())
75
- result := r .desiredState == "" || review .GetState () == r .desiredState
76
- return utils .AddStatusNode (result , detail , details )
77
- }
78
- }
79
- r .gh .Logger .Debugf ("User %s has not reviewed PR %d yet" , r .user , pr .GetNumber ())
80
-
81
114
return utils .AddStatusNode (false , detail , details )
82
115
}
83
116
@@ -123,6 +156,14 @@ func (r *ReviewByTeamMembersRequirement) IsSatisfied(pr *github.PullRequest, det
123
156
return utils .AddStatusNode (false , detail , details )
124
157
}
125
158
159
+ reviews , err := r .gh .ListPRReviews (pr .GetNumber ())
160
+ if err != nil {
161
+ r .gh .Logger .Errorf ("unable to fetch existing reviews of pr %d: %v" , pr .GetNumber (), err )
162
+ return utils .AddStatusNode (false , detail , details )
163
+ }
164
+
165
+ reviews = deduplicateReviews (reviews )
166
+
126
167
// If not a dry run, request a team review if no member has reviewed yet,
127
168
// and the team review has not been requested.
128
169
if ! r .gh .DryRun {
@@ -144,12 +185,18 @@ func (r *ReviewByTeamMembersRequirement) IsSatisfied(pr *github.PullRequest, det
144
185
145
186
if ! teamRequested {
146
187
for _ , user := range reviewers .Users {
147
- if slices .ContainsFunc (teamMembers , func (memb * github.User ) bool {
148
- return memb .GetID () == user .GetID ()
149
- }) {
188
+ if containsUserWithLogin (teamMembers , user .GetLogin ()) {
150
189
usersRequested = append (usersRequested , user .GetLogin ())
151
190
}
152
191
}
192
+
193
+ for _ , rev := range reviews {
194
+ // if not already requested and user is a team member...
195
+ if ! slices .Contains (usersRequested , rev .User .GetLogin ()) &&
196
+ containsUserWithLogin (teamMembers , rev .User .GetLogin ()) {
197
+ usersRequested = append (usersRequested , rev .User .GetLogin ())
198
+ }
199
+ }
153
200
}
154
201
155
202
switch {
@@ -176,33 +223,29 @@ func (r *ReviewByTeamMembersRequirement) IsSatisfied(pr *github.PullRequest, det
176
223
177
224
// Check how many members of this team already reviewed this PR.
178
225
reviewCount := uint (0 )
179
- reviews , err := r .gh .ListPRReviews (pr .GetNumber ())
180
- if err != nil {
181
- r .gh .Logger .Errorf ("unable to check if a member of team %s already reviewed this PR: %v" , r .team , err )
182
- return utils .AddStatusNode (false , detail , details )
183
- }
184
226
185
- stateStr := ""
186
- if r .desiredState != "" {
187
- stateStr = fmt .Sprintf ("%q " , r .desiredState )
188
- }
189
227
for _ , review := range reviews {
190
- for _ , member := range teamMembers {
191
- if review .GetUser ().GetLogin () == member .GetLogin () {
192
- if desired := r .desiredState ; desired == "" || desired == review .GetState () {
193
- reviewCount += 1
194
- }
195
- r .gh .Logger .Debugf (
196
- "Member %s from team %s already reviewed PR %d with state %s (%d/%d required %sreview(s))" ,
197
- member .GetLogin (), r .team , pr .GetNumber (), review .GetState (), reviewCount , r .count , stateStr ,
198
- )
228
+ login := review .GetUser ().GetLogin ()
229
+ if containsUserWithLogin (teamMembers , login ) {
230
+ if desired := r .desiredState ; desired == "" || desired == review .GetState () {
231
+ reviewCount += 1
199
232
}
233
+ r .gh .Logger .Debugf (
234
+ "Member %s from team %s already reviewed PR %d with state %s (%d/%d required review(s) with state %q)" ,
235
+ login , r .team , pr .GetNumber (), review .GetState (), reviewCount , r .count , r .desiredState ,
236
+ )
200
237
}
201
238
}
202
239
203
240
return utils .AddStatusNode (reviewCount >= r .count , detail , details )
204
241
}
205
242
243
+ func containsUserWithLogin (users []* github.User , login string ) bool {
244
+ return slices .ContainsFunc (users , func (u * github.User ) bool {
245
+ return u .GetLogin () == login
246
+ })
247
+ }
248
+
206
249
// WithCount specifies the number of required reviews.
207
250
// By default, this is 1.
208
251
func (r * ReviewByTeamMembersRequirement ) WithCount (n uint ) * ReviewByTeamMembersRequirement {
@@ -262,20 +305,17 @@ func (r *ReviewByOrgMembersRequirement) IsSatisfied(pr *github.PullRequest, deta
262
305
r .gh .Logger .Errorf ("unable to check number of reviews on this PR: %v" , err )
263
306
return utils .AddStatusNode (false , detail , details )
264
307
}
308
+ reviews = deduplicateReviews (reviews )
265
309
266
- stateStr := ""
267
- if r .desiredState != "" {
268
- stateStr = fmt .Sprintf ("%q " , r .desiredState )
269
- }
270
310
for _ , review := range reviews {
271
311
if review .GetAuthorAssociation () == "MEMBER" {
272
312
if r .desiredState == "" || review .GetState () == r .desiredState {
273
313
reviewed ++
274
314
}
275
315
r .gh .Logger .Debugf (
276
- "Member %s already reviewed PR %d with state %s (%d/%d required %sreviews )" ,
316
+ "Member %s already reviewed PR %d with state %s (%d/%d required reviews with state %q )" ,
277
317
review .GetUser ().GetLogin (), pr .GetNumber (), review .GetState (),
278
- reviewed , r .count , stateStr ,
318
+ reviewed , r .count , r . desiredState ,
279
319
)
280
320
}
281
321
}
0 commit comments