mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 12:53:43 +01:00 
			
		
		
		
	Merge f8cba0b64f151340a93184106b78e79921e6e867 into 8aa1179ce45daff794ffb3754bdc3c828b394262
This commit is contained in:
		
						commit
						52bedce280
					
				@ -56,6 +56,7 @@ type ProtectedBranch struct {
 | 
				
			|||||||
	RequiredApprovals             int64    `xorm:"NOT NULL DEFAULT 0"`
 | 
						RequiredApprovals             int64    `xorm:"NOT NULL DEFAULT 0"`
 | 
				
			||||||
	BlockOnRejectedReviews        bool     `xorm:"NOT NULL DEFAULT false"`
 | 
						BlockOnRejectedReviews        bool     `xorm:"NOT NULL DEFAULT false"`
 | 
				
			||||||
	BlockOnOfficialReviewRequests bool     `xorm:"NOT NULL DEFAULT false"`
 | 
						BlockOnOfficialReviewRequests bool     `xorm:"NOT NULL DEFAULT false"`
 | 
				
			||||||
 | 
						BlockOnCodeownerReviews       bool     `xorm:"NOT NULL DEFAULT false"`
 | 
				
			||||||
	BlockOnOutdatedBranch         bool     `xorm:"NOT NULL DEFAULT false"`
 | 
						BlockOnOutdatedBranch         bool     `xorm:"NOT NULL DEFAULT false"`
 | 
				
			||||||
	DismissStaleApprovals         bool     `xorm:"NOT NULL DEFAULT false"`
 | 
						DismissStaleApprovals         bool     `xorm:"NOT NULL DEFAULT false"`
 | 
				
			||||||
	IgnoreStaleApprovals          bool     `xorm:"NOT NULL DEFAULT false"`
 | 
						IgnoreStaleApprovals          bool     `xorm:"NOT NULL DEFAULT false"`
 | 
				
			||||||
 | 
				
			|||||||
@ -25,6 +25,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models/migrations/v1_23"
 | 
						"code.gitea.io/gitea/models/migrations/v1_23"
 | 
				
			||||||
	"code.gitea.io/gitea/models/migrations/v1_24"
 | 
						"code.gitea.io/gitea/models/migrations/v1_24"
 | 
				
			||||||
	"code.gitea.io/gitea/models/migrations/v1_25"
 | 
						"code.gitea.io/gitea/models/migrations/v1_25"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/migrations/v1_26"
 | 
				
			||||||
	"code.gitea.io/gitea/models/migrations/v1_6"
 | 
						"code.gitea.io/gitea/models/migrations/v1_6"
 | 
				
			||||||
	"code.gitea.io/gitea/models/migrations/v1_7"
 | 
						"code.gitea.io/gitea/models/migrations/v1_7"
 | 
				
			||||||
	"code.gitea.io/gitea/models/migrations/v1_8"
 | 
						"code.gitea.io/gitea/models/migrations/v1_8"
 | 
				
			||||||
@ -395,6 +396,9 @@ func prepareMigrationTasks() []*migration {
 | 
				
			|||||||
		newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
 | 
							newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
 | 
				
			||||||
		newMigration(322, "Extend comment tree_path length limit", v1_25.ExtendCommentTreePathLength),
 | 
							newMigration(322, "Extend comment tree_path length limit", v1_25.ExtendCommentTreePathLength),
 | 
				
			||||||
		newMigration(323, "Add support for actions concurrency", v1_25.AddActionsConcurrency),
 | 
							newMigration(323, "Add support for actions concurrency", v1_25.AddActionsConcurrency),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Gitea 1.25.0 ends at database version 323
 | 
				
			||||||
 | 
							newMigration(324, "Add block on codeowner reviews branch protection", v1_26.AddBlockOnCodeownerReviews),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return preparedMigrations
 | 
						return preparedMigrations
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								models/migrations/v1_26/v324.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								models/migrations/v1_26/v324.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package v1_26
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"xorm.io/xorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AddBlockOnCodeownerReviews(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						type ProtectedBranch struct {
 | 
				
			||||||
 | 
							BlockOnCodeownerReviews bool `xorm:"NOT NULL DEFAULT false"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return x.Sync(new(ProtectedBranch))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -58,6 +58,7 @@ type BranchProtection struct {
 | 
				
			|||||||
	ApprovalsWhitelistTeams       []string `json:"approvals_whitelist_teams"`
 | 
						ApprovalsWhitelistTeams       []string `json:"approvals_whitelist_teams"`
 | 
				
			||||||
	BlockOnRejectedReviews        bool     `json:"block_on_rejected_reviews"`
 | 
						BlockOnRejectedReviews        bool     `json:"block_on_rejected_reviews"`
 | 
				
			||||||
	BlockOnOfficialReviewRequests bool     `json:"block_on_official_review_requests"`
 | 
						BlockOnOfficialReviewRequests bool     `json:"block_on_official_review_requests"`
 | 
				
			||||||
 | 
						BlockOnCodeownerReviews       bool     `json:"block_on_codeowner_reviews"`
 | 
				
			||||||
	BlockOnOutdatedBranch         bool     `json:"block_on_outdated_branch"`
 | 
						BlockOnOutdatedBranch         bool     `json:"block_on_outdated_branch"`
 | 
				
			||||||
	DismissStaleApprovals         bool     `json:"dismiss_stale_approvals"`
 | 
						DismissStaleApprovals         bool     `json:"dismiss_stale_approvals"`
 | 
				
			||||||
	IgnoreStaleApprovals          bool     `json:"ignore_stale_approvals"`
 | 
						IgnoreStaleApprovals          bool     `json:"ignore_stale_approvals"`
 | 
				
			||||||
@ -98,6 +99,7 @@ type CreateBranchProtectionOption struct {
 | 
				
			|||||||
	ApprovalsWhitelistTeams       []string `json:"approvals_whitelist_teams"`
 | 
						ApprovalsWhitelistTeams       []string `json:"approvals_whitelist_teams"`
 | 
				
			||||||
	BlockOnRejectedReviews        bool     `json:"block_on_rejected_reviews"`
 | 
						BlockOnRejectedReviews        bool     `json:"block_on_rejected_reviews"`
 | 
				
			||||||
	BlockOnOfficialReviewRequests bool     `json:"block_on_official_review_requests"`
 | 
						BlockOnOfficialReviewRequests bool     `json:"block_on_official_review_requests"`
 | 
				
			||||||
 | 
						BlockOnCodeownerReviews       bool     `json:"block_on_codeowner_reviews"`
 | 
				
			||||||
	BlockOnOutdatedBranch         bool     `json:"block_on_outdated_branch"`
 | 
						BlockOnOutdatedBranch         bool     `json:"block_on_outdated_branch"`
 | 
				
			||||||
	DismissStaleApprovals         bool     `json:"dismiss_stale_approvals"`
 | 
						DismissStaleApprovals         bool     `json:"dismiss_stale_approvals"`
 | 
				
			||||||
	IgnoreStaleApprovals          bool     `json:"ignore_stale_approvals"`
 | 
						IgnoreStaleApprovals          bool     `json:"ignore_stale_approvals"`
 | 
				
			||||||
@ -131,6 +133,7 @@ type EditBranchProtectionOption struct {
 | 
				
			|||||||
	ApprovalsWhitelistTeams       []string `json:"approvals_whitelist_teams"`
 | 
						ApprovalsWhitelistTeams       []string `json:"approvals_whitelist_teams"`
 | 
				
			||||||
	BlockOnRejectedReviews        *bool    `json:"block_on_rejected_reviews"`
 | 
						BlockOnRejectedReviews        *bool    `json:"block_on_rejected_reviews"`
 | 
				
			||||||
	BlockOnOfficialReviewRequests *bool    `json:"block_on_official_review_requests"`
 | 
						BlockOnOfficialReviewRequests *bool    `json:"block_on_official_review_requests"`
 | 
				
			||||||
 | 
						BlockOnCodeownerReviews       *bool    `json:"block_on_codeowner_reviews"`
 | 
				
			||||||
	BlockOnOutdatedBranch         *bool    `json:"block_on_outdated_branch"`
 | 
						BlockOnOutdatedBranch         *bool    `json:"block_on_outdated_branch"`
 | 
				
			||||||
	DismissStaleApprovals         *bool    `json:"dismiss_stale_approvals"`
 | 
						DismissStaleApprovals         *bool    `json:"dismiss_stale_approvals"`
 | 
				
			||||||
	IgnoreStaleApprovals          *bool    `json:"ignore_stale_approvals"`
 | 
						IgnoreStaleApprovals          *bool    `json:"ignore_stale_approvals"`
 | 
				
			||||||
 | 
				
			|||||||
@ -1916,6 +1916,7 @@ pulls.required_status_check_administrator = As an administrator, you may still m
 | 
				
			|||||||
pulls.blocked_by_approvals = "This pull request doesn't have enough required approvals yet. %d of %d official approvals granted."
 | 
					pulls.blocked_by_approvals = "This pull request doesn't have enough required approvals yet. %d of %d official approvals granted."
 | 
				
			||||||
pulls.blocked_by_approvals_whitelisted = "This pull request doesn't have enough required approvals yet. %d of %d approvals granted from users or teams on the allowlist."
 | 
					pulls.blocked_by_approvals_whitelisted = "This pull request doesn't have enough required approvals yet. %d of %d approvals granted from users or teams on the allowlist."
 | 
				
			||||||
pulls.blocked_by_rejection = "This pull request has changes requested by an official reviewer."
 | 
					pulls.blocked_by_rejection = "This pull request has changes requested by an official reviewer."
 | 
				
			||||||
 | 
					pulls.blocked_by_codeowners = "This pull request is missing approval from one or more code owners."
 | 
				
			||||||
pulls.blocked_by_official_review_requests = "This pull request has official review requests."
 | 
					pulls.blocked_by_official_review_requests = "This pull request has official review requests."
 | 
				
			||||||
pulls.blocked_by_outdated_branch = "This pull request is blocked because it's outdated."
 | 
					pulls.blocked_by_outdated_branch = "This pull request is blocked because it's outdated."
 | 
				
			||||||
pulls.blocked_by_changed_protected_files_1= "This pull request is blocked because it changes a protected file:"
 | 
					pulls.blocked_by_changed_protected_files_1= "This pull request is blocked because it changes a protected file:"
 | 
				
			||||||
@ -2556,6 +2557,8 @@ settings.block_rejected_reviews = Block merge on rejected reviews
 | 
				
			|||||||
settings.block_rejected_reviews_desc = Merging will not be possible when changes are requested by official reviewers, even if there are enough approvals.
 | 
					settings.block_rejected_reviews_desc = Merging will not be possible when changes are requested by official reviewers, even if there are enough approvals.
 | 
				
			||||||
settings.block_on_official_review_requests = Block merge on official review requests
 | 
					settings.block_on_official_review_requests = Block merge on official review requests
 | 
				
			||||||
settings.block_on_official_review_requests_desc = Merging will not be possible when it has official review requests, even if there are enough approvals.
 | 
					settings.block_on_official_review_requests_desc = Merging will not be possible when it has official review requests, even if there are enough approvals.
 | 
				
			||||||
 | 
					settings.block_on_codeowner_reviews = Require approval from code owners
 | 
				
			||||||
 | 
					settings.block_on_codeowner_reviews_desc = Merging will only be possible if at least one code owner per code owner rule has given an approving review.
 | 
				
			||||||
settings.block_outdated_branch = Block merge if pull request is outdated
 | 
					settings.block_outdated_branch = Block merge if pull request is outdated
 | 
				
			||||||
settings.block_outdated_branch_desc = Merging will not be possible when head branch is behind base branch.
 | 
					settings.block_outdated_branch_desc = Merging will not be possible when head branch is behind base branch.
 | 
				
			||||||
settings.block_admin_merge_override = Administrators must follow branch protection rules
 | 
					settings.block_admin_merge_override = Administrators must follow branch protection rules
 | 
				
			||||||
 | 
				
			|||||||
@ -855,6 +855,10 @@ func EditBranchProtection(ctx *context.APIContext) {
 | 
				
			|||||||
		protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
 | 
							protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if form.BlockOnCodeownerReviews != nil {
 | 
				
			||||||
 | 
							protectBranch.BlockOnCodeownerReviews = *form.BlockOnCodeownerReviews
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if form.DismissStaleApprovals != nil {
 | 
						if form.DismissStaleApprovals != nil {
 | 
				
			||||||
		protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
 | 
							protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -952,6 +952,7 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss
 | 
				
			|||||||
		ctx.Data["ProtectedBranch"] = pb
 | 
							ctx.Data["ProtectedBranch"] = pb
 | 
				
			||||||
		ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pb, pull)
 | 
							ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pb, pull)
 | 
				
			||||||
		ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
 | 
							ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
 | 
				
			||||||
 | 
							ctx.Data["IsBlockedByCodeowners"] = !issue_service.HasAllRequiredCodeownerReviews(ctx, pb, pull)
 | 
				
			||||||
		ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull)
 | 
							ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull)
 | 
				
			||||||
		ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pb, pull)
 | 
							ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pb, pull)
 | 
				
			||||||
		ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pb, pull)
 | 
							ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pb, pull)
 | 
				
			||||||
 | 
				
			|||||||
@ -254,6 +254,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
 | 
						protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
 | 
				
			||||||
	protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
 | 
						protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
 | 
				
			||||||
 | 
						protectBranch.BlockOnCodeownerReviews = f.BlockOnCodeownerReviews
 | 
				
			||||||
	protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
 | 
						protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
 | 
				
			||||||
	protectBranch.IgnoreStaleApprovals = f.IgnoreStaleApprovals
 | 
						protectBranch.IgnoreStaleApprovals = f.IgnoreStaleApprovals
 | 
				
			||||||
	protectBranch.RequireSignedCommits = f.RequireSignedCommits
 | 
						protectBranch.RequireSignedCommits = f.RequireSignedCommits
 | 
				
			||||||
 | 
				
			|||||||
@ -189,6 +189,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo
 | 
				
			|||||||
		ApprovalsWhitelistTeams:       approvalsWhitelistTeams,
 | 
							ApprovalsWhitelistTeams:       approvalsWhitelistTeams,
 | 
				
			||||||
		BlockOnRejectedReviews:        bp.BlockOnRejectedReviews,
 | 
							BlockOnRejectedReviews:        bp.BlockOnRejectedReviews,
 | 
				
			||||||
		BlockOnOfficialReviewRequests: bp.BlockOnOfficialReviewRequests,
 | 
							BlockOnOfficialReviewRequests: bp.BlockOnOfficialReviewRequests,
 | 
				
			||||||
 | 
							BlockOnCodeownerReviews:       bp.BlockOnCodeownerReviews,
 | 
				
			||||||
		BlockOnOutdatedBranch:         bp.BlockOnOutdatedBranch,
 | 
							BlockOnOutdatedBranch:         bp.BlockOnOutdatedBranch,
 | 
				
			||||||
		DismissStaleApprovals:         bp.DismissStaleApprovals,
 | 
							DismissStaleApprovals:         bp.DismissStaleApprovals,
 | 
				
			||||||
		IgnoreStaleApprovals:          bp.IgnoreStaleApprovals,
 | 
							IgnoreStaleApprovals:          bp.IgnoreStaleApprovals,
 | 
				
			||||||
 | 
				
			|||||||
@ -184,6 +184,7 @@ type ProtectBranchForm struct {
 | 
				
			|||||||
	ApprovalsWhitelistTeams       string
 | 
						ApprovalsWhitelistTeams       string
 | 
				
			||||||
	BlockOnRejectedReviews        bool
 | 
						BlockOnRejectedReviews        bool
 | 
				
			||||||
	BlockOnOfficialReviewRequests bool
 | 
						BlockOnOfficialReviewRequests bool
 | 
				
			||||||
 | 
						BlockOnCodeownerReviews       bool
 | 
				
			||||||
	BlockOnOutdatedBranch         bool
 | 
						BlockOnOutdatedBranch         bool
 | 
				
			||||||
	DismissStaleApprovals         bool
 | 
						DismissStaleApprovals         bool
 | 
				
			||||||
	IgnoreStaleApprovals          bool
 | 
						IgnoreStaleApprovals          bool
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import (
 | 
				
			|||||||
	"slices"
 | 
						"slices"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
						issues_model "code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	org_model "code.gitea.io/gitea/models/organization"
 | 
						org_model "code.gitea.io/gitea/models/organization"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
@ -49,6 +50,129 @@ func IsCodeOwnerFile(f string) bool {
 | 
				
			|||||||
	return slices.Contains(codeOwnerFiles, f)
 | 
						return slices.Contains(codeOwnerFiles, f)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func HasAllRequiredCodeownerReviews(ctx context.Context, pb *git_model.ProtectedBranch, pr *issues_model.PullRequest) bool {
 | 
				
			||||||
 | 
						if !pb.BlockOnCodeownerReviews {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := pr.LoadHeadRepo(ctx); err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := pr.LoadBaseRepo(ctx); err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := pr.LoadIssue(ctx); err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pr.Issue.Repo = pr.BaseRepo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if pr.BaseRepo.IsFork {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						repo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer repo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var data string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, file := range codeOwnerFiles {
 | 
				
			||||||
 | 
							if blob, err := commit.GetBlobByPath(file); err == nil {
 | 
				
			||||||
 | 
								data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// no code owner file = no one to approve
 | 
				
			||||||
 | 
						if data == "" {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rules, _ := issues_model.GetCodeOwnersFromContent(ctx, data)
 | 
				
			||||||
 | 
						if len(rules) == 0 {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// get the mergebase
 | 
				
			||||||
 | 
						mergeBase, err := getMergeBase(ctx, pr.BaseRepo, repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// https://github.com/go-gitea/gitea/issues/29763, we need to get the files changed
 | 
				
			||||||
 | 
						// between the merge base and the head commit but not the base branch and the head commit
 | 
				
			||||||
 | 
						changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.GetGitHeadRefName())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						approvingReviews, err := issues_model.FindLatestReviews(ctx, issues_model.FindReviewOptions{
 | 
				
			||||||
 | 
							Types:   []issues_model.ReviewType{issues_model.ReviewTypeApprove},
 | 
				
			||||||
 | 
							IssueID: pr.IssueID,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hasApprovals := true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						matchingRules := make([]*issues_model.CodeOwnerRule, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, rule := range rules {
 | 
				
			||||||
 | 
							for _, f := range changedFiles {
 | 
				
			||||||
 | 
								if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) {
 | 
				
			||||||
 | 
									matchingRules = append(matchingRules, rule)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, rule := range matchingRules {
 | 
				
			||||||
 | 
							ruleReviewers := slices.Clone(rule.Users)
 | 
				
			||||||
 | 
							for _, t := range rule.Teams {
 | 
				
			||||||
 | 
								if err := t.LoadMembers(ctx); err != nil {
 | 
				
			||||||
 | 
									return false
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ruleReviewers = slices.AppendSeq(ruleReviewers, slices.Values(t.Members))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// we need at least 1 code owner that isn't the PR author
 | 
				
			||||||
 | 
							hasPotentialReviewers := slices.ContainsFunc(ruleReviewers, func(elem *user_model.User) bool { return elem.ID != pr.Issue.PosterID })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !hasPotentialReviewers {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// then we need at least 1 approving review from any valid code owner for this rule
 | 
				
			||||||
 | 
							hasRuleApproval := slices.ContainsFunc(ruleReviewers, func(elem *user_model.User) bool {
 | 
				
			||||||
 | 
								return slices.ContainsFunc(approvingReviews, func(review *issues_model.Review) bool {
 | 
				
			||||||
 | 
									return review.ReviewerID == elem.ID
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !hasRuleApproval {
 | 
				
			||||||
 | 
								hasApprovals = false
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return hasApprovals
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
 | 
					func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
 | 
				
			||||||
	if err := pr.LoadIssue(ctx); err != nil {
 | 
						if err := pr.LoadIssue(ctx); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
 | 
				
			|||||||
@ -598,6 +598,10 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques
 | 
				
			|||||||
		return util.ErrorWrap(ErrNotReadyToMerge, "The head branch is behind the base branch")
 | 
							return util.ErrorWrap(ErrNotReadyToMerge, "The head branch is behind the base branch")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !issue_service.HasAllRequiredCodeownerReviews(ctx, pb, pr) {
 | 
				
			||||||
 | 
							return util.ErrorWrap(ErrNotReadyToMerge, "There are missing code owner reviews.")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if skipProtectedFilesCheck {
 | 
						if skipProtectedFilesCheck {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@
 | 
				
			|||||||
	{{- else if .IsBlockedByApprovals}}red
 | 
						{{- else if .IsBlockedByApprovals}}red
 | 
				
			||||||
	{{- else if .IsBlockedByRejection}}red
 | 
						{{- else if .IsBlockedByRejection}}red
 | 
				
			||||||
	{{- else if .IsBlockedByOfficialReviewRequests}}red
 | 
						{{- else if .IsBlockedByOfficialReviewRequests}}red
 | 
				
			||||||
 | 
						{{- else if .IsBlockedByCodeowners}}red
 | 
				
			||||||
	{{- else if .IsBlockedByOutdatedBranch}}red
 | 
						{{- else if .IsBlockedByOutdatedBranch}}red
 | 
				
			||||||
	{{- else if .IsBlockedByChangedProtectedFiles}}red
 | 
						{{- else if .IsBlockedByChangedProtectedFiles}}red
 | 
				
			||||||
	{{- else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsFailure .RequiredStatusCheckState.IsError)}}red
 | 
						{{- else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsFailure .RequiredStatusCheckState.IsError)}}red
 | 
				
			||||||
@ -130,6 +131,11 @@
 | 
				
			|||||||
						{{svg "octicon-x"}}
 | 
											{{svg "octicon-x"}}
 | 
				
			||||||
					{{ctx.Locale.Tr "repo.pulls.blocked_by_official_review_requests"}}
 | 
										{{ctx.Locale.Tr "repo.pulls.blocked_by_official_review_requests"}}
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
 | 
									{{else if .IsBlockedByCodeowners}}
 | 
				
			||||||
 | 
										<div class="item">
 | 
				
			||||||
 | 
											{{svg "octicon-x"}}
 | 
				
			||||||
 | 
										{{ctx.Locale.Tr "repo.pulls.blocked_by_codeowners"}}
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
				{{else if .IsBlockedByOutdatedBranch}}
 | 
									{{else if .IsBlockedByOutdatedBranch}}
 | 
				
			||||||
					<div class="item">
 | 
										<div class="item">
 | 
				
			||||||
						{{svg "octicon-x"}}
 | 
											{{svg "octicon-x"}}
 | 
				
			||||||
@ -166,7 +172,7 @@
 | 
				
			|||||||
					</div>
 | 
										</div>
 | 
				
			||||||
				{{end}}
 | 
									{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
 | 
									{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByCodeowners .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				{{/* admin can merge without checks, writer can merge when checks succeed */}}
 | 
									{{/* admin can merge without checks, writer can merge when checks succeed */}}
 | 
				
			||||||
				{{$canMergeNow := and (or (and (not $.ProtectedBranch.BlockAdminMergeOverride) $.IsRepoAdmin) (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
 | 
									{{$canMergeNow := and (or (and (not $.ProtectedBranch.BlockAdminMergeOverride) $.IsRepoAdmin) (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
 | 
				
			||||||
@ -336,6 +342,11 @@
 | 
				
			|||||||
						{{svg "octicon-x"}}
 | 
											{{svg "octicon-x"}}
 | 
				
			||||||
						{{ctx.Locale.Tr "repo.pulls.blocked_by_official_review_requests"}}
 | 
											{{ctx.Locale.Tr "repo.pulls.blocked_by_official_review_requests"}}
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
 | 
										{{else if .IsBlockedByCodeowners}}
 | 
				
			||||||
 | 
										<div class="item text red">
 | 
				
			||||||
 | 
											{{svg "octicon-x"}}
 | 
				
			||||||
 | 
											{{ctx.Locale.Tr "repo.pulls.blocked_by_codeowners"}}
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
				{{else if .IsBlockedByOutdatedBranch}}
 | 
									{{else if .IsBlockedByOutdatedBranch}}
 | 
				
			||||||
					<div class="item text red">
 | 
										<div class="item text red">
 | 
				
			||||||
						{{svg "octicon-x"}}
 | 
											{{svg "octicon-x"}}
 | 
				
			||||||
 | 
				
			|||||||
@ -320,6 +320,13 @@
 | 
				
			|||||||
						<p class="help">{{ctx.Locale.Tr "repo.settings.block_on_official_review_requests_desc"}}</p>
 | 
											<p class="help">{{ctx.Locale.Tr "repo.settings.block_on_official_review_requests_desc"}}</p>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
									<div class="field">
 | 
				
			||||||
 | 
										<div class="ui checkbox">
 | 
				
			||||||
 | 
											<input name="block_on_codeowner_reviews" type="checkbox" {{if .Rule.BlockOnCodeownerReviews}}checked{{end}}>
 | 
				
			||||||
 | 
											<label>{{ctx.Locale.Tr "repo.settings.block_on_codeowner_reviews"}}</label>
 | 
				
			||||||
 | 
											<p class="help">{{ctx.Locale.Tr "repo.settings.block_on_codeowner_reviews_desc"}}</p>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
				<div class="field">
 | 
									<div class="field">
 | 
				
			||||||
					<div class="ui checkbox">
 | 
										<div class="ui checkbox">
 | 
				
			||||||
						<input name="block_on_outdated_branch" type="checkbox" {{if .Rule.BlockOnOutdatedBranch}}checked{{end}}>
 | 
											<input name="block_on_outdated_branch" type="checkbox" {{if .Rule.BlockOnOutdatedBranch}}checked{{end}}>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							@ -21850,6 +21850,10 @@
 | 
				
			|||||||
          "type": "boolean",
 | 
					          "type": "boolean",
 | 
				
			||||||
          "x-go-name": "BlockAdminMergeOverride"
 | 
					          "x-go-name": "BlockAdminMergeOverride"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "block_on_codeowner_reviews": {
 | 
				
			||||||
 | 
					          "type": "boolean",
 | 
				
			||||||
 | 
					          "x-go-name": "BlockOnCodeownerReviews"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "block_on_official_review_requests": {
 | 
					        "block_on_official_review_requests": {
 | 
				
			||||||
          "type": "boolean",
 | 
					          "type": "boolean",
 | 
				
			||||||
          "x-go-name": "BlockOnOfficialReviewRequests"
 | 
					          "x-go-name": "BlockOnOfficialReviewRequests"
 | 
				
			||||||
@ -22706,6 +22710,10 @@
 | 
				
			|||||||
          "type": "boolean",
 | 
					          "type": "boolean",
 | 
				
			||||||
          "x-go-name": "BlockAdminMergeOverride"
 | 
					          "x-go-name": "BlockAdminMergeOverride"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "block_on_codeowner_reviews": {
 | 
				
			||||||
 | 
					          "type": "boolean",
 | 
				
			||||||
 | 
					          "x-go-name": "BlockOnCodeownerReviews"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "block_on_official_review_requests": {
 | 
					        "block_on_official_review_requests": {
 | 
				
			||||||
          "type": "boolean",
 | 
					          "type": "boolean",
 | 
				
			||||||
          "x-go-name": "BlockOnOfficialReviewRequests"
 | 
					          "x-go-name": "BlockOnOfficialReviewRequests"
 | 
				
			||||||
@ -24056,6 +24064,10 @@
 | 
				
			|||||||
          "type": "boolean",
 | 
					          "type": "boolean",
 | 
				
			||||||
          "x-go-name": "BlockAdminMergeOverride"
 | 
					          "x-go-name": "BlockAdminMergeOverride"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "block_on_codeowner_reviews": {
 | 
				
			||||||
 | 
					          "type": "boolean",
 | 
				
			||||||
 | 
					          "x-go-name": "BlockOnCodeownerReviews"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "block_on_official_review_requests": {
 | 
					        "block_on_official_review_requests": {
 | 
				
			||||||
          "type": "boolean",
 | 
					          "type": "boolean",
 | 
				
			||||||
          "x-go-name": "BlockOnOfficialReviewRequests"
 | 
					          "x-go-name": "BlockOnOfficialReviewRequests"
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
						issues_model "code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unittest"
 | 
						"code.gitea.io/gitea/models/unittest"
 | 
				
			||||||
@ -19,6 +20,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/test"
 | 
						"code.gitea.io/gitea/modules/test"
 | 
				
			||||||
	issue_service "code.gitea.io/gitea/services/issue"
 | 
						issue_service "code.gitea.io/gitea/services/issue"
 | 
				
			||||||
 | 
						pull_service "code.gitea.io/gitea/services/pull"
 | 
				
			||||||
	repo_service "code.gitea.io/gitea/services/repository"
 | 
						repo_service "code.gitea.io/gitea/services/repository"
 | 
				
			||||||
	files_service "code.gitea.io/gitea/services/repository/files"
 | 
						files_service "code.gitea.io/gitea/services/repository/files"
 | 
				
			||||||
	"code.gitea.io/gitea/tests"
 | 
						"code.gitea.io/gitea/tests"
 | 
				
			||||||
@ -49,6 +51,8 @@ func TestPullView_ReviewerMissed(t *testing.T) {
 | 
				
			|||||||
func TestPullView_CodeOwner(t *testing.T) {
 | 
					func TestPullView_CodeOwner(t *testing.T) {
 | 
				
			||||||
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | 
						onGiteaRun(t, func(t *testing.T, u *url.URL) {
 | 
				
			||||||
		user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
							user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
				
			||||||
 | 
							user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
 | 
				
			||||||
 | 
							user8 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 8})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Create the repo.
 | 
							// Create the repo.
 | 
				
			||||||
		repo, err := repo_service.CreateRepositoryDirectly(t.Context(), user2, user2, repo_service.CreateRepoOptions{
 | 
							repo, err := repo_service.CreateRepositoryDirectly(t.Context(), user2, user2, repo_service.CreateRepoOptions{
 | 
				
			||||||
@ -60,6 +64,17 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
				
			|||||||
		}, true)
 | 
							}, true)
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// create code owner branch protection
 | 
				
			||||||
 | 
							protectBranch := git_model.ProtectedBranch{
 | 
				
			||||||
 | 
								BlockOnCodeownerReviews: true,
 | 
				
			||||||
 | 
								RepoID:                  repo.ID,
 | 
				
			||||||
 | 
								RuleName:                "master",
 | 
				
			||||||
 | 
								CanPush:                 true,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = pull_service.CreateOrUpdateProtectedBranch(t.Context(), repo, &protectBranch, git_model.WhitelistOptions{})
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// add CODEOWNERS to default branch
 | 
							// add CODEOWNERS to default branch
 | 
				
			||||||
		_, err = files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
							_, err = files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
				
			||||||
			OldBranch: repo.DefaultBranch,
 | 
								OldBranch: repo.DefaultBranch,
 | 
				
			||||||
@ -96,7 +111,7 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
				
			|||||||
			assert.NoError(t, pr.LoadIssue(t.Context()))
 | 
								assert.NoError(t, pr.LoadIssue(t.Context()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// update the file on the pr branch
 | 
								// update the file on the pr branch
 | 
				
			||||||
			_, err = files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
								resp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
				
			||||||
				OldBranch: "codeowner-basebranch",
 | 
									OldBranch: "codeowner-basebranch",
 | 
				
			||||||
				Files: []*files_service.ChangeRepoFile{
 | 
									Files: []*files_service.ChangeRepoFile{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
@ -124,6 +139,22 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
				
			|||||||
			prUpdated2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
 | 
								prUpdated2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
 | 
				
			||||||
			assert.NoError(t, prUpdated2.LoadIssue(t.Context()))
 | 
								assert.NoError(t, prUpdated2.LoadIssue(t.Context()))
 | 
				
			||||||
			assert.Equal(t, "Test Pull Request2", prUpdated2.Issue.Title)
 | 
								assert.Equal(t, "Test Pull Request2", prUpdated2.Issue.Title)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// ensure it cannot be merged
 | 
				
			||||||
 | 
								hasCodeownerReviews := issue_service.HasAllRequiredCodeownerReviews(t.Context(), &protectBranch, pr)
 | 
				
			||||||
 | 
								assert.False(t, hasCodeownerReviews)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								issues_model.SubmitReview(t.Context(), user5, pr.Issue, issues_model.ReviewTypeApprove, "Very good", resp.Commit.SHA, false, make([]string, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// should still fail (we also need user8)
 | 
				
			||||||
 | 
								hasCodeownerReviews = issue_service.HasAllRequiredCodeownerReviews(t.Context(), &protectBranch, pr)
 | 
				
			||||||
 | 
								assert.False(t, hasCodeownerReviews)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								issues_model.SubmitReview(t.Context(), user8, pr.Issue, issues_model.ReviewTypeApprove, "Very good", resp.Commit.SHA, false, make([]string, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// now we should be able to merge
 | 
				
			||||||
 | 
								hasCodeownerReviews = issue_service.HasAllRequiredCodeownerReviews(t.Context(), &protectBranch, pr)
 | 
				
			||||||
 | 
								assert.True(t, hasCodeownerReviews)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// change the default branch CODEOWNERS file to change README.md's codeowner
 | 
							// change the default branch CODEOWNERS file to change README.md's codeowner
 | 
				
			||||||
@ -140,7 +171,7 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		t.Run("Second Pull Request", func(t *testing.T) {
 | 
							t.Run("Second Pull Request", func(t *testing.T) {
 | 
				
			||||||
			// create a new branch to prepare for pull request
 | 
								// create a new branch to prepare for pull request
 | 
				
			||||||
			_, err = files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
								resp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{
 | 
				
			||||||
				NewBranch: "codeowner-basebranch2",
 | 
									NewBranch: "codeowner-basebranch2",
 | 
				
			||||||
				Files: []*files_service.ChangeRepoFile{
 | 
									Files: []*files_service.ChangeRepoFile{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
@ -158,6 +189,15 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch2"})
 | 
								pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch2"})
 | 
				
			||||||
			unittest.AssertExistsAndLoadBean(t, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
 | 
								unittest.AssertExistsAndLoadBean(t, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// should need user8 approval only now
 | 
				
			||||||
 | 
								hasCodeownerReviews := issue_service.HasAllRequiredCodeownerReviews(t.Context(), &protectBranch, pr)
 | 
				
			||||||
 | 
								assert.False(t, hasCodeownerReviews)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								issues_model.SubmitReview(t.Context(), user8, pr.Issue, issues_model.ReviewTypeApprove, "Very good", resp.Commit.SHA, false, make([]string, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								hasCodeownerReviews = issue_service.HasAllRequiredCodeownerReviews(t.Context(), &protectBranch, pr)
 | 
				
			||||||
 | 
								assert.True(t, hasCodeownerReviews)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		t.Run("Forked Repo Pull Request", func(t *testing.T) {
 | 
							t.Run("Forked Repo Pull Request", func(t *testing.T) {
 | 
				
			||||||
@ -169,7 +209,7 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
				
			|||||||
			assert.NoError(t, err)
 | 
								assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// create a new branch to prepare for pull request
 | 
								// create a new branch to prepare for pull request
 | 
				
			||||||
			_, err = files_service.ChangeRepoFiles(t.Context(), forkedRepo, user5, &files_service.ChangeRepoFilesOptions{
 | 
								resp, err := files_service.ChangeRepoFiles(t.Context(), forkedRepo, user5, &files_service.ChangeRepoFilesOptions{
 | 
				
			||||||
				NewBranch: "codeowner-basebranch-forked",
 | 
									NewBranch: "codeowner-basebranch-forked",
 | 
				
			||||||
				Files: []*files_service.ChangeRepoFile{
 | 
									Files: []*files_service.ChangeRepoFile{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
@ -210,6 +250,21 @@ func TestPullView_CodeOwner(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadRepoID: forkedRepo.ID, HeadBranch: "codeowner-basebranch-forked"})
 | 
								pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadRepoID: forkedRepo.ID, HeadBranch: "codeowner-basebranch-forked"})
 | 
				
			||||||
			unittest.AssertExistsAndLoadBean(t, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
 | 
								unittest.AssertExistsAndLoadBean(t, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// will also need user8 for this
 | 
				
			||||||
 | 
								hasCodeownerReviews := issue_service.HasAllRequiredCodeownerReviews(t.Context(), &protectBranch, pr)
 | 
				
			||||||
 | 
								assert.False(t, hasCodeownerReviews)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								issues_model.SubmitReview(t.Context(), user5, pr.Issue, issues_model.ReviewTypeApprove, "Very good", resp.Commit.SHA, false, make([]string, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// should still fail (user5 is not a code owner for this PR)
 | 
				
			||||||
 | 
								hasCodeownerReviews = issue_service.HasAllRequiredCodeownerReviews(t.Context(), &protectBranch, pr)
 | 
				
			||||||
 | 
								assert.False(t, hasCodeownerReviews)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								issues_model.SubmitReview(t.Context(), user8, pr.Issue, issues_model.ReviewTypeApprove, "Very good", resp.Commit.SHA, false, make([]string, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								hasCodeownerReviews = issue_service.HasAllRequiredCodeownerReviews(t.Context(), &protectBranch, pr)
 | 
				
			||||||
 | 
								assert.True(t, hasCodeownerReviews)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user