From fb6d898a710fcf547d6fe5c4b0410278a3a63446 Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Tue, 10 Feb 2026 12:05:00 +0100 Subject: [PATCH 1/2] Pusher is author of manual merged changes When ff-only merge of a PR happens, the pusher is the de-facto author of the merge, not the last committer in the PR submission. --- services/pull/check.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/services/pull/check.go b/services/pull/check.go index 8826fca280..c00eca92be 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -362,7 +362,17 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool { return false } - merger, _ := user_model.GetUserByEmail(ctx, commit.Author.Email) + var merger *user_model.User + if commit.ParentCount() > 1 { + merger, _ = user_model.GetUserByEmail(ctx, commit.Author.Email) + } else { + branch, err := git_model.GetBranch(ctx, pr.BaseRepoID, pr.BaseBranch) + if err == nil { + if err = branch.LoadPusher(ctx); err == nil { + merger = branch.Pusher + } + } + } // When the commit author is unknown set the BaseRepo owner as merger if merger == nil { From 55ae2102bef98b1f209155c2d729d5048086bc9b Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Tue, 10 Feb 2026 12:52:18 +0100 Subject: [PATCH 2/2] Add integration test --- tests/integration/manual_merge_test.go | 68 ++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/integration/manual_merge_test.go diff --git a/tests/integration/manual_merge_test.go b/tests/integration/manual_merge_test.go new file mode 100644 index 0000000000..c4877d22e2 --- /dev/null +++ b/tests/integration/manual_merge_test.go @@ -0,0 +1,68 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/url" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/assert" +) + +func TestManualMergeAutodetect(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // user2 is the repo owner + // user1 is the pusher/merger + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session2 := loginUser(t, user2.Name) + + // Create a repo owned by user2 + repoName := "manual-merge-autodetect" + var repo api.Repository + user2Ctx := NewAPITestContext(t, user2.Name, repoName, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + doAPICreateRepository(user2Ctx, false, func(t *testing.T, r api.Repository) { + repo = r + })(t) + + // Enable autodetect manual merge + doAPIEditRepository(user2Ctx, &api.EditRepoOption{ + HasPullRequests: util.ToPointer(true), + AllowManualMerge: util.ToPointer(true), + AutodetectManualMerge: util.ToPointer(true), + })(t) + + // Create a PR from a branch + branchName := "feature" + testEditFileToNewBranch(t, session2, user2.Name, repo.Name, repo.DefaultBranch, branchName, "README.md", "Manual Merge Test") + + apiPull, err := doAPICreatePullRequest(NewAPITestContext(t, user1.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository), user2.Name, repo.Name, repo.DefaultBranch, branchName)(t) + assert.NoError(t, err) + + // user1 clones and pushes the branch to master (fast-forward) + dstPath := t.TempDir() + u, _ := url.Parse(giteaURL.String()) + u.Path = fmt.Sprintf("%s/%s.git", user2.Name, repo.Name) + u.User = url.UserPassword(user1.Name, userPassword) + + doGitClone(dstPath, u)(t) + doGitMerge(dstPath, "origin/"+branchName)(t) + doGitPushTestRepository(dstPath, "origin", repo.DefaultBranch)(t) + + // Check if the PR is merged and who is the merger + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID}) + assert.True(t, pr.HasMerged) + assert.Equal(t, issues_model.PullRequestStatusManuallyMerged, pr.Status) + // Merger should be user1 (the pusher), not the commit author (user2) or repo owner (user2) + assert.Equal(t, user1.ID, pr.MergerID) + }) +}