Skip to content

Commit de2ad2e

Browse files
enkowxiaoguangGiteaBot
authored
Make admins adhere to branch protection rules (#32248)
This introduces a new flag `BlockAdminMergeOverride` on the branch protection rules that prevents admins/repo owners from bypassing branch protection rules and merging without approvals or failing status checks. Fixes #17131 --------- Co-authored-by: wxiaoguang <[email protected]> Co-authored-by: Giteabot <[email protected]>
1 parent 620f196 commit de2ad2e

File tree

14 files changed

+113
-9
lines changed

14 files changed

+113
-9
lines changed

models/git/protected_branch.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type ProtectedBranch struct {
6363
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
6464
ProtectedFilePatterns string `xorm:"TEXT"`
6565
UnprotectedFilePatterns string `xorm:"TEXT"`
66+
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
6667

6768
CreatedUnix timeutil.TimeStamp `xorm:"created"`
6869
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,8 @@ var migrations = []Migration{
603603
NewMigration("Add index for release sha1", v1_23.AddIndexForReleaseSha1),
604604
// v305 -> v306
605605
NewMigration("Add Repository Licenses", v1_23.AddRepositoryLicenses),
606+
// v306 -> v307
607+
NewMigration("Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
606608
}
607609

608610
// GetCurrentDBVersion returns the current db version

models/migrations/v1_23/v306.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_23 //nolint
5+
6+
import "xorm.io/xorm"
7+
8+
func AddBlockAdminMergeOverrideBranchProtection(x *xorm.Engine) error {
9+
type ProtectedBranch struct {
10+
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
11+
}
12+
return x.Sync(new(ProtectedBranch))
13+
}

modules/structs/repo_branch.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type BranchProtection struct {
5252
RequireSignedCommits bool `json:"require_signed_commits"`
5353
ProtectedFilePatterns string `json:"protected_file_patterns"`
5454
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
55+
BlockAdminMergeOverride bool `json:"block_admin_merge_override"`
5556
// swagger:strfmt date-time
5657
Created time.Time `json:"created_at"`
5758
// swagger:strfmt date-time
@@ -90,6 +91,7 @@ type CreateBranchProtectionOption struct {
9091
RequireSignedCommits bool `json:"require_signed_commits"`
9192
ProtectedFilePatterns string `json:"protected_file_patterns"`
9293
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
94+
BlockAdminMergeOverride bool `json:"block_admin_merge_override"`
9395
}
9496

9597
// EditBranchProtectionOption options for editing a branch protection
@@ -121,4 +123,5 @@ type EditBranchProtectionOption struct {
121123
RequireSignedCommits *bool `json:"require_signed_commits"`
122124
ProtectedFilePatterns *string `json:"protected_file_patterns"`
123125
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
126+
BlockAdminMergeOverride *bool `json:"block_admin_merge_override"`
124127
}

options/locale/locale_en-US.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2461,6 +2461,8 @@ settings.block_on_official_review_requests = Block merge on official review requ
24612461
settings.block_on_official_review_requests_desc = Merging will not be possible when it has official review requests, even if there are enough approvals.
24622462
settings.block_outdated_branch = Block merge if pull request is outdated
24632463
settings.block_outdated_branch_desc = Merging will not be possible when head branch is behind base branch.
2464+
settings.block_admin_merge_override = Administrators must follow branch protection rules
2465+
settings.block_admin_merge_override_desc = Administrators must follow branch protection rules and can not circumvent it.
24642466
settings.default_branch_desc = Select a default repository branch for pull requests and code commits:
24652467
settings.merge_style_desc = Merge Styles
24662468
settings.default_merge_style_desc = Default Merge Style

routers/api/v1/repo/branch.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
642642
ProtectedFilePatterns: form.ProtectedFilePatterns,
643643
UnprotectedFilePatterns: form.UnprotectedFilePatterns,
644644
BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
645+
BlockAdminMergeOverride: form.BlockAdminMergeOverride,
645646
}
646647

647648
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
@@ -852,6 +853,10 @@ func EditBranchProtection(ctx *context.APIContext) {
852853
protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
853854
}
854855

856+
if form.BlockAdminMergeOverride != nil {
857+
protectBranch.BlockAdminMergeOverride = *form.BlockAdminMergeOverride
858+
}
859+
855860
var whitelistUsers, forcePushAllowlistUsers, mergeWhitelistUsers, approvalsWhitelistUsers []int64
856861
if form.PushWhitelistUsernames != nil {
857862
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)

routers/web/repo/setting/protected_branch.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
256256
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
257257
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
258258
protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
259+
protectBranch.BlockAdminMergeOverride = f.BlockAdminMergeOverride
259260

260261
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
261262
UserIDs: whitelistUsers,

services/convert/convert.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo
185185
RequireSignedCommits: bp.RequireSignedCommits,
186186
ProtectedFilePatterns: bp.ProtectedFilePatterns,
187187
UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
188+
BlockAdminMergeOverride: bp.BlockAdminMergeOverride,
188189
Created: bp.CreatedUnix.AsTime(),
189190
Updated: bp.UpdatedUnix.AsTime(),
190191
}

services/forms/repo_form.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ type ProtectBranchForm struct {
219219
RequireSignedCommits bool
220220
ProtectedFilePatterns string
221221
UnprotectedFilePatterns string
222+
BlockAdminMergeOverride bool
222223
}
223224

224225
// Validate validates the fields

services/pull/check.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const (
6868
)
6969

7070
// CheckPullMergeable check if the pull mergeable based on all conditions (branch protection, merge options, ...)
71-
func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminSkipProtectionCheck bool) error {
71+
func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminForceMerge bool) error {
7272
return db.WithTx(stdCtx, func(ctx context.Context) error {
7373
if pr.HasMerged {
7474
return ErrHasMerged
@@ -118,13 +118,22 @@ func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *acc
118118
err = nil
119119
}
120120

121-
// * if the doer is admin, they could skip the branch protection check
122-
if adminSkipProtectionCheck {
123-
if isRepoAdmin, errCheckAdmin := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer); errCheckAdmin != nil {
124-
log.Error("Unable to check if %-v is a repo admin in %-v: %v", doer, pr.BaseRepo, errCheckAdmin)
125-
return errCheckAdmin
126-
} else if isRepoAdmin {
127-
err = nil // repo admin can skip the check, so clear the error
121+
// * if admin tries to "Force Merge", they could sometimes skip the branch protection check
122+
if adminForceMerge {
123+
isRepoAdmin, errForceMerge := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer)
124+
if errForceMerge != nil {
125+
return fmt.Errorf("IsUserRepoAdmin failed, repo: %v, doer: %v, err: %w", pr.BaseRepoID, doer.ID, errForceMerge)
126+
}
127+
128+
protectedBranchRule, errForceMerge := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
129+
if errForceMerge != nil {
130+
return fmt.Errorf("GetFirstMatchProtectedBranchRule failed, repo: %v, base branch: %v, err: %w", pr.BaseRepoID, pr.BaseBranch, errForceMerge)
131+
}
132+
133+
// if doer is admin and the "Force Merge" is not blocked, then clear the branch protection check error
134+
blockAdminForceMerge := protectedBranchRule != nil && protectedBranchRule.BlockAdminMergeOverride
135+
if isRepoAdmin && !blockAdminForceMerge {
136+
err = nil
128137
}
129138
}
130139

0 commit comments

Comments
 (0)