@@ -132,21 +132,22 @@ func (r *Rule) Evaluate(ctx context.Context, prctx pull.Context) (res common.Res
132
132
}
133
133
res .PredicateResults = predicateResults
134
134
135
- allowedCandidates , err := r .FilteredCandidates (ctx , prctx )
135
+ candidates , dismissals , err := r .FilteredCandidates (ctx , prctx )
136
136
if err != nil {
137
137
res .Error = errors .Wrap (err , "failed to filter candidates" )
138
138
return
139
139
}
140
140
141
- approved , msg , err := r .IsApproved (ctx , prctx , allowedCandidates )
141
+ approved , approvers , err := r .IsApproved (ctx , prctx , candidates )
142
142
if err != nil {
143
143
res .Error = errors .Wrap (err , "failed to compute approval status" )
144
144
return
145
145
}
146
146
147
- res .AllowedCandidates = allowedCandidates
147
+ res .Approvers = approvers
148
+ res .Dismissals = dismissals
149
+ res .StatusDescription = r .statusDescription (approved , approvers , candidates )
148
150
149
- res .StatusDescription = msg
150
151
if approved {
151
152
res .Status = common .StatusApproved
152
153
} else {
@@ -177,12 +178,12 @@ func (r *Rule) getReviewRequestRule() *common.ReviewRequestRule {
177
178
}
178
179
}
179
180
180
- func (r * Rule ) IsApproved (ctx context.Context , prctx pull.Context , candidates []* common.Candidate ) (bool , string , error ) {
181
+ func (r * Rule ) IsApproved (ctx context.Context , prctx pull.Context , candidates []* common.Candidate ) (bool , [] * common. Candidate , error ) {
181
182
log := zerolog .Ctx (ctx )
182
183
183
184
if r .Requires .Count <= 0 {
184
185
log .Debug ().Msg ("rule requires no approvals" )
185
- return true , "No approval required" , nil
186
+ return true , nil , nil
186
187
}
187
188
188
189
log .Debug ().Msgf ("found %d candidates for approval" , len (candidates ))
@@ -202,7 +203,7 @@ func (r *Rule) IsApproved(ctx context.Context, prctx pull.Context, candidates []
202
203
if ! r .Options .AllowContributor && ! r .Options .AllowNonAuthorContributor {
203
204
commits , err := r .filteredCommits (ctx , prctx )
204
205
if err != nil {
205
- return false , "" , err
206
+ return false , nil , err
206
207
}
207
208
208
209
for _ , c := range commits {
@@ -215,7 +216,7 @@ func (r *Rule) IsApproved(ctx context.Context, prctx pull.Context, candidates []
215
216
}
216
217
217
218
// filter real approvers using banned status and required membership
218
- var approvers []string
219
+ var approvers []* common. Candidate
219
220
for _ , c := range candidates {
220
221
if banned [c .User ] {
221
222
log .Debug ().Str ("user" , c .User ).Msg ("rejecting approval by banned user" )
@@ -224,113 +225,118 @@ func (r *Rule) IsApproved(ctx context.Context, prctx pull.Context, candidates []
224
225
225
226
isApprover , err := r .Requires .Actors .IsActor (ctx , prctx , c .User )
226
227
if err != nil {
227
- return false , "" , errors .Wrap (err , "failed to check candidate status" )
228
+ return false , nil , errors .Wrap (err , "failed to check candidate status" )
228
229
}
229
230
if ! isApprover {
230
- log .Debug ().Str ("user" , c .User ).Msg ("ignoring approval by non-whitelisted user" )
231
+ log .Debug ().Str ("user" , c .User ).Msg ("ignoring approval by non-required user" )
231
232
continue
232
233
}
233
234
234
- approvers = append (approvers , c . User )
235
+ approvers = append (approvers , c )
235
236
}
236
237
237
238
log .Debug ().Msgf ("found %d/%d required approvers" , len (approvers ), r .Requires .Count )
238
- remaining := r .Requires .Count - len (approvers )
239
-
240
- if remaining <= 0 {
241
- msg := fmt .Sprintf ("Approved by %s" , strings .Join (approvers , ", " ))
242
- return true , msg , nil
243
- }
244
-
245
- if len (candidates ) > 0 && len (approvers ) == 0 {
246
- msg := fmt .Sprintf ("%d/%d approvals required. Ignored %s from disqualified users" ,
247
- len (approvers ),
248
- r .Requires .Count ,
249
- numberOfApprovals (len (candidates )))
250
- return false , msg , nil
251
- }
252
-
253
- msg := fmt .Sprintf ("%d/%d approvals required" , len (approvers ), r .Requires .Count )
254
- return false , msg , nil
239
+ return len (approvers ) >= r .Requires .Count , approvers , nil
255
240
}
256
241
257
- func (r * Rule ) FilteredCandidates (ctx context.Context , prctx pull.Context ) ([]* common.Candidate , error ) {
242
+ // FilteredCandidates returns the potential approval candidates and any
243
+ // candidates that should be dimissed due to rule options.
244
+ func (r * Rule ) FilteredCandidates (ctx context.Context , prctx pull.Context ) ([]* common.Candidate , []* common.Dismissal , error ) {
245
+
258
246
candidates , err := r .Options .GetMethods ().Candidates (ctx , prctx )
259
247
if err != nil {
260
- return nil , errors .Wrap (err , "failed to get approval candidates" )
248
+ return nil , nil , errors .Wrap (err , "failed to get approval candidates" )
261
249
}
262
250
263
251
sort .Stable (common .CandidatesByCreationTime (candidates ))
264
252
253
+ var editDismissals []* common.Dismissal
265
254
if r .Options .IgnoreEditedComments {
266
- candidates , err = r .filterEditedCandidates (ctx , prctx , candidates )
255
+ candidates , editDismissals , err = r .filterEditedCandidates (ctx , prctx , candidates )
267
256
if err != nil {
268
- return nil , err
257
+ return nil , nil , err
269
258
}
270
259
}
271
260
261
+ var pushDismissals []* common.Dismissal
272
262
if r .Options .InvalidateOnPush {
273
- candidates , err = r .filterInvalidCandidates (ctx , prctx , candidates )
263
+ candidates , pushDismissals , err = r .filterInvalidCandidates (ctx , prctx , candidates )
274
264
if err != nil {
275
- return nil , err
265
+ return nil , nil , err
276
266
}
277
267
}
278
268
279
- return candidates , nil
269
+ var dismissals []* common.Dismissal
270
+ dismissals = append (dismissals , editDismissals ... )
271
+ dismissals = append (dismissals , pushDismissals ... )
272
+
273
+ return candidates , dismissals , nil
280
274
}
281
275
282
- func (r * Rule ) filterEditedCandidates (ctx context.Context , prctx pull.Context , candidates []* common.Candidate ) ([]* common.Candidate , error ) {
276
+ func (r * Rule ) filterEditedCandidates (ctx context.Context , prctx pull.Context , candidates []* common.Candidate ) ([]* common.Candidate , [] * common. Dismissal , error ) {
283
277
log := zerolog .Ctx (ctx )
284
278
285
279
if ! r .Options .IgnoreEditedComments {
286
- return candidates , nil
280
+ return candidates , nil , nil
287
281
}
288
282
289
- var allowedCandidates []* common.Candidate
290
- for _ , candidate := range candidates {
291
- if candidate .LastEditedAt .IsZero () {
292
- allowedCandidates = append (allowedCandidates , candidate )
283
+ var allowed []* common.Candidate
284
+ var dismissed []* common.Dismissal
285
+ for _ , c := range candidates {
286
+ if c .LastEditedAt .IsZero () {
287
+ allowed = append (allowed , c )
288
+ } else {
289
+ dismissed = append (dismissed , & common.Dismissal {
290
+ Candidate : c ,
291
+ Reason : "Comment was edited" ,
292
+ })
293
293
}
294
294
}
295
295
296
- log .Debug ().Msgf ("discarded %d candidates with edited comments" , len (candidates ) - len ( allowedCandidates ))
296
+ log .Debug ().Msgf ("discarded %d candidates with edited comments" , len (dismissed ))
297
297
298
- return allowedCandidates , nil
298
+ return allowed , dismissed , nil
299
299
}
300
300
301
- func (r * Rule ) filterInvalidCandidates (ctx context.Context , prctx pull.Context , candidates []* common.Candidate ) ([]* common.Candidate , error ) {
301
+ func (r * Rule ) filterInvalidCandidates (ctx context.Context , prctx pull.Context , candidates []* common.Candidate ) ([]* common.Candidate , [] * common. Dismissal , error ) {
302
302
log := zerolog .Ctx (ctx )
303
303
304
304
if ! r .Options .InvalidateOnPush {
305
- return candidates , nil
305
+ return candidates , nil , nil
306
306
}
307
307
308
308
commits , err := r .filteredCommits (ctx , prctx )
309
309
if err != nil {
310
- return nil , err
310
+ return nil , nil , err
311
311
}
312
312
if len (commits ) == 0 {
313
- return candidates , nil
313
+ return candidates , nil , nil
314
314
}
315
315
316
316
last := findLastPushed (commits )
317
317
if last == nil {
318
- return nil , errors .New ("no commit contained a push date" )
318
+ return nil , nil , errors .New ("no commit contained a push date" )
319
319
}
320
320
321
- var allowedCandidates []* common.Candidate
322
- for _ , candidate := range candidates {
323
- if candidate .CreatedAt .After (* last .PushedAt ) {
324
- allowedCandidates = append (allowedCandidates , candidate )
321
+ var allowed []* common.Candidate
322
+ var dismissed []* common.Dismissal
323
+ for _ , c := range candidates {
324
+ if c .CreatedAt .After (* last .PushedAt ) {
325
+ allowed = append (allowed , c )
326
+ } else {
327
+ dismissed = append (dismissed , & common.Dismissal {
328
+ Candidate : c ,
329
+ Reason : fmt .Sprintf ("Invalidated by push of %.7s" , last .SHA ),
330
+ })
325
331
}
326
332
}
327
333
328
334
log .Debug ().Msgf ("discarded %d candidates invalidated by push of %s at %s" ,
329
- len (candidates ) - len ( allowedCandidates ),
335
+ len (dismissed ),
330
336
last .SHA ,
331
337
last .PushedAt .Format (time .RFC3339 ))
332
338
333
- return allowedCandidates , nil
339
+ return allowed , dismissed , nil
334
340
}
335
341
336
342
func (r * Rule ) filteredCommits (ctx context.Context , prctx pull.Context ) ([]* pull.Commit , error ) {
@@ -369,6 +375,26 @@ func (r *Rule) filteredCommits(ctx context.Context, prctx pull.Context) ([]*pull
369
375
return filtered , nil
370
376
}
371
377
378
+ func (r * Rule ) statusDescription (approved bool , approvers , candidates []* common.Candidate ) string {
379
+ if approved {
380
+ if len (approvers ) == 0 {
381
+ return "No approval required"
382
+ }
383
+
384
+ var names []string
385
+ for _ , c := range approvers {
386
+ names = append (names , c .User )
387
+ }
388
+ return fmt .Sprintf ("Approved by %s" , strings .Join (names , ", " ))
389
+ }
390
+
391
+ desc := fmt .Sprintf ("%d/%d required approvals" , len (approvers ), r .Requires .Count )
392
+ if disqualified := len (candidates ) - len (approvers ); disqualified > 0 {
393
+ desc += fmt .Sprintf (". Ignored %s from disqualified users" , numberOfApprovals (disqualified ))
394
+ }
395
+ return desc
396
+ }
397
+
372
398
func isUpdateMerge (commits []* pull.Commit , c * pull.Commit ) bool {
373
399
// must be a simple merge commit (exactly 2 parents)
374
400
if len (c .Parents ) != 2 {
0 commit comments