Allow fast-forward-only merge when signed commits are required (#37335)

Fast-forward-only creates no Gitea commit, so skip the "can Gitea sign"
precheck for it. Pre-check head-commit verification for styles that
preserve user commits on the target (merge, fast-forward-only) so a PR
with unsigned commits surfaces a localized error instead of a 500 at the
pre-receive hook. The dropdown still shows every configured style; the
avatar and signing warning toggle per selection via
data-pull-merge-style.

Fixes #12272 

**Note**: Admin force-merge does not bypass the new head-commits check.
This matches the existing `isSignedIfRequired` behavior.

Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Nikita Vakula
2026-04-24 02:04:32 +02:00
committed by GitHub
parent 899ede1d55
commit 3b2fd9791c
8 changed files with 205 additions and 38 deletions
+3 -1
View File
@@ -1100,7 +1100,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, repo_model.MergeStyle(form.Do), form.ForceMerge); err != nil {
switch {
case errors.Is(err, pull_service.ErrIsClosed):
if issue.IsPull {
@@ -1120,6 +1120,8 @@ func MergePullRequest(ctx *context.Context) {
ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
case asymkey_service.IsErrWontSign(err):
ctx.JSONError(err.Error()) // has no translation ...
case errors.Is(err, pull_service.ErrHeadCommitsNotAllVerified):
ctx.JSONError(ctx.Tr("repo.pulls.require_signed_head_commits_unverified"))
case errors.Is(err, pull_service.ErrDependenciesLeft):
ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
default: