Skip to content

Commit 90982bf

Browse files
authored
Add force_merge to merge request and fix checking mergable (#23010) (#23032)
Backport #23010. Fix #23000. The bug was introduced in #22633, and it seems that it has been noticed: #22633 (comment) . However, #22633 did nothing wrong, the logic should be "check if they is admin only when `force` is true". So we should provide the `ForceMerge` when merging from UI. After this, an admin can also send a normal merge request with `ForceMerge` false. So it fixes a potential bug: if the admin doesn't want to do a force merge, they just see the green "Merge" button and click it. At the same time, the status of the PR changed, and it shouldn't be merged now, so the admin could send an unexpected force merge. In addition, I updated `ForceMerge *bool` to `ForceMerge bool`, I don't see the reason to use a pointer. And fixed the logic of CheckPullMergable to handle auto merge and force merge correctly.
1 parent 8fa62be commit 90982bf

File tree

6 files changed

+59
-20
lines changed

6 files changed

+59
-20
lines changed

Diff for: routers/api/v1/repo/pull.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -767,11 +767,18 @@ func MergePullRequest(ctx *context.APIContext) {
767767
}
768768
}
769769

770-
manuallMerge := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged
771-
force := form.ForceMerge != nil && *form.ForceMerge
770+
manuallyMerged := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged
771+
772+
mergeCheckType := pull_service.MergeCheckTypeGeneral
773+
if form.MergeWhenChecksSucceed {
774+
mergeCheckType = pull_service.MergeCheckTypeAuto
775+
}
776+
if manuallyMerged {
777+
mergeCheckType = pull_service.MergeCheckTypeManually
778+
}
772779

