@@ -529,16 +529,18 @@ type mergeChecker struct {
529529 ghc githubClient
530530
531531 sync.Mutex
532- cache map [config.OrgRepo ]map [types.PullRequestMergeType ]bool
532+ cache map [config.OrgRepo ]map [types.PullRequestMergeType ]bool
533+ branchProtectionReviewsCache map [string ]bool
533534}
534535
535536// newMergeChecker creates a mergeChecker for GitHub, and should be used only by
536537// GitHubProvider.
537538func newMergeChecker (cfg config.Getter , ghc githubClient ) * mergeChecker {
538539 m := & mergeChecker {
539- config : cfg ,
540- ghc : ghc ,
541- cache : map [config.OrgRepo ]map [types.PullRequestMergeType ]bool {},
540+ config : cfg ,
541+ ghc : ghc ,
542+ cache : map [config.OrgRepo ]map [types.PullRequestMergeType ]bool {},
543+ branchProtectionReviewsCache : map [string ]bool {},
542544 }
543545
544546 go m .clearCache ()
@@ -554,6 +556,7 @@ func (m *mergeChecker) clearCache() {
554556 <- ticker .C
555557 m .Lock ()
556558 m .cache = make (map [config.OrgRepo ]map [types.PullRequestMergeType ]bool )
559+ m .branchProtectionReviewsCache = map [string ]bool {}
557560 m .Unlock ()
558561 }
559562}
@@ -588,6 +591,36 @@ func (m *mergeChecker) repoMethods(orgRepo config.OrgRepo) (map[types.PullReques
588591 return repoMethods , nil
589592}
590593
594+ // branchRequiresReviews checks if branch protection requires pull request reviews.
595+ func (m * mergeChecker ) branchRequiresReviews (org , repo , branch string ) (bool , error ) {
596+ cacheKey := fmt .Sprintf ("%s/%s:%s" , org , repo , branch )
597+ m .Lock ()
598+ cached , ok := m .branchProtectionReviewsCache [cacheKey ]
599+ m .Unlock ()
600+ if ok {
601+ return cached , nil
602+ }
603+
604+ bp , err := m .ghc .GetBranchProtection (org , repo , branch )
605+ if err != nil {
606+ // If branch protection is not configured or we can't access it,
607+ // assume reviews are not required (GitHub's default).
608+ // Cache the result to avoid repeated API calls for the same branch.
609+ m .Lock ()
610+ m .branchProtectionReviewsCache [cacheKey ] = false
611+ m .Unlock ()
612+ return false , nil
613+ }
614+
615+ requiresReviews := bp .RequiredPullRequestReviews != nil &&
616+ bp .RequiredPullRequestReviews .RequiredApprovingReviewCount > 0
617+
618+ m .Lock ()
619+ m .branchProtectionReviewsCache [cacheKey ] = requiresReviews
620+ m .Unlock ()
621+ return requiresReviews , nil
622+ }
623+
591624// isAllowed checks if a PR does not have merge conflicts and requests an
592625// allowed merge method. If there is no error it returns a string explanation if
593626// not allowed or "" if allowed.
@@ -621,6 +654,19 @@ func (m *mergeChecker) isAllowedToMerge(crc *CodeReviewCommon) (string, error) {
621654 } else if ! allowed {
622655 return fmt .Sprintf ("Merge type %q disallowed by repo settings" , * mergeMethod ), nil
623656 }
657+ // Check if PR has "changes requested" review state and branch protection requires reviews.
658+ if pr .ReviewDecision == githubql .PullRequestReviewDecisionChangesRequested {
659+ requiresReviews , err := m .branchRequiresReviews (crc .Org , crc .Repo , crc .BaseRefName )
660+ if err != nil {
661+ logrus .WithError (err ).WithFields (logrus.Fields {
662+ "org" : crc .Org ,
663+ "repo" : crc .Repo ,
664+ "branch" : crc .BaseRefName ,
665+ }).Debug ("Error checking if branch requires reviews, allowing GitHub to enforce" )
666+ } else if requiresReviews {
667+ return "PR has changes requested in reviews and branch protection requires reviews" , nil
668+ }
669+ }
624670 return "" , nil
625671}
626672
0 commit comments