Skip to content

Commit 0ef7cde

Browse files
authored
Merge branch 'main' into fix-dropzone-image
2 parents 7ebf907 + de2ad2e commit 0ef7cde

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

templates/repo/issue/view_content/pull.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@
164164
{{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not .RequiredStatusCheckState.IsSuccess))}}
165165

166166
{{/* admin can merge without checks, writer can merge when checks succeed */}}
167-
{{$canMergeNow := and (or $.IsRepoAdmin (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
167+
{{$canMergeNow := and (or (and (not $.ProtectedBranch.BlockAdminMergeOverride) $.IsRepoAdmin) (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
168168
{{/* admin and writer both can make an auto merge schedule */}}
169169

170170
{{if $canMergeNow}}

templates/repo/settings/protected_branch.tmpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,13 @@
323323
<p class="help">{{ctx.Locale.Tr "repo.settings.block_outdated_branch_desc"}}</p>
324324
</div>
325325
</div>
326+
<div class="field">
327+
<div class="ui checkbox">
328+
<input name="block_admin_merge_override" type="checkbox" {{if .Rule.BlockAdminMergeOverride}}checked{{end}}>
329+
<label>{{ctx.Locale.Tr "repo.settings.block_admin_merge_override"}}</label>
330+
<p class="help">{{ctx.Locale.Tr "repo.settings.block_admin_merge_override_desc"}}</p>
331+
</div>
332+
</div>
326333
<div class="divider"></div>
327334

328335
<div class="field">

templates/swagger/v1_json.tmpl

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/pull_merge_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,3 +976,50 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.
976976
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
977977
})
978978
}
979+
980+
func TestPullNonMergeForAdminWithBranchProtection(t *testing.T) {
981+
onGiteaRun(t, func(t *testing.T, u *url.URL) {
982+
// create a pull request
983+
session := loginUser(t, "user1")
984+
forkedName := "repo1-1"
985+
testRepoFork(t, session, "user2", "repo1", "user1", forkedName, "")
986+
defer testDeleteRepository(t, session, "user1", forkedName)
987+
988+
testEditFile(t, session, "user1", forkedName, "master", "README.md", "Hello, World (Edited)\n")
989+
testPullCreate(t, session, "user1", forkedName, false, "master", "master", "Indexer notifier test pull")
990+
991+
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
992+
forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: forkedName})
993+
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
994+
BaseRepoID: baseRepo.ID,
995+
BaseBranch: "master",
996+
HeadRepoID: forkedRepo.ID,
997+
HeadBranch: "master",
998+
})
999+
1000+
// add protected branch for commit status
1001+
csrf := GetUserCSRFToken(t, session)
1002+
// Change master branch to protected
1003+
pbCreateReq := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
1004+
"_csrf": csrf,
1005+
"rule_name": "master",
1006+
"enable_push": "true",
1007+
"enable_status_check": "true",
1008+
"status_check_contexts": "gitea/actions",
1009+
"block_admin_merge_override": "true",
1010+
})
1011+
session.MakeRequest(t, pbCreateReq, http.StatusSeeOther)
1012+
1013+
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
1014+
1015+
mergeReq := NewRequestWithValues(t, "POST", "/api/v1/repos/user2/repo1/pulls/6/merge", map[string]string{
1016+
"_csrf": csrf,
1017+
"head_commit_id": "",
1018+
"merge_when_checks_succeed": "false",
1019+
"force_merge": "true",
1020+
"do": "rebase",
1021+
}).AddTokenAuth(token)
1022+
1023+
session.MakeRequest(t, mergeReq, http.StatusMethodNotAllowed)
1024+
})
1025+
}

0 commit comments

Comments
 (0)