diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 3c3eb66d5c..1f82bfce1b 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -922,7 +922,8 @@ func MergePullRequest(ctx *context.APIContext) { } } - manuallyMerged := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged + mergeStyle := repo_model.MergeStyle(form.Do) + manuallyMerged := mergeStyle == repo_model.MergeStyleManuallyMerged mergeCheckType := pull_service.MergeCheckTypeGeneral if form.MergeWhenChecksSucceed { @@ -933,7 +934,7 @@ func MergePullRequest(ctx *context.APIContext) { } // start with merging by checking - if err := pull_service.CheckPullMergeable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil { + if err := pull_service.CheckPullMergeable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge, mergeStyle); err != nil { if errors.Is(err, pull_service.ErrIsClosed) { ctx.NotFound() } else if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) { diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index e9cdfc5ac7..dc36d2de68 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1317,7 +1317,8 @@ func MergePullRequest(ctx *context.Context) { pr.Issue = issue pr.Issue.Repo = ctx.Repo.Repository - manuallyMerged := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged + mergeStyle := repo_model.MergeStyle(form.Do) + manuallyMerged := mergeStyle == repo_model.MergeStyleManuallyMerged mergeCheckType := pull_service.MergeCheckTypeGeneral if form.MergeWhenChecksSucceed { @@ -1328,7 +1329,7 @@ func MergePullRequest(ctx *context.Context) { } // start with merging by checking - if err := pull_service.CheckPullMergeable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil { + if err := pull_service.CheckPullMergeable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge, mergeStyle); err != nil { switch { case errors.Is(err, pull_service.ErrIsClosed): if issue.IsPull { diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go index 099d048927..4c5459adf6 100644 --- a/services/automerge/automerge.go +++ b/services/automerge/automerge.go @@ -215,7 +215,7 @@ func handlePullRequestAutoMerge(pullID int64, sha string) { return } - if err := pull_service.CheckPullMergeable(ctx, doer, &perm, pr, pull_service.MergeCheckTypeGeneral, false); err != nil { + if err := pull_service.CheckPullMergeable(ctx, doer, &perm, pr, pull_service.MergeCheckTypeGeneral, false, scheduledPRM.MergeStyle); err != nil { if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) { log.Info("%-v was scheduled to automerge by an unauthorized user", pr) return diff --git a/services/pull/check.go b/services/pull/check.go index 64bddf497c..0e74973456 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -65,7 +65,7 @@ const ( ) // CheckPullMergeable check if the pull mergeable based on all conditions (branch protection, merge options, ...) -func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminSkipProtectionCheck bool) error { +func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminSkipProtectionCheck bool, mergeStyle repo_model.MergeStyle) error { return db.WithTx(stdCtx, func(ctx context.Context) error { if pr.HasMerged { return ErrHasMerged @@ -136,7 +136,7 @@ func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *acc } } - if _, err := isSignedIfRequired(ctx, pr, doer); err != nil { + if _, err := isSignedIfRequired(ctx, pr, doer, mergeStyle); err != nil { return err } @@ -151,7 +151,7 @@ func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *acc } // isSignedIfRequired check if merge will be signed if required -func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) (bool, error) { +func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle) (bool, error) { pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) if err != nil { return false, err @@ -161,11 +161,22 @@ func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer return true, nil } + if !isMergeSigningRequired(mergeStyle) { + return true, nil + } + sign, _, _, err := asymkey_service.SignMerge(ctx, pr, doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName()) return sign, err } +func isMergeSigningRequired(mergeStyle repo_model.MergeStyle) bool { + // Only fast-forward-only is guaranteed not to create a new commit. Rebase + // rewrites commits when the pull request is behind, and it can also amend + // the tip commit when a REBASE_TEMPLATE is configured. + return mergeStyle != repo_model.MergeStyleFastForwardOnly +} + // checkAndUpdateStatus checks if pull request is possible to leaving checking status, // and set to be either conflict or mergeable. func checkAndUpdateStatus(ctx context.Context, pr *issues_model.PullRequest) bool { diff --git a/services/pull/check_test.go b/services/pull/check_test.go index 9b7e1660bc..3115bb7a21 100644 --- a/services/pull/check_test.go +++ b/services/pull/check_test.go @@ -11,6 +11,7 @@ import ( "forgejo.org/models/db" issues_model "forgejo.org/models/issues" + repo_model "forgejo.org/models/repo" "forgejo.org/models/unittest" "forgejo.org/modules/queue" "forgejo.org/modules/setting" @@ -67,3 +68,53 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { prPatchCheckerQueue.ShutdownWait(5 * time.Second) prPatchCheckerQueue = nil } + +func TestIsMergeSigningRequired(t *testing.T) { + testCases := []struct { + name string + mergeStyle repo_model.MergeStyle + expected bool + }{ + { + name: "fast-forward never requires signing", + mergeStyle: repo_model.MergeStyleFastForwardOnly, + expected: false, + }, + { + name: "rebase requires signing even when up to date", + mergeStyle: repo_model.MergeStyleRebase, + expected: true, + }, + { + name: "rebase-merge requires signing", + mergeStyle: repo_model.MergeStyleRebaseMerge, + expected: true, + }, + { + name: "squash commits require signing", + mergeStyle: repo_model.MergeStyleSquash, + expected: true, + }, + { + name: "merge commits require signing", + mergeStyle: repo_model.MergeStyleMerge, + expected: true, + }, + { + name: "rebase-update style still requires signing", + mergeStyle: repo_model.MergeStyleRebaseUpdate, + expected: true, + }, + { + name: "empty merge style requires signing", + mergeStyle: "", + expected: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert.Equal(t, testCase.expected, isMergeSigningRequired(testCase.mergeStyle)) + }) + } +} diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl index b5cce664a3..ca9b943fa2 100644 --- a/templates/repo/issue/view_content/pull.tmpl +++ b/templates/repo/issue/view_content/pull.tmpl @@ -1,3 +1,9 @@ +{{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}} +{{$prConfig := $prUnit.PullRequestsConfig}} +{{$fastForwardStyleAllowed := and $prConfig.AllowFastForwardOnly (eq .Issue.PullRequest.CommitsBehind 0)}} +{{$manualMergeStyleAllowed := $prConfig.AllowManualMerge}} +{{$hasUnsignedMergeStyle := or $fastForwardStyleAllowed $manualMergeStyleAllowed}} +{{$signingBlocksAllMergeStyles := and .AllowMerge .RequireSigned (not .WillSign) (not $hasUnsignedMergeStyle)}} {{if and .Issue.PullRequest.HasMerged (not .IsPullBranchDeletable)}} {{/* Then the merge box will not be displayed because this page already contains enough information */}} {{else}} @@ -14,7 +20,7 @@ {{- else if .IsBlockedByChangedProtectedFiles}}red {{- else if and .EnableStatusCheck (or .RequiredStatusCheckState.IsFailure .RequiredStatusCheckState.IsError)}}red {{- else if and .EnableStatusCheck (or (not $.LatestCommitStatus) .RequiredStatusCheckState.IsPending .RequiredStatusCheckState.IsWarning)}}yellow - {{- else if and .AllowMerge .RequireSigned (not .WillSign)}}red + {{- else if $signingBlocksAllMergeStyles}}red {{- else if .Issue.PullRequest.IsChecking}}yellow {{- else if .Issue.PullRequest.IsEmpty}}grey {{- else if .Issue.PullRequest.CanAutoMerge}}green @@ -148,7 +154,7 @@ {{svg "octicon-x"}} {{ctx.Locale.Tr "repo.pulls.required_status_check_missing"}} - {{else if and .AllowMerge .RequireSigned (not .WillSign)}} + {{else if $signingBlocksAllMergeStyles}}