Add block on official review requests branch protection (#13705)

Signed-off-by: a1012112796 <1012112796@qq.com>

Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
a1012112796 2020-11-29 03:30:46 +08:00 committed by GitHub
parent 7ed5bf8cbe
commit 9c26dc1f3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 228 additions and 141 deletions

View file

@ -21,28 +21,29 @@ import (
// ProtectedBranch struct // ProtectedBranch struct
type ProtectedBranch struct { type ProtectedBranch struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"` RepoID int64 `xorm:"UNIQUE(s)"`
BranchName string `xorm:"UNIQUE(s)"` BranchName string `xorm:"UNIQUE(s)"`
CanPush bool `xorm:"NOT NULL DEFAULT false"` CanPush bool `xorm:"NOT NULL DEFAULT false"`
EnableWhitelist bool EnableWhitelist bool
WhitelistUserIDs []int64 `xorm:"JSON TEXT"` WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"` WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"` EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"` WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"` MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
StatusCheckContexts []string `xorm:"JSON TEXT"` StatusCheckContexts []string `xorm:"JSON TEXT"`
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"` EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"` ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
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"`
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"` BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"` BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"` DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
ProtectedFilePatterns string `xorm:"TEXT"` RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
ProtectedFilePatterns string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"created"` CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"` UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
@ -171,13 +172,12 @@ func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest)
} }
// MergeBlockedByRejectedReview returns true if merge is blocked by rejected reviews // MergeBlockedByRejectedReview returns true if merge is blocked by rejected reviews
// An official ReviewRequest should also block Merge like Reject
func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(pr *PullRequest) bool { func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(pr *PullRequest) bool {
if !protectBranch.BlockOnRejectedReviews { if !protectBranch.BlockOnRejectedReviews {
return false return false
} }
rejectExist, err := x.Where("issue_id = ?", pr.IssueID). rejectExist, err := x.Where("issue_id = ?", pr.IssueID).
And("type in ( ?, ?)", ReviewTypeReject, ReviewTypeRequest). And("type = ?", ReviewTypeReject).
And("official = ?", true). And("official = ?", true).
Exist(new(Review)) Exist(new(Review))
if err != nil { if err != nil {
@ -188,6 +188,24 @@ func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(pr *PullReque
return rejectExist return rejectExist
} }
// MergeBlockedByOfficialReviewRequests block merge because of some review request to official reviewer
// of from official review
func (protectBranch *ProtectedBranch) MergeBlockedByOfficialReviewRequests(pr *PullRequest) bool {
if !protectBranch.BlockOnOfficialReviewRequests {
return false
}
has, err := x.Where("issue_id = ?", pr.IssueID).
And("type = ?", ReviewTypeRequest).
And("official = ?", true).
Exist(new(Review))
if err != nil {
log.Error("MergeBlockedByOfficialReviewRequests: %v", err)
return true
}
return has
}
// MergeBlockedByOutdatedBranch returns true if merge is blocked by an outdated head branch // MergeBlockedByOutdatedBranch returns true if merge is blocked by an outdated head branch
func (protectBranch *ProtectedBranch) MergeBlockedByOutdatedBranch(pr *PullRequest) bool { func (protectBranch *ProtectedBranch) MergeBlockedByOutdatedBranch(pr *PullRequest) bool {
return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0 return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0

View file

@ -254,6 +254,8 @@ var migrations = []Migration{
NewMigration("code comment replies should have the commitID of the review they are replying to", updateCodeCommentReplies), NewMigration("code comment replies should have the commitID of the review they are replying to", updateCodeCommentReplies),
// v159 -> v160 // v159 -> v160
NewMigration("update reactions constraint", updateReactionConstraint), NewMigration("update reactions constraint", updateReactionConstraint),
// v160 -> v161
NewMigration("Add block on official review requests branch protection", addBlockOnOfficialReviewRequests),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

17
models/migrations/v160.go Normal file
View file

@ -0,0 +1,17 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"xorm.io/xorm"
)
func addBlockOnOfficialReviewRequests(x *xorm.Engine) error {
type ProtectedBranch struct {
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync2(new(ProtectedBranch))
}

View file

@ -178,25 +178,26 @@ func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
// ProtectBranchForm form for changing protected branch settings // ProtectBranchForm form for changing protected branch settings
type ProtectBranchForm struct { type ProtectBranchForm struct {
Protected bool Protected bool
EnablePush string EnablePush string
WhitelistUsers string WhitelistUsers string
WhitelistTeams string WhitelistTeams string
WhitelistDeployKeys bool WhitelistDeployKeys bool
EnableMergeWhitelist bool EnableMergeWhitelist bool
MergeWhitelistUsers string MergeWhitelistUsers string
MergeWhitelistTeams string MergeWhitelistTeams string
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` EnableStatusCheck bool
StatusCheckContexts []string StatusCheckContexts []string
RequiredApprovals int64 RequiredApprovals int64
EnableApprovalsWhitelist bool EnableApprovalsWhitelist bool
ApprovalsWhitelistUsers string ApprovalsWhitelistUsers string
ApprovalsWhitelistTeams string ApprovalsWhitelistTeams string
BlockOnRejectedReviews bool BlockOnRejectedReviews bool
BlockOnOutdatedBranch bool BlockOnOfficialReviewRequests bool
DismissStaleApprovals bool BlockOnOutdatedBranch bool
RequireSignedCommits bool DismissStaleApprovals bool
ProtectedFilePatterns string RequireSignedCommits bool
ProtectedFilePatterns string
} }
// Validate validates the fields // Validate validates the fields

View file

@ -105,28 +105,29 @@ func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection {
} }
return &api.BranchProtection{ return &api.BranchProtection{
BranchName: bp.BranchName, BranchName: bp.BranchName,
EnablePush: bp.CanPush, EnablePush: bp.CanPush,
EnablePushWhitelist: bp.EnableWhitelist, EnablePushWhitelist: bp.EnableWhitelist,
PushWhitelistUsernames: pushWhitelistUsernames, PushWhitelistUsernames: pushWhitelistUsernames,
PushWhitelistTeams: pushWhitelistTeams, PushWhitelistTeams: pushWhitelistTeams,
PushWhitelistDeployKeys: bp.WhitelistDeployKeys, PushWhitelistDeployKeys: bp.WhitelistDeployKeys,
EnableMergeWhitelist: bp.EnableMergeWhitelist, EnableMergeWhitelist: bp.EnableMergeWhitelist,
MergeWhitelistUsernames: mergeWhitelistUsernames, MergeWhitelistUsernames: mergeWhitelistUsernames,
MergeWhitelistTeams: mergeWhitelistTeams, MergeWhitelistTeams: mergeWhitelistTeams,
EnableStatusCheck: bp.EnableStatusCheck, EnableStatusCheck: bp.EnableStatusCheck,
StatusCheckContexts: bp.StatusCheckContexts, StatusCheckContexts: bp.StatusCheckContexts,
RequiredApprovals: bp.RequiredApprovals, RequiredApprovals: bp.RequiredApprovals,
EnableApprovalsWhitelist: bp.EnableApprovalsWhitelist, EnableApprovalsWhitelist: bp.EnableApprovalsWhitelist,
ApprovalsWhitelistUsernames: approvalsWhitelistUsernames, ApprovalsWhitelistUsernames: approvalsWhitelistUsernames,
ApprovalsWhitelistTeams: approvalsWhitelistTeams, ApprovalsWhitelistTeams: approvalsWhitelistTeams,
BlockOnRejectedReviews: bp.BlockOnRejectedReviews, BlockOnRejectedReviews: bp.BlockOnRejectedReviews,
BlockOnOutdatedBranch: bp.BlockOnOutdatedBranch, BlockOnOfficialReviewRequests: bp.BlockOnOfficialReviewRequests,
DismissStaleApprovals: bp.DismissStaleApprovals, BlockOnOutdatedBranch: bp.BlockOnOutdatedBranch,
RequireSignedCommits: bp.RequireSignedCommits, DismissStaleApprovals: bp.DismissStaleApprovals,
ProtectedFilePatterns: bp.ProtectedFilePatterns, RequireSignedCommits: bp.RequireSignedCommits,
Created: bp.CreatedUnix.AsTime(), ProtectedFilePatterns: bp.ProtectedFilePatterns,
Updated: bp.UpdatedUnix.AsTime(), Created: bp.CreatedUnix.AsTime(),
Updated: bp.UpdatedUnix.AsTime(),
} }
} }

View file

@ -23,26 +23,27 @@ type Branch struct {
// BranchProtection represents a branch protection for a repository // BranchProtection represents a branch protection for a repository
type BranchProtection struct { type BranchProtection struct {
BranchName string `json:"branch_name"` BranchName string `json:"branch_name"`
EnablePush bool `json:"enable_push"` EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"` EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"` PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistTeams []string `json:"push_whitelist_teams"` PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist"` EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"` MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
EnableStatusCheck bool `json:"enable_status_check"` EnableStatusCheck bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"` StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals int64 `json:"required_approvals"` RequiredApprovals int64 `json:"required_approvals"`
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
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"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
DismissStaleApprovals bool `json:"dismiss_stale_approvals"` BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
RequireSignedCommits bool `json:"require_signed_commits"` DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
ProtectedFilePatterns string `json:"protected_file_patterns"` RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
// swagger:strfmt date-time // swagger:strfmt date-time
Created time.Time `json:"created_at"` Created time.Time `json:"created_at"`
// swagger:strfmt date-time // swagger:strfmt date-time
@ -51,47 +52,49 @@ type BranchProtection struct {
// CreateBranchProtectionOption options for creating a branch protection // CreateBranchProtectionOption options for creating a branch protection
type CreateBranchProtectionOption struct { type CreateBranchProtectionOption struct {
BranchName string `json:"branch_name"` BranchName string `json:"branch_name"`
EnablePush bool `json:"enable_push"` EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"` EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"` PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistTeams []string `json:"push_whitelist_teams"` PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist"` EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"` MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
EnableStatusCheck bool `json:"enable_status_check"` EnableStatusCheck bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"` StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals int64 `json:"required_approvals"` RequiredApprovals int64 `json:"required_approvals"`
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
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"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
DismissStaleApprovals bool `json:"dismiss_stale_approvals"` BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
RequireSignedCommits bool `json:"require_signed_commits"` DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
ProtectedFilePatterns string `json:"protected_file_patterns"` RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
} }
// EditBranchProtectionOption options for editing a branch protection // EditBranchProtectionOption options for editing a branch protection
type EditBranchProtectionOption struct { type EditBranchProtectionOption struct {
EnablePush *bool `json:"enable_push"` EnablePush *bool `json:"enable_push"`
EnablePushWhitelist *bool `json:"enable_push_whitelist"` EnablePushWhitelist *bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"` PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistTeams []string `json:"push_whitelist_teams"` PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"` PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"`
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"` EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"` MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
EnableStatusCheck *bool `json:"enable_status_check"` EnableStatusCheck *bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"` StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals *int64 `json:"required_approvals"` RequiredApprovals *int64 `json:"required_approvals"`
EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"` EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
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"`
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"` BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"`
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"` BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
RequireSignedCommits *bool `json:"require_signed_commits"` DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
ProtectedFilePatterns *string `json:"protected_file_patterns"` RequireSignedCommits *bool `json:"require_signed_commits"`
ProtectedFilePatterns *string `json:"protected_file_patterns"`
} }

View file

@ -1244,6 +1244,7 @@ pulls.required_status_check_missing = Some required checks are missing.
pulls.required_status_check_administrator = As an administrator, you may still merge this pull request. pulls.required_status_check_administrator = As an administrator, you may still merge this pull request.
pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals yet. %d of %d approvals granted." pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals yet. %d of %d approvals granted."
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_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:"
pulls.blocked_by_changed_protected_files_n= "This Pull Request is blocked because it changes protected files:" pulls.blocked_by_changed_protected_files_n= "This Pull Request is blocked because it changes protected files:"
@ -1707,6 +1708,8 @@ settings.protected_branch_deletion = Disable Branch Protection
settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue? settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue?
settings.block_rejected_reviews = Block merge on rejected reviews 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_desc = Merging will not be possible when it has official review requests, even if there are enough approvals.
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.default_branch_desc = Select a default repository branch for pull requests and code commits: settings.default_branch_desc = Select a default repository branch for pull requests and code commits:

View file

@ -509,21 +509,22 @@ func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtec
} }
protectBranch = &models.ProtectedBranch{ protectBranch = &models.ProtectedBranch{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
BranchName: form.BranchName, BranchName: form.BranchName,
CanPush: form.EnablePush, CanPush: form.EnablePush,
EnableWhitelist: form.EnablePush && form.EnablePushWhitelist, EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
EnableMergeWhitelist: form.EnableMergeWhitelist, EnableMergeWhitelist: form.EnableMergeWhitelist,
WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys, WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
EnableStatusCheck: form.EnableStatusCheck, EnableStatusCheck: form.EnableStatusCheck,
StatusCheckContexts: form.StatusCheckContexts, StatusCheckContexts: form.StatusCheckContexts,
EnableApprovalsWhitelist: form.EnableApprovalsWhitelist, EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
RequiredApprovals: requiredApprovals, RequiredApprovals: requiredApprovals,
BlockOnRejectedReviews: form.BlockOnRejectedReviews, BlockOnRejectedReviews: form.BlockOnRejectedReviews,
DismissStaleApprovals: form.DismissStaleApprovals, BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
RequireSignedCommits: form.RequireSignedCommits, DismissStaleApprovals: form.DismissStaleApprovals,
ProtectedFilePatterns: form.ProtectedFilePatterns, RequireSignedCommits: form.RequireSignedCommits,
BlockOnOutdatedBranch: form.BlockOnOutdatedBranch, ProtectedFilePatterns: form.ProtectedFilePatterns,
BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
} }
err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{ err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
@ -652,6 +653,10 @@ func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtection
protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
} }
if form.BlockOnOfficialReviewRequests != nil {
protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
}
if form.DismissStaleApprovals != nil { if form.DismissStaleApprovals != nil {
protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
} }

View file

@ -1455,6 +1455,7 @@ func ViewIssue(ctx *context.Context) {
cnt := pull.ProtectedBranch.GetGrantedApprovalsCount(pull) cnt := pull.ProtectedBranch.GetGrantedApprovalsCount(pull)
ctx.Data["IsBlockedByApprovals"] = !pull.ProtectedBranch.HasEnoughApprovals(pull) ctx.Data["IsBlockedByApprovals"] = !pull.ProtectedBranch.HasEnoughApprovals(pull)
ctx.Data["IsBlockedByRejection"] = pull.ProtectedBranch.MergeBlockedByRejectedReview(pull) ctx.Data["IsBlockedByRejection"] = pull.ProtectedBranch.MergeBlockedByRejectedReview(pull)
ctx.Data["IsBlockedByOfficialReviewRequests"] = pull.ProtectedBranch.MergeBlockedByOfficialReviewRequests(pull)
ctx.Data["IsBlockedByOutdatedBranch"] = pull.ProtectedBranch.MergeBlockedByOutdatedBranch(pull) ctx.Data["IsBlockedByOutdatedBranch"] = pull.ProtectedBranch.MergeBlockedByOutdatedBranch(pull)
ctx.Data["GrantedApprovals"] = cnt ctx.Data["GrantedApprovals"] = cnt
ctx.Data["RequireSigned"] = pull.ProtectedBranch.RequireSignedCommits ctx.Data["RequireSigned"] = pull.ProtectedBranch.RequireSignedCommits

View file

@ -246,6 +246,7 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm)
} }
} }
protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
protectBranch.DismissStaleApprovals = f.DismissStaleApprovals protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
protectBranch.RequireSignedCommits = f.RequireSignedCommits protectBranch.RequireSignedCommits = f.RequireSignedCommits
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns

View file

@ -591,6 +591,11 @@ func CheckPRReadyToMerge(pr *models.PullRequest, skipProtectedFilesCheck bool) (
Reason: "There are requested changes", Reason: "There are requested changes",
} }
} }
if pr.ProtectedBranch.MergeBlockedByOfficialReviewRequests(pr) {
return models.ErrNotAllowedToMerge{
Reason: "There are official review requests",
}
}
if pr.ProtectedBranch.MergeBlockedByOutdatedBranch(pr) { if pr.ProtectedBranch.MergeBlockedByOutdatedBranch(pr) {
return models.ErrNotAllowedToMerge{ return models.ErrNotAllowedToMerge{

View file

@ -84,6 +84,7 @@
{{- else if .IsPullRequestBroken}}red {{- else if .IsPullRequestBroken}}red
{{- else if .IsBlockedByApprovals}}red {{- else if .IsBlockedByApprovals}}red
{{- else if .IsBlockedByRejection}}red {{- else if .IsBlockedByRejection}}red
{{- else if .IsBlockedByOfficialReviewRequests}}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
@ -159,6 +160,11 @@
<i class="icon icon-octicon">{{svg "octicon-x"}}</i> <i class="icon icon-octicon">{{svg "octicon-x"}}</i>
{{$.i18n.Tr "repo.pulls.blocked_by_rejection"}} {{$.i18n.Tr "repo.pulls.blocked_by_rejection"}}
</div> </div>
{{else if .IsBlockedByOfficialReviewRequests}}
<div class="item">
<i class="icon icon-octicon">{{svg "octicon-x"}}</i>
{{$.i18n.Tr "repo.pulls.blocked_by_official_review_requests"}}
</div>
{{else if .IsBlockedByOutdatedBranch}} {{else if .IsBlockedByOutdatedBranch}}
<div class="item"> <div class="item">
<i class="icon icon-octicon">{{svg "octicon-x"}}</i> <i class="icon icon-octicon">{{svg "octicon-x"}}</i>
@ -194,7 +200,7 @@
{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }} {{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }}
</div> </div>
{{end}} {{end}}
{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}} {{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
{{if and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}} {{if and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
{{if $notAllOverridableChecksOk}} {{if $notAllOverridableChecksOk}}
<div class="item"> <div class="item">
@ -384,7 +390,12 @@
{{else if .IsBlockedByRejection}} {{else if .IsBlockedByRejection}}
<div class="item text red"> <div class="item text red">
{{svg "octicon-x"}} {{svg "octicon-x"}}
{{$.i18n.Tr "repo.pulls.blocked_by_rejection"}} {{$.i18n.Tr "repo.pulls.blocked_by_rejection"}}
</div>
{{else if .IsBlockedByOfficialReviewRequests}}
<div class="item text red">
{{svg "octicon-x"}}
{{$.i18n.Tr "repo.pulls.blocked_by_official_review_requests"}}
</div> </div>
{{else if .IsBlockedByOutdatedBranch}} {{else if .IsBlockedByOutdatedBranch}}
<div class="item text red"> <div class="item text red">

View file

@ -211,6 +211,13 @@
<p class="help">{{.i18n.Tr "repo.settings.block_rejected_reviews_desc"}}</p> <p class="help">{{.i18n.Tr "repo.settings.block_rejected_reviews_desc"}}</p>
</div> </div>
</div> </div>
<div class="field">
<div class="ui checkbox">
<input name="block_on_official_review_requests" type="checkbox" {{if .Branch.BlockOnOfficialReviewRequests}}checked{{end}}>
<label for="block_on_official_review_requests">{{.i18n.Tr "repo.settings.block_on_official_review_requests"}}</label>
<p class="help">{{.i18n.Tr "repo.settings.block_on_official_review_requests_desc"}}</p>
</div>
</div>
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<input name="dismiss_stale_approvals" type="checkbox" {{if .Branch.DismissStaleApprovals}}checked{{end}}> <input name="dismiss_stale_approvals" type="checkbox" {{if .Branch.DismissStaleApprovals}}checked{{end}}>

View file

@ -11326,6 +11326,10 @@
}, },
"x-go-name": "ApprovalsWhitelistUsernames" "x-go-name": "ApprovalsWhitelistUsernames"
}, },
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
},
"block_on_outdated_branch": { "block_on_outdated_branch": {
"type": "boolean", "type": "boolean",
"x-go-name": "BlockOnOutdatedBranch" "x-go-name": "BlockOnOutdatedBranch"
@ -11660,6 +11664,10 @@
}, },
"x-go-name": "ApprovalsWhitelistUsernames" "x-go-name": "ApprovalsWhitelistUsernames"
}, },
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
},
"block_on_outdated_branch": { "block_on_outdated_branch": {
"type": "boolean", "type": "boolean",
"x-go-name": "BlockOnOutdatedBranch" "x-go-name": "BlockOnOutdatedBranch"
@ -12605,6 +12613,10 @@
}, },
"x-go-name": "ApprovalsWhitelistUsernames" "x-go-name": "ApprovalsWhitelistUsernames"
}, },
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
},
"block_on_outdated_branch": { "block_on_outdated_branch": {
"type": "boolean", "type": "boolean",
"x-go-name": "BlockOnOutdatedBranch" "x-go-name": "BlockOnOutdatedBranch"