mirror of
https://github.com/go-gitea/gitea.git
synced 2026-01-30 16:00:44 +01:00
In Git 2.38, the `merge-tree` command introduced the `--write-tree` option, which works directly on bare repositories. In Git 2.40, a new parameter `--merge-base` introduced so we require Git 2.40 to use the merge tree feature. This option produces the merged tree object ID, allowing us to perform diffs between commits without creating a temporary repository. By avoiding the overhead of setting up and tearing down temporary repos, this approach delivers a notable performance improvement. It also fixes a possible situation that conflict files might be empty but it's a conflict status according to https://git-scm.com/docs/git-merge-tree#_mistakes_to_avoid Replace #35542 --------- Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
155 lines
4.5 KiB
Go
155 lines
4.5 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package pull
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
issues_model "code.gitea.io/gitea/models/issues"
|
|
"code.gitea.io/gitea/models/unittest"
|
|
"code.gitea.io/gitea/modules/git/gitcmd"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func testPullRequestMergeCheck(t *testing.T,
|
|
targetFunc func(ctx context.Context, pr *issues_model.PullRequest) error,
|
|
pr *issues_model.PullRequest,
|
|
expectedStatus issues_model.PullRequestStatus,
|
|
expectedConflictedFiles []string,
|
|
expectedChangedProtectedFiles []string,
|
|
) {
|
|
assert.NoError(t, pr.LoadIssue(t.Context()))
|
|
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
|
|
assert.NoError(t, pr.LoadHeadRepo(t.Context()))
|
|
pr.Status = issues_model.PullRequestStatusChecking
|
|
pr.ConflictedFiles = []string{"unrelated-conflicted-file"}
|
|
pr.ChangedProtectedFiles = []string{"unrelated-protected-file"}
|
|
pr.MergeBase = ""
|
|
pr.HeadCommitID = ""
|
|
err := targetFunc(t.Context(), pr)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expectedStatus, pr.Status)
|
|
assert.Equal(t, expectedConflictedFiles, pr.ConflictedFiles)
|
|
assert.Equal(t, expectedChangedProtectedFiles, pr.ChangedProtectedFiles)
|
|
assert.NotEmpty(t, pr.MergeBase)
|
|
assert.NotEmpty(t, pr.HeadCommitID)
|
|
}
|
|
|
|
func TestPullRequestMergeable(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
|
|
t.Run("NoConflict-MergeTree", func(t *testing.T) {
|
|
testPullRequestMergeCheck(t, checkPullRequestMergeableByMergeTree, pr, issues_model.PullRequestStatusMergeable, nil, nil)
|
|
})
|
|
t.Run("NoConflict-TmpRepo", func(t *testing.T) {
|
|
testPullRequestMergeCheck(t, checkPullRequestMergeableByTmpRepo, pr, issues_model.PullRequestStatusMergeable, nil, nil)
|
|
})
|
|
|
|
pr.BaseBranch, pr.HeadBranch = "test-merge-tree-conflict-base", "test-merge-tree-conflict-head"
|
|
conflictFiles := createConflictBranches(t, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.HeadBranch)
|
|
t.Run("Conflict-MergeTree", func(t *testing.T) {
|
|
testPullRequestMergeCheck(t, checkPullRequestMergeableByMergeTree, pr, issues_model.PullRequestStatusConflict, conflictFiles, nil)
|
|
})
|
|
t.Run("Conflict-TmpRepo", func(t *testing.T) {
|
|
testPullRequestMergeCheck(t, checkPullRequestMergeableByTmpRepo, pr, issues_model.PullRequestStatusConflict, conflictFiles, nil)
|
|
})
|
|
|
|
pr.BaseBranch, pr.HeadBranch = "test-merge-tree-empty-base", "test-merge-tree-empty-head"
|
|
createEmptyBranches(t, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.HeadBranch)
|
|
t.Run("Empty-MergeTree", func(t *testing.T) {
|
|
testPullRequestMergeCheck(t, checkPullRequestMergeableByMergeTree, pr, issues_model.PullRequestStatusEmpty, nil, nil)
|
|
})
|
|
t.Run("Empty-TmpRepo", func(t *testing.T) {
|
|
testPullRequestMergeCheck(t, checkPullRequestMergeableByTmpRepo, pr, issues_model.PullRequestStatusEmpty, nil, nil)
|
|
})
|
|
}
|
|
|
|
func createConflictBranches(t *testing.T, repoPath, baseBranch, headBranch string) []string {
|
|
conflictFile := "conflict.txt"
|
|
stdin := fmt.Sprintf(
|
|
`reset refs/heads/%[1]s
|
|
from refs/heads/master
|
|
|
|
commit refs/heads/%[1]s
|
|
mark :1
|
|
committer Test <test@example.com> 0 +0000
|
|
data 17
|
|
add conflict file
|
|
M 100644 inline %[3]s
|
|
data 4
|
|
base
|
|
|
|
commit refs/heads/%[1]s
|
|
mark :2
|
|
committer Test <test@example.com> 0 +0000
|
|
data 11
|
|
base change
|
|
from :1
|
|
M 100644 inline %[3]s
|
|
data 11
|
|
base change
|
|
|
|
reset refs/heads/%[2]s
|
|
from :1
|
|
|
|
commit refs/heads/%[2]s
|
|
mark :3
|
|
committer Test <test@example.com> 0 +0000
|
|
data 11
|
|
head change
|
|
from :1
|
|
M 100644 inline %[3]s
|
|
data 11
|
|
head change
|
|
`, baseBranch, headBranch, conflictFile)
|
|
err := gitcmd.NewCommand("fast-import").WithDir(repoPath).WithStdinBytes([]byte(stdin)).RunWithStderr(t.Context())
|
|
require.NoError(t, err)
|
|
return []string{conflictFile}
|
|
}
|
|
|
|
func createEmptyBranches(t *testing.T, repoPath, baseBranch, headBranch string) {
|
|
emptyFile := "empty.txt"
|
|
stdin := fmt.Sprintf(`reset refs/heads/%[1]s
|
|
from refs/heads/master
|
|
|
|
commit refs/heads/%[1]s
|
|
mark :1
|
|
committer Test <test@example.com> 0 +0000
|
|
data 14
|
|
add empty file
|
|
M 100644 inline %[3]s
|
|
data 4
|
|
base
|
|
|
|
reset refs/heads/%[2]s
|
|
from :1
|
|
|
|
commit refs/heads/%[2]s
|
|
mark :2
|
|
committer Test <test@example.com> 0 +0000
|
|
data 17
|
|
change empty file
|
|
from :1
|
|
M 100644 inline %[3]s
|
|
data 6
|
|
change
|
|
|
|
commit refs/heads/%[2]s
|
|
mark :3
|
|
committer Test <test@example.com> 0 +0000
|
|
data 17
|
|
revert empty file
|
|
from :2
|
|
M 100644 inline %[3]s
|
|
data 4
|
|
base
|
|
`, baseBranch, headBranch, emptyFile)
|
|
err := gitcmd.NewCommand("fast-import").WithDir(repoPath).WithStdinBytes([]byte(stdin)).RunWithStderr(t.Context())
|
|
require.NoError(t, err)
|
|
}
|