diff --git a/README.md b/README.md index c640a059e..77141db29 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,20 @@ maxRequestedChanges: NONE: 0 ``` +### `maxPendingReviews` (condition, default: .inf) + +Similar to `maxRequestedChanges`, maxPendingReviews determines the maximum number of +non-completed reviews before a pull request will be blocked from being automatically +merged. + +In the example below, automatic merges will be blocked when one of the owners, members +or collaborators has not completed the requested review. + +```yaml +maxPendingReviews: + COLLABORATOR: 0 +``` + ### `blockingBaseBranches` (condition, default: none) Whenever blocking base branches are configured, pull requests will only be automatically diff --git a/auto-merge.example.yml b/auto-merge.example.yml index 1861680d1..d75fc6001 100644 --- a/auto-merge.example.yml +++ b/auto-merge.example.yml @@ -18,6 +18,14 @@ requiredReviewers: maxRequestedChanges: COLLABORATOR: 0 +# The maximum number of pending reviews from each association. +# Setting this number to 0 will prevent automatic merging while not all reviewers have either +# approved or requested changes.. +# Pending reviews from associations not defined in this list are ignored for automatic merging. +# Dismissed reviews are ignored for automatic merging. +maxPendingReviews: + COLLABORATOR: 0 + # Whether an out-of-date pull request is automatically updated. # It does so by merging its base on top of the head of the pull request. # This is the equivalent of clicking the 'Update branch' button. diff --git a/src/conditions/index.ts b/src/conditions/index.ts index 2e6470530..aa131558d 100644 --- a/src/conditions/index.ts +++ b/src/conditions/index.ts @@ -6,6 +6,7 @@ import blockingChecks from './blockingChecks' import blockingLabels from './blockingLabels' import blockingTitle from './blockingTitle' import maximumChangesRequested from './maximumChangesRequested' +import maximumPendingReviews from './maximumPendingReviews' import mergeable from './mergeable' import minimumApprovals from './minimumApprovals' import requiredAuthorRole from './requiredAuthorRole' @@ -24,6 +25,7 @@ export const conditions = { blockingLabels, blockingTitle, maximumChangesRequested, + maximumPendingReviews, mergeable, minimumApprovals, requiredAuthorRole, diff --git a/src/config.ts b/src/config.ts index 2d6cc0d8f..9c2c26c57 100644 --- a/src/config.ts +++ b/src/config.ts @@ -30,6 +30,7 @@ export type ConditionConfig = { minApprovals: { [key in CommentAuthorAssociation]?: number }, requiredReviewers: string[], maxRequestedChanges: { [key in CommentAuthorAssociation]?: number }, + maxPendingReviews: { [key in CommentAuthorAssociation]?: number }, requiredBaseBranches: Pattern[], blockingBaseBranches: Pattern[], requiredLabels: Pattern[], @@ -57,6 +58,8 @@ export const defaultRuleConfig: ConditionConfig = { maxRequestedChanges: { NONE: 0 }, + maxPendingReviews: { + }, blockingBaseBranches: [], requiredBaseBranches: [], blockingLabels: [], @@ -101,6 +104,7 @@ const conditionConfigDecoder: Decoder = object({ minApprovals: reviewConfigDecover, requiredReviewers: array(string()), maxRequestedChanges: reviewConfigDecover, + maxPendingReviews: reviewConfigDecover, requiredBaseBranches: array(patternDecoder), blockingBaseBranches: array(patternDecoder), requiredLabels: array(patternDecoder), @@ -117,6 +121,7 @@ const configDecoder: Decoder = object({ minApprovals: reviewConfigDecover, requiredReviewers: array(string()), maxRequestedChanges: reviewConfigDecover, + maxPendingReviews: reviewConfigDecover, requiredBaseBranches: array(patternDecoder), blockingBaseBranches: array(patternDecoder), requiredLabels: array(patternDecoder), diff --git a/test/mock.ts b/test/mock.ts index a59298848..9ca0824e9 100644 --- a/test/mock.ts +++ b/test/mock.ts @@ -160,6 +160,16 @@ export function review (options: Partial & { state: PullRequestReviewSta } } +export const pendingReview = (options?: Partial) => + review({ + state: PullRequestReviewState.PENDING, + ...options + }) +export const dismissedReview = (options?: Partial) => + review({ + state: PullRequestReviewState.DISMISSED, + ...options + }) export const approvedReview = (options?: Partial) => review({ state: PullRequestReviewState.APPROVED,