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
+34
View File
@@ -9,6 +9,7 @@ import (
"time"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/pull"
repo_model "code.gitea.io/gitea/models/repo"
@@ -73,6 +74,39 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) {
prPatchCheckerQueue = nil
}
func TestCheckSigningRequirementsHeadCommits(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
require.NoError(t, pr.LoadBaseRepo(ctx))
require.NoError(t, pr.LoadHeadRepo(ctx))
check := func() error {
return checkSigningRequirements(ctx, pr, nil, repo_model.MergeStyleFastForwardOnly)
}
// No protected branch rule on the base branch: the check must pass.
require.NoError(t, check())
// Protected branch without RequireSignedCommits: the check must still pass.
require.NoError(t, git_model.UpdateProtectBranch(ctx, pr.BaseRepo, &git_model.ProtectedBranch{
RepoID: pr.BaseRepoID,
RuleName: pr.BaseBranch,
RequireSignedCommits: false,
}, git_model.WhitelistOptions{}))
require.NoError(t, check())
// With RequireSignedCommits enabled: the test fixture commits have no signatures,
// so the check must report ErrHeadCommitsNotAllVerified.
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
require.NoError(t, err)
require.NotNil(t, pb)
pb.RequireSignedCommits = true
require.NoError(t, git_model.UpdateProtectBranch(ctx, pr.BaseRepo, pb, git_model.WhitelistOptions{}))
require.ErrorIs(t, check(), ErrHeadCommitsNotAllVerified)
}
func TestMarkPullRequestAsMergeable(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())