773780
// start with merging by checking
774-
if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, manuallMerge, force); err != nil {
781+
if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil {
775782
if errors.Is(err, pull_service.ErrIsClosed) {
776783
ctx.NotFound()
777784
} else if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) {
@@ -793,7 +800,7 @@ func MergePullRequest(ctx *context.APIContext) {
793800
}
794801

795802
// handle manually-merged mark
796-
if manuallMerge {
803+
if manuallyMerged {
797804
if err := pull_service.MergedManually(pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
798805
if models.IsErrInvalidMergeStyle(err) {
799806
ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))

Diff for: routers/web/repo/pull.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -912,11 +912,19 @@ func MergePullRequest(ctx *context.Context) {
912912
pr := issue.PullRequest
913913
pr.Issue = issue
914914
pr.Issue.Repo = ctx.Repo.Repository
915-
manualMerge := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged
916-
forceMerge := form.ForceMerge != nil && *form.ForceMerge
915+
916+
manuallyMerged := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged
917+
918+
mergeCheckType := pull_service.MergeCheckTypeGeneral
919+
if form.MergeWhenChecksSucceed {
920+
mergeCheckType = pull_service.MergeCheckTypeAuto
921+
}
922+
if manuallyMerged {
923+
mergeCheckType = pull_service.MergeCheckTypeManually
924+
}
917925

918926
// start with merging by checking
919-
if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, manualMerge, forceMerge); err != nil {
927+
if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil {
920928
switch {
921929
case errors.Is(err, pull_service.ErrIsClosed):
922930
if issue.IsPull {
@@ -948,7 +956,7 @@ func MergePullRequest(ctx *context.Context) {
948956
}
949957

950958
// handle manually-merged mark
951-
if manualMerge {
959+
if manuallyMerged {
952960
if err := pull_service.MergedManually(pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
953961
switch {
954962

Diff for: services/automerge/automerge.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func handlePull(pullID int64, sha string) {
231231
return
232232
}
233233

234-
if err := pull_service.CheckPullMergable(ctx, doer, &perm, pr, false, false); err != nil {
234+
if err := pull_service.CheckPullMergable(ctx, doer, &perm, pr, pull_service.MergeCheckTypeGeneral, false); err != nil {
235235
if errors.Is(pull_service.ErrUserNotAllowedToMerge, err) {
236236
log.Info("%-v was scheduled to automerge by an unauthorized user", pr)
237237
return

Diff for: services/forms/repo_form.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ type MergePullRequestForm struct {
597597
MergeMessageField string
598598
MergeCommitID string // only used for manually-merged
599599
HeadCommitID string `json:"head_commit_id,omitempty"`
600-
ForceMerge *bool `json:"force_merge,omitempty"`
600+
ForceMerge bool `json:"force_merge,omitempty"`
601601
MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed,omitempty"`
602602
DeleteBranchAfterMerge bool `json:"delete_branch_after_merge,omitempty"`
603603
}

Diff for: services/pull/check.go

+28-9
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,16 @@ func AddToTaskQueue(pr *issues_model.PullRequest) {
5959
}
6060
}
6161

62+
type MergeCheckType int
63+
64+
const (
65+
MergeCheckTypeGeneral MergeCheckType = iota // general merge checks for "merge", "rebase", "squash", etc
66+
MergeCheckTypeManually // Manually Merged button (mark a PR as merged manually)
67+
MergeCheckTypeAuto // Auto Merge (Scheduled Merge) After Checks Succeed
68+
)
69+
6270
// CheckPullMergable check if the pull mergable based on all conditions (branch protection, merge options, ...)
63-
func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, manuallMerge, force bool) error {
71+
func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminSkipProtectionCheck bool) error {
6472
return db.WithTx(func(ctx context.Context) error {
6573
if pr.HasMerged {
6674
return ErrHasMerged
@@ -80,8 +88,8 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
8088
return ErrUserNotAllowedToMerge
8189
}
8290

83-
if manuallMerge {
84-
// don't check rules to "auto merge", doer is going to mark this pull as merged manually
91+
if mergeCheckType == MergeCheckTypeManually {
92+
// if doer is doing "manually merge" (mark as merged manually), do not check anything
8593
return nil
8694
}
8795

@@ -103,14 +111,25 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
103111
return err
104112
}
105113

106-
if !force {
107-
return err
114+
// Now the branch protection check failed, check whether the failure could be skipped (skip by setting err = nil)
115+
116+
// * when doing Auto Merge (Scheduled Merge After Checks Succeed), skip the branch protection check
117+
if mergeCheckType == MergeCheckTypeAuto {
118+
err = nil
119+
}
120+
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
128+
}
108129
}
109130

110-
if isRepoAdmin, err2 := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer); err2 != nil {
111-
log.Error("Unable to check if %-v is a repo admin in %-v: %v", doer, pr.BaseRepo, err2)
112-
return err2
113-
} else if !isRepoAdmin {
131+
// If there is still a branch protection check error, return it
132+
if err != nil {
114133
return err
115134
}
116135
}

Diff for: web_src/js/components/PullRequestMergeForm.vue

+6-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<input type="hidden" name="_csrf" :value="csrfToken">
1919
<input type="hidden" name="head_commit_id" v-model="mergeForm.pullHeadCommitID">
2020
<input type="hidden" name="merge_when_checks_succeed" v-model="autoMergeWhenSucceed">
21+
<input type="hidden" name="force_merge" v-model="forceMerge">
2122

2223
<template v-if="!mergeStyleDetail.hideMergeMessageTexts">
2324
<div class="field">
@@ -127,6 +128,7 @@ export default {
127128
textDoMerge: '',
128129
mergeTitleFieldText: '',
129130
mergeMessageFieldText: '',
131+
hideAutoMerge: false,
130132
},
131133
mergeStyleAllowedCount: 0,
132134
@@ -138,7 +140,10 @@ export default {
138140
mergeButtonStyleClass() {
139141
if (this.mergeForm.allOverridableChecksOk) return 'green';
140142
return this.autoMergeWhenSucceed ? 'blue' : 'red';
141-
}
143+
},
144+
forceMerge() {
145+
return this.mergeForm.canMergeNow && !this.mergeForm.allOverridableChecksOk;
146+
},
142147
},
143148
144149
watch: {

0 commit comments

Comments
 (0)