mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-28 02:38:44 +09:00
fix: merge autodetect can't close other PRs but only the last one when multiple PRs are pushed at once (#37512) (#37516)
Backport #37512 Fixes #37510. Co-authored-by: Jason Learst <jason@jasonlearst.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -6,13 +6,16 @@ package integration
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
@@ -26,7 +29,6 @@ func TestManualMergeAutodetect(t *testing.T) {
|
||||
// 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"
|
||||
@@ -41,35 +43,62 @@ func TestManualMergeAutodetect(t *testing.T) {
|
||||
AutodetectManualMerge: new(true),
|
||||
})(t)
|
||||
|
||||
// Create a PR from a branch
|
||||
branchName := "feature"
|
||||
testEditFileToNewBranch(t, session2, user2.Name, repoName, defaultBranch, branchName, "README.md", "Manual Merge Test")
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: repoName})
|
||||
user1Ctx := NewAPITestContext(t, user1.Name, repoName, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
apiPull, err := doAPICreatePullRequest(NewAPITestContext(t, user1.Name, repoName, auth_model.AccessTokenScopeWriteRepository), user2.Name, repoName, defaultBranch, branchName)(t)
|
||||
assert.NoError(t, err)
|
||||
// multiple PRs should be able to be closed together if a push contains their branch commits.
|
||||
branchNames := []string{"fix-1", "fix-2"}
|
||||
apiPulls := make([]api.PullRequest, len(branchNames))
|
||||
for i, branchName := range branchNames {
|
||||
_, err := createFileInBranch(user2, repo,
|
||||
createFileInBranchOptions{OldBranch: defaultBranch, NewBranch: branchName},
|
||||
map[string]string{"test-file-" + branchName: "dummy"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
apiPulls[i], err = doAPICreatePullRequest(user1Ctx, user2.Name, repoName, defaultBranch, branchName)(t)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// user1 clones and pushes the branch to master (fast-forward)
|
||||
// user1 clones, then merges every branch sequentially, then pushes once.
|
||||
// The first merge fast-forwards; the rest produce real merge commits,
|
||||
// which generates multiple commits for "git rev-list --ancestry-path --merges ...".
|
||||
dstPath := t.TempDir()
|
||||
u, _ := url.Parse(giteaURL.String())
|
||||
u.Path = fmt.Sprintf("%s/%s.git", user2.Name, repoName)
|
||||
u.User = url.UserPassword(user1.Name, userPassword)
|
||||
|
||||
doGitClone(dstPath, u)(t)
|
||||
doGitMerge(dstPath, "origin/"+branchName)(t)
|
||||
|
||||
// Capture each branch's expected merge commit hash from the local clone,
|
||||
// so we can assert that Gitea recorded the correct merge commit per PR
|
||||
// (and not just "some merge commit" — see the regression where every PR
|
||||
// was attributed to the last merge in the push).
|
||||
expectedMergeCommits := make([]string, len(branchNames))
|
||||
for i, branchName := range branchNames {
|
||||
doGitMerge(dstPath, "--no-ff", "-m", "merge "+branchName, "origin/"+branchName)(t)
|
||||
head, _, cmdErr := gitcmd.NewCommand("rev-parse", "HEAD").WithDir(dstPath).RunStdString(t.Context())
|
||||
require.NoError(t, cmdErr)
|
||||
expectedMergeCommits[i] = strings.TrimSpace(head)
|
||||
}
|
||||
doGitPushTestRepository(dstPath, "origin", defaultBranch)(t)
|
||||
|
||||
// Wait for the PR to be marked as merged by the background task
|
||||
var pr *issues_model.PullRequest
|
||||
// Every PR should be detected as manually merged by the background task, not just the last one.
|
||||
require.Eventually(t, func() bool {
|
||||
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
|
||||
return pr.HasMerged
|
||||
for _, apiPull := range apiPulls {
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
|
||||
if !pr.HasMerged {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}, 10*time.Second, 100*time.Millisecond)
|
||||
|
||||
// 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)
|
||||
for i, apiPull := range apiPulls {
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
|
||||
assert.Truef(t, pr.HasMerged, "PR %d (%s) should be marked as merged", i+1, branchNames[i])
|
||||
assert.Equalf(t, issues_model.PullRequestStatusManuallyMerged, pr.Status, "PR %d (%s) should have ManuallyMerged status", i+1, branchNames[i])
|
||||
assert.Equalf(t, user1.ID, pr.MergerID, "PR %d (%s) merger should be the pusher", i+1, branchNames[i])
|
||||
assert.Equalf(t, expectedMergeCommits[i], pr.MergedCommitID, "PR %d (%s) should be attributed to its own merge commit, not another PR's", i+1, branchNames[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user