mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
105 lines
4.3 KiB
Go
105 lines
4.3 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
auth_model "github.com/hanzoai/git/models/auth"
|
|
issues_model "github.com/hanzoai/git/models/issues"
|
|
repo_model "github.com/hanzoai/git/models/repo"
|
|
"github.com/hanzoai/git/models/unittest"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
"github.com/hanzoai/git/modules/git/gitcmd"
|
|
"github.com/hanzoai/git/modules/setting"
|
|
api "github.com/hanzoai/git/modules/structs"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestManualMergeAutodetect(t *testing.T) {
|
|
onGitRun(t, func(t *testing.T, gitURL *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})
|
|
|
|
// Create a repo owned by user2
|
|
repoName := "manual-merge-autodetect"
|
|
defaultBranch := setting.Repository.DefaultBranch
|
|
user2Ctx := NewAPITestContext(t, user2.Name, repoName, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
|
doAPICreateRepository(user2Ctx, false)(t)
|
|
|
|
// Enable autodetect manual merge
|
|
doAPIEditRepository(user2Ctx, &api.EditRepoOption{
|
|
HasPullRequests: new(true),
|
|
AllowManualMerge: new(true),
|
|
AutodetectManualMerge: new(true),
|
|
})(t)
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: repoName})
|
|
user1Ctx := NewAPITestContext(t, user1.Name, repoName, auth_model.AccessTokenScopeWriteRepository)
|
|
|
|
// 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, 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(gitURL.String())
|
|
u.Path = fmt.Sprintf("%s/%s.git", user2.Name, repoName)
|
|
u.User = url.UserPassword(user1.Name, userPassword)
|
|
|
|
doGitClone(dstPath, u)(t)
|
|
|
|
// Capture each branch's expected merge commit hash from the local clone,
|
|
// so we can assert that the forge 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)
|
|
|
|
// Every PR should be detected as manually merged by the background task, not just the last one.
|
|
require.Eventually(t, func() bool {
|
|
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)
|
|
|
|
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])
|
|
}
|
|
})
|
|
}
|