mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
940 lines
38 KiB
940 lines
38 KiB
// Copyright 2017 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package integration |
|
|
|
import ( |
|
"context" |
|
"encoding/hex" |
|
"fmt" |
|
"io" |
|
mathRand "math/rand/v2" |
|
"net/http" |
|
"net/url" |
|
"os" |
|
"os/exec" |
|
"path" |
|
"path/filepath" |
|
"slices" |
|
"strconv" |
|
"strings" |
|
"testing" |
|
"time" |
|
|
|
auth_model "code.gitea.io/gitea/models/auth" |
|
issues_model "code.gitea.io/gitea/models/issues" |
|
"code.gitea.io/gitea/models/perm" |
|
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/commitstatus" |
|
"code.gitea.io/gitea/modules/git" |
|
"code.gitea.io/gitea/modules/git/gitcmd" |
|
"code.gitea.io/gitea/modules/lfs" |
|
"code.gitea.io/gitea/modules/setting" |
|
api "code.gitea.io/gitea/modules/structs" |
|
"code.gitea.io/gitea/tests" |
|
|
|
"github.com/kballard/go-shellquote" |
|
"github.com/stretchr/testify/assert" |
|
"github.com/stretchr/testify/require" |
|
) |
|
|
|
const ( |
|
testFileSizeSmall = 10 |
|
testFileSizeLarge = 10 * 1024 * 1024 // 10M |
|
) |
|
|
|
func TestGitGeneral(t *testing.T) { |
|
onGiteaRun(t, testGitGeneral) |
|
} |
|
|
|
func testGitGeneral(t *testing.T, u *url.URL) { |
|
username := "user2" |
|
baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) |
|
|
|
u.Path = baseAPITestContext.GitPath() |
|
|
|
forkedUserCtx := NewAPITestContext(t, "user4", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) |
|
|
|
t.Run("HTTP", func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
ensureAnonymousClone(t, u) |
|
httpContext := baseAPITestContext |
|
httpContext.Reponame = "repo-tmp-17" |
|
forkedUserCtx.Reponame = httpContext.Reponame |
|
|
|
dstPath := t.TempDir() |
|
|
|
t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false)) |
|
t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, httpContext.Username, perm.AccessModeRead)) |
|
|
|
t.Run("ForkFromDifferentUser", doAPIForkRepository(httpContext, forkedUserCtx.Username)) |
|
|
|
u.Path = httpContext.GitPath() |
|
u.User = url.UserPassword(username, userPassword) |
|
|
|
t.Run("Clone", doGitClone(dstPath, u)) |
|
|
|
dstPath2 := t.TempDir() |
|
|
|
t.Run("Partial Clone", doPartialGitClone(dstPath2, u)) |
|
|
|
pushedFilesStandard := standardCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge) |
|
pushedFilesLFS := lfsCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge) |
|
rawTest(t, &httpContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1]) |
|
mediaTest(t, &httpContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1]) |
|
|
|
t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "test/head")) |
|
t.Run("CreateProtectedBranch", doCreateProtectedBranch(&httpContext, dstPath)) |
|
t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath)) |
|
t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath)) |
|
t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge")) |
|
t.Run("MergeFork", func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
t.Run("CreatePRAndMerge", doMergeFork(httpContext, forkedUserCtx, "master", httpContext.Username+":master")) |
|
rawTest(t, &forkedUserCtx, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1]) |
|
mediaTest(t, &forkedUserCtx, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1]) |
|
}) |
|
|
|
t.Run("PushCreate", doPushCreate(httpContext, u)) |
|
}) |
|
t.Run("SSH", func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
sshContext := baseAPITestContext |
|
sshContext.Reponame = "repo-tmp-18" |
|
keyname := "my-testing-key" |
|
forkedUserCtx.Reponame = sshContext.Reponame |
|
t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false)) |
|
t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, sshContext.Username, perm.AccessModeRead)) |
|
t.Run("ForkFromDifferentUser", doAPIForkRepository(sshContext, forkedUserCtx.Username)) |
|
|
|
// Setup key the user ssh key |
|
withKeyFile(t, keyname, func(keyFile string) { |
|
var keyID int64 |
|
t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile, func(t *testing.T, key api.PublicKey) { |
|
keyID = key.ID |
|
})) |
|
assert.NotZero(t, keyID) |
|
t.Run("LFSAccessTest", doSSHLFSAccessTest(sshContext, keyID)) |
|
|
|
// Setup remote link |
|
// TODO: get url from api |
|
sshURL := createSSHUrl(sshContext.GitPath(), u) |
|
|
|
// Setup clone folder |
|
dstPath := t.TempDir() |
|
|
|
t.Run("Clone", doGitClone(dstPath, sshURL)) |
|
|
|
pushedFilesStandard := standardCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge) |
|
pushedFilesLFS := lfsCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge) |
|
rawTest(t, &sshContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1]) |
|
mediaTest(t, &sshContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1]) |
|
|
|
t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "test/head2")) |
|
t.Run("CreateProtectedBranch", doCreateProtectedBranch(&sshContext, dstPath)) |
|
t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath)) |
|
t.Run("MergeFork", func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
t.Run("CreatePRAndMerge", doMergeFork(sshContext, forkedUserCtx, "master", sshContext.Username+":master")) |
|
rawTest(t, &forkedUserCtx, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1]) |
|
mediaTest(t, &forkedUserCtx, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1]) |
|
}) |
|
|
|
t.Run("PushCreate", doPushCreate(sshContext, sshURL)) |
|
}) |
|
}) |
|
} |
|
|
|
func doSSHLFSAccessTest(_ APITestContext, keyID int64) func(*testing.T) { |
|
return func(t *testing.T) { |
|
sshCommand := os.Getenv("GIT_SSH_COMMAND") // it is set in withKeyFile |
|
sshCmdParts, err := shellquote.Split(sshCommand) // and parse the ssh command to construct some mocked arguments |
|
require.NoError(t, err) |
|
|
|
t.Run("User2AccessOwned", func(t *testing.T) { |
|
sshCmdUser2Self := append(slices.Clone(sshCmdParts), |
|
"-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost, |
|
"git-lfs-authenticate", "user2/repo1.git", "upload", // accessible to own repo |
|
) |
|
cmd := exec.CommandContext(t.Context(), sshCmdUser2Self[0], sshCmdUser2Self[1:]...) |
|
_, err := cmd.Output() |
|
assert.NoError(t, err) // accessible, no error |
|
}) |
|
|
|
t.Run("User2AccessOther", func(t *testing.T) { |
|
sshCmdUser2Other := append(slices.Clone(sshCmdParts), |
|
"-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost, |
|
"git-lfs-authenticate", "user5/repo4.git", "upload", // inaccessible to other's (user5/repo4) |
|
) |
|
cmd := exec.CommandContext(t.Context(), sshCmdUser2Other[0], sshCmdUser2Other[1:]...) |
|
_, err := cmd.Output() |
|
var errExit *exec.ExitError |
|
require.ErrorAs(t, err, &errExit) // inaccessible, error |
|
assert.Contains(t, string(errExit.Stderr), fmt.Sprintf("User: 2:user2 with Key: %d:test-key is not authorized to write to user5/repo4.", keyID)) |
|
}) |
|
} |
|
} |
|
|
|
func ensureAnonymousClone(t *testing.T, u *url.URL) { |
|
dstLocalPath := t.TempDir() |
|
t.Run("CloneAnonymous", doGitClone(dstLocalPath, u)) |
|
} |
|
|
|
func standardCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFiles []string) { |
|
t.Run("CommitAndPushStandard", func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
pushedFiles = commitAndPushTest(t, dstPath, "data-file-", sizes...) |
|
}) |
|
return pushedFiles |
|
} |
|
|
|
func lfsCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFiles []string) { |
|
t.Run("CommitAndPushLFS", func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
prefix := "lfs-data-file-" |
|
err := gitcmd.NewCommand("lfs").AddArguments("install").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) |
|
assert.NoError(t, err) |
|
_, _, err = gitcmd.NewCommand("lfs").AddArguments("track").AddDynamicArguments(prefix+"*").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) |
|
assert.NoError(t, err) |
|
err = git.AddChanges(t.Context(), dstPath, false, ".gitattributes") |
|
assert.NoError(t, err) |
|
|
|
err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{ |
|
Committer: &git.Signature{ |
|
Email: "user2@example.com", |
|
Name: "User Two", |
|
When: time.Now(), |
|
}, |
|
Author: &git.Signature{ |
|
Email: "user2@example.com", |
|
Name: "User Two", |
|
When: time.Now(), |
|
}, |
|
Message: fmt.Sprintf("Testing commit @ %v", time.Now()), |
|
}) |
|
assert.NoError(t, err) |
|
|
|
pushedFiles = commitAndPushTest(t, dstPath, prefix, sizes...) |
|
t.Run("Locks", func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
lockTest(t, dstPath) |
|
}) |
|
}) |
|
return pushedFiles |
|
} |
|
|
|
func commitAndPushTest(t *testing.T, dstPath, prefix string, sizes ...int) (pushedFiles []string) { |
|
for _, size := range sizes { |
|
t.Run("PushCommit Size-"+strconv.Itoa(size), func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
pushedFiles = append(pushedFiles, doCommitAndPush(t, size, dstPath, prefix)) |
|
}) |
|
} |
|
return pushedFiles |
|
} |
|
|
|
func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) { |
|
t.Run("Raw", func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
username := ctx.Username |
|
reponame := ctx.Reponame |
|
|
|
session := loginUser(t, username) |
|
|
|
// Request raw paths |
|
req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little)) |
|
resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) |
|
assert.Equal(t, testFileSizeSmall, resp.Length) |
|
|
|
if setting.LFS.StartServer { |
|
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS)) |
|
resp := session.MakeRequest(t, req, http.StatusOK) |
|
assert.NotEqual(t, testFileSizeSmall, resp.Body.Len()) |
|
assert.LessOrEqual(t, resp.Body.Len(), 1024) |
|
if resp.Body.Len() != testFileSizeSmall && resp.Body.Len() <= 1024 { |
|
assert.Contains(t, resp.Body.String(), lfs.MetaFileIdentifier) |
|
} |
|
} |
|
|
|
if !testing.Short() { |
|
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big)) |
|
resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) |
|
assert.Equal(t, testFileSizeLarge, resp.Length) |
|
|
|
if setting.LFS.StartServer { |
|
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS)) |
|
resp := session.MakeRequest(t, req, http.StatusOK) |
|
assert.NotEqual(t, testFileSizeLarge, resp.Body.Len()) |
|
if resp.Body.Len() != testFileSizeLarge && resp.Body.Len() <= 1024 { |
|
assert.Contains(t, resp.Body.String(), lfs.MetaFileIdentifier) |
|
} |
|
} |
|
} |
|
}) |
|
} |
|
|
|
func mediaTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) { |
|
t.Run("Media", func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
|
|
username := ctx.Username |
|
reponame := ctx.Reponame |
|
|
|
session := loginUser(t, username) |
|
|
|
// Request media paths |
|
req := NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", little)) |
|
resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) |
|
assert.Equal(t, testFileSizeSmall, resp.Length) |
|
|
|
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS)) |
|
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) |
|
assert.Equal(t, testFileSizeSmall, resp.Length) |
|
|
|
if !testing.Short() { |
|
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big)) |
|
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) |
|
assert.Equal(t, testFileSizeLarge, resp.Length) |
|
|
|
if setting.LFS.StartServer { |
|
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS)) |
|
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) |
|
assert.Equal(t, testFileSizeLarge, resp.Length) |
|
} |
|
} |
|
}) |
|
} |
|
|
|
func lockTest(t *testing.T, repoPath string) { |
|
lockFileTest(t, "README.md", repoPath) |
|
} |
|
|
|
func lockFileTest(t *testing.T, filename, repoPath string) { |
|
_, _, err := gitcmd.NewCommand("lfs").AddArguments("locks").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) |
|
assert.NoError(t, err) |
|
_, _, err = gitcmd.NewCommand("lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) |
|
assert.NoError(t, err) |
|
_, _, err = gitcmd.NewCommand("lfs").AddArguments("locks").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) |
|
assert.NoError(t, err) |
|
_, _, err = gitcmd.NewCommand("lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) |
|
assert.NoError(t, err) |
|
} |
|
|
|
func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string { |
|
name, err := generateCommitWithNewData(t.Context(), size, repoPath, "user2@example.com", "User Two", prefix) |
|
assert.NoError(t, err) |
|
_, _, err = gitcmd.NewCommand("push", "origin", "master").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) // Push |
|
assert.NoError(t, err) |
|
return name |
|
} |
|
|
|
func generateCommitWithNewData(ctx context.Context, size int, repoPath, email, fullName, prefix string) (string, error) { |
|
tmpFile, err := os.CreateTemp(repoPath, prefix) |
|
if err != nil { |
|
return "", err |
|
} |
|
defer tmpFile.Close() |
|
|
|
var seed [32]byte |
|
rander := mathRand.NewChaCha8(seed) // for testing only, no need to seed |
|
_, err = io.CopyN(tmpFile, rander, int64(size)) |
|
if err != nil { |
|
return "", err |
|
} |
|
_ = tmpFile.Close() |
|
|
|
// Commit |
|
err = git.AddChanges(ctx, repoPath, false, filepath.Base(tmpFile.Name())) |
|
if err != nil { |
|
return "", err |
|
} |
|
err = git.CommitChanges(ctx, repoPath, git.CommitChangesOptions{ |
|
Committer: &git.Signature{ |
|
Email: email, |
|
Name: fullName, |
|
When: time.Now(), |
|
}, |
|
Author: &git.Signature{ |
|
Email: email, |
|
Name: fullName, |
|
When: time.Now(), |
|
}, |
|
Message: fmt.Sprintf("Testing commit @ %v", time.Now()), |
|
}) |
|
return filepath.Base(tmpFile.Name()), err |
|
} |
|
|
|
func doCreateProtectedBranch(baseCtx *APITestContext, dstPath string) func(t *testing.T) { |
|
return func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) |
|
|
|
t.Run("ProtectBranchWithFilePatterns", doProtectBranch(ctx, "release-*", baseCtx.Username, "", "", "config*")) |
|
|
|
// push a new branch without any new commits |
|
t.Run("CreateProtectedBranch-NoChanges", doGitCreateBranch(dstPath, "release-v1.0")) |
|
t.Run("PushProtectedBranch-NoChanges", doGitPushTestRepository(dstPath, "origin", "release-v1.0")) |
|
t.Run("CheckoutMaster-NoChanges", doGitCheckoutBranch(dstPath, "master")) |
|
|
|
// push a new branch with a new unprotected file |
|
t.Run("CreateProtectedBranch-UnprotectedFile", doGitCreateBranch(dstPath, "release-v2.0")) |
|
_, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "abc.txt") |
|
assert.NoError(t, err) |
|
t.Run("PushProtectedBranch-UnprotectedFile", doGitPushTestRepository(dstPath, "origin", "release-v2.0")) |
|
t.Run("CheckoutMaster-UnprotectedFile", doGitCheckoutBranch(dstPath, "master")) |
|
|
|
// push a new branch with a new protected file |
|
t.Run("CreateProtectedBranch-ProtectedFile", doGitCreateBranch(dstPath, "release-v3.0")) |
|
_, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "config") |
|
assert.NoError(t, err) |
|
t.Run("PushProtectedBranch-ProtectedFile", doGitPushTestRepositoryFail(dstPath, "origin", "release-v3.0")) |
|
t.Run("CheckoutMaster-ProtectedFile", doGitCheckoutBranch(dstPath, "master")) |
|
} |
|
} |
|
|
|
func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { |
|
return func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
t.Run("CreateBranchProtected", doGitCreateBranch(dstPath, "protected")) |
|
t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) |
|
|
|
ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) |
|
|
|
// Protect branch without any whitelisting |
|
t.Run("ProtectBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "", "", "")) |
|
|
|
// Try to push without permissions, which should fail |
|
t.Run("TryPushWithoutPermissions", func(t *testing.T) { |
|
_, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-") |
|
assert.NoError(t, err) |
|
doGitPushTestRepositoryFail(dstPath, "origin", "protected")(t) |
|
}) |
|
|
|
// Set up permissions for normal push but not force push |
|
t.Run("SetupNormalPushPermissions", doProtectBranch(ctx, "protected", baseCtx.Username, "", "", "")) |
|
|
|
// Normal push should work |
|
t.Run("NormalPushWithPermissions", func(t *testing.T) { |
|
_, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-") |
|
assert.NoError(t, err) |
|
doGitPushTestRepository(dstPath, "origin", "protected")(t) |
|
}) |
|
|
|
// Try to force push without force push permissions, which should fail |
|
t.Run("ForcePushWithoutForcePermissions", func(t *testing.T) { |
|
t.Run("CreateDivergentHistory", func(t *testing.T) { |
|
gitcmd.NewCommand("reset", "--hard", "HEAD~1").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) |
|
_, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-new") |
|
assert.NoError(t, err) |
|
}) |
|
doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")(t) |
|
}) |
|
|
|
// Set up permissions for force push but not normal push |
|
t.Run("SetupForcePushPermissions", doProtectBranch(ctx, "protected", "", baseCtx.Username, "", "")) |
|
|
|
// Try to force push without normal push permissions, which should fail |
|
t.Run("ForcePushWithoutNormalPermissions", doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")) |
|
|
|
// Set up permissions for normal and force push (both are required to force push) |
|
t.Run("SetupNormalAndForcePushPermissions", doProtectBranch(ctx, "protected", baseCtx.Username, baseCtx.Username, "", "")) |
|
|
|
// Force push should now work |
|
t.Run("ForcePushWithPermissions", doGitPushTestRepository(dstPath, "-f", "origin", "protected")) |
|
|
|
t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "", "", "")) |
|
t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected")) |
|
var pr api.PullRequest |
|
var err error |
|
t.Run("CreatePullRequest", func(t *testing.T) { |
|
pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected")(t) |
|
assert.NoError(t, err) |
|
}) |
|
t.Run("GenerateCommit", func(t *testing.T) { |
|
_, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-") |
|
assert.NoError(t, err) |
|
}) |
|
t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected-2")) |
|
var pr2 api.PullRequest |
|
t.Run("CreatePullRequest", func(t *testing.T) { |
|
pr2, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "unprotected", "unprotected-2")(t) |
|
assert.NoError(t, err) |
|
}) |
|
t.Run("MergePR2", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr2.Index)) |
|
t.Run("MergePR", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) |
|
t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) |
|
|
|
t.Run("ProtectProtectedBranchUnprotectedFilePaths", doProtectBranch(ctx, "protected", "", "", "unprotected-file-*", "")) |
|
t.Run("GenerateCommit", func(t *testing.T) { |
|
_, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "unprotected-file-") |
|
assert.NoError(t, err) |
|
}) |
|
t.Run("PushUnprotectedFilesToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) |
|
|
|
t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username, "", "", "")) |
|
|
|
t.Run("CheckoutMaster", doGitCheckoutBranch(dstPath, "master")) |
|
t.Run("CreateBranchForced", doGitCreateBranch(dstPath, "toforce")) |
|
t.Run("GenerateCommit", func(t *testing.T) { |
|
_, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-") |
|
assert.NoError(t, err) |
|
}) |
|
t.Run("FailToForcePushToProtectedBranch", doGitPushTestRepositoryFail(dstPath, "-f", "origin", "toforce:protected")) |
|
t.Run("MergeProtectedToToforce", doGitMerge(dstPath, "protected")) |
|
t.Run("PushToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "toforce:protected")) |
|
t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master")) |
|
} |
|
} |
|
|
|
func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhitelistForcePush, unprotectedFilePatterns, protectedFilePatterns string) func(t *testing.T) { |
|
return doProtectBranchExt(ctx, branch, doProtectBranchOptions{ |
|
UserToWhitelistPush: userToWhitelistPush, |
|
UserToWhitelistForcePush: userToWhitelistForcePush, |
|
UnprotectedFilePatterns: unprotectedFilePatterns, |
|
ProtectedFilePatterns: protectedFilePatterns, |
|
}) |
|
} |
|
|
|
type doProtectBranchOptions struct { |
|
UserToWhitelistPush, UserToWhitelistForcePush, UnprotectedFilePatterns, ProtectedFilePatterns string |
|
|
|
StatusCheckPatterns []string |
|
} |
|
|
|
func doProtectBranchExt(ctx APITestContext, ruleName string, opts doProtectBranchOptions) func(t *testing.T) { |
|
// We are going to just use the owner to set the protection. |
|
return func(t *testing.T) { |
|
csrf := GetUserCSRFToken(t, ctx.Session) |
|
|
|
formData := map[string]string{ |
|
"_csrf": csrf, |
|
"rule_name": ruleName, |
|
"unprotected_file_patterns": opts.UnprotectedFilePatterns, |
|
"protected_file_patterns": opts.ProtectedFilePatterns, |
|
} |
|
|
|
if opts.UserToWhitelistPush != "" { |
|
user, err := user_model.GetUserByName(t.Context(), opts.UserToWhitelistPush) |
|
assert.NoError(t, err) |
|
formData["whitelist_users"] = strconv.FormatInt(user.ID, 10) |
|
formData["enable_push"] = "whitelist" |
|
formData["enable_whitelist"] = "on" |
|
} |
|
|
|
if opts.UserToWhitelistForcePush != "" { |
|
user, err := user_model.GetUserByName(t.Context(), opts.UserToWhitelistForcePush) |
|
assert.NoError(t, err) |
|
formData["force_push_allowlist_users"] = strconv.FormatInt(user.ID, 10) |
|
formData["enable_force_push"] = "whitelist" |
|
formData["enable_force_push_allowlist"] = "on" |
|
} |
|
|
|
if len(opts.StatusCheckPatterns) > 0 { |
|
formData["enable_status_check"] = "on" |
|
formData["status_check_contexts"] = strings.Join(opts.StatusCheckPatterns, "\n") |
|
} |
|
|
|
// Send the request to update branch protection settings |
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), formData) |
|
ctx.Session.MakeRequest(t, req, http.StatusSeeOther) |
|
|
|
// Check if the "master" branch has been locked successfully |
|
flashMsg := ctx.Session.GetCookieFlashMessage() |
|
assert.Equal(t, `Branch protection for rule "`+ruleName+`" has been updated.`, flashMsg.SuccessMsg) |
|
} |
|
} |
|
|
|
func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) func(t *testing.T) { |
|
return func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
var pr api.PullRequest |
|
var err error |
|
|
|
// Create a test pullrequest |
|
t.Run("CreatePullRequest", func(t *testing.T) { |
|
pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t) |
|
assert.NoError(t, err) |
|
}) |
|
|
|
// Ensure the PR page works |
|
t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) |
|
|
|
// Then get the diff string |
|
var diffHash string |
|
var diffLength int |
|
t.Run("GetDiff", func(t *testing.T) { |
|
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(baseCtx.Username), url.PathEscape(baseCtx.Reponame), pr.Index)) |
|
resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK) |
|
diffHash = string(resp.Hash.Sum(nil)) |
|
diffLength = resp.Length |
|
}) |
|
|
|
// Now: Merge the PR & make sure that doesn't break the PR page or change its diff |
|
t.Run("MergePR", doAPIMergePullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)) |
|
t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) |
|
t.Run("CheckPR", func(t *testing.T) { |
|
oldMergeBase := pr.MergeBase |
|
pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) |
|
assert.NoError(t, err) |
|
assert.Equal(t, oldMergeBase, pr2.MergeBase) |
|
}) |
|
t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) |
|
|
|
// Then: Delete the head branch & make sure that doesn't break the PR page or change its diff |
|
t.Run("DeleteHeadBranch", doBranchDelete(baseCtx, baseCtx.Username, baseCtx.Reponame, headBranch)) |
|
t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) |
|
t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) |
|
|
|
// Delete the head repository & make sure that doesn't break the PR page or change its diff |
|
t.Run("DeleteHeadRepository", doAPIDeleteRepository(ctx)) |
|
t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) |
|
t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) |
|
} |
|
} |
|
|
|
func doCreatePRAndSetManuallyMerged(ctx, baseCtx APITestContext, dstPath, baseBranch, headBranch string) func(t *testing.T) { |
|
return func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
var ( |
|
pr api.PullRequest |
|
err error |
|
lastCommitID string |
|
) |
|
|
|
trueBool := true |
|
falseBool := false |
|
|
|
t.Run("AllowSetManuallyMergedAndSwitchOffAutodetectManualMerge", doAPIEditRepository(baseCtx, &api.EditRepoOption{ |
|
HasPullRequests: &trueBool, |
|
AllowManualMerge: &trueBool, |
|
AutodetectManualMerge: &falseBool, |
|
})) |
|
|
|
t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) |
|
t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", headBranch)) |
|
t.Run("CreateEmptyPullRequest", func(t *testing.T) { |
|
pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t) |
|
assert.NoError(t, err) |
|
}) |
|
lastCommitID = pr.Base.Sha |
|
t.Run("ManuallyMergePR", doAPIManuallyMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, lastCommitID, pr.Index)) |
|
} |
|
} |
|
|
|
func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest) func(t *testing.T) { |
|
return func(t *testing.T) { |
|
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) |
|
ctx.Session.MakeRequest(t, req, http.StatusOK) |
|
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/files", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) |
|
ctx.Session.MakeRequest(t, req, http.StatusOK) |
|
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) |
|
ctx.Session.MakeRequest(t, req, http.StatusOK) |
|
} |
|
} |
|
|
|
func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffHash string, diffLength int) func(t *testing.T) { |
|
return func(t *testing.T) { |
|
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) |
|
resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK) |
|
actual := string(resp.Hash.Sum(nil)) |
|
actualLength := resp.Length |
|
|
|
equal := diffHash == actual |
|
assert.True(t, equal, "Unexpected change in the diff string: expected hash: %s size: %d but was actually: %s size: %d", hex.EncodeToString([]byte(diffHash)), diffLength, hex.EncodeToString([]byte(actual)), actualLength) |
|
} |
|
} |
|
|
|
func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { |
|
return func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
|
|
// create a context for a currently non-existent repository |
|
ctx.Reponame = "repo-tmp-push-create-" + u.Scheme |
|
u.Path = ctx.GitPath() |
|
|
|
// Create a temporary directory |
|
tmpDir := t.TempDir() |
|
|
|
// Now create local repository to push as our test and set its origin |
|
t.Run("InitTestRepository", doGitInitTestRepository(tmpDir)) |
|
t.Run("AddRemote", doGitAddRemote(tmpDir, "origin", u)) |
|
|
|
// Disable "Push To Create" and attempt to push |
|
setting.Repository.EnablePushCreateUser = false |
|
t.Run("FailToPushAndCreateTestRepository", doGitPushTestRepositoryFail(tmpDir, "origin", "master")) |
|
|
|
// Enable "Push To Create" |
|
setting.Repository.EnablePushCreateUser = true |
|
|
|
// Assert that cloning from a non-existent repository does not create it and that it definitely wasn't create above |
|
t.Run("FailToCloneFromNonExistentRepository", doGitCloneFail(u)) |
|
|
|
// Then "Push To Create"x |
|
t.Run("SuccessfullyPushAndCreateTestRepository", doGitPushTestRepository(tmpDir, "origin", "master")) |
|
|
|
// Finally, fetch repo from database and ensure the correct repository has been created |
|
repo, err := repo_model.GetRepositoryByOwnerAndName(t.Context(), ctx.Username, ctx.Reponame) |
|
assert.NoError(t, err) |
|
assert.False(t, repo.IsEmpty) |
|
assert.True(t, repo.IsPrivate) |
|
|
|
// Now add a remote that is invalid to "Push To Create" |
|
invalidCtx := ctx |
|
invalidCtx.Reponame = "invalid/repo-tmp-push-create-" + u.Scheme |
|
u.Path = invalidCtx.GitPath() |
|
t.Run("AddInvalidRemote", doGitAddRemote(tmpDir, "invalid", u)) |
|
|
|
// Fail to "Push To Create" the invalid |
|
t.Run("FailToPushAndCreateInvalidTestRepository", doGitPushTestRepositoryFail(tmpDir, "invalid", "master")) |
|
} |
|
} |
|
|
|
func doBranchDelete(ctx APITestContext, owner, repo, branch string) func(*testing.T) { |
|
return func(t *testing.T) { |
|
csrf := GetUserCSRFToken(t, ctx.Session) |
|
|
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/branches/delete?name=%s", url.PathEscape(owner), url.PathEscape(repo), url.QueryEscape(branch)), map[string]string{ |
|
"_csrf": csrf, |
|
}) |
|
ctx.Session.MakeRequest(t, req, http.StatusOK) |
|
} |
|
} |
|
|
|
func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { |
|
return func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
|
|
ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) |
|
|
|
// automerge will merge immediately if the PR is mergeable and there is no "status check" because no status check also means "all checks passed" |
|
// so we must set up a status check to test the auto merge feature |
|
doProtectBranchExt(ctx, "protected", doProtectBranchOptions{StatusCheckPatterns: []string{"*"}})(t) |
|
|
|
t.Run("CheckoutProtected", doGitCheckoutBranch(dstPath, "protected")) |
|
t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) |
|
t.Run("GenerateCommit", func(t *testing.T) { |
|
_, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-") |
|
assert.NoError(t, err) |
|
}) |
|
t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected3")) |
|
var pr api.PullRequest |
|
var err error |
|
t.Run("CreatePullRequest", func(t *testing.T) { |
|
pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected3")(t) |
|
assert.NoError(t, err) |
|
}) |
|
|
|
// Request repository commits page |
|
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", baseCtx.Username, baseCtx.Reponame, pr.Index)) |
|
resp := ctx.Session.MakeRequest(t, req, http.StatusOK) |
|
doc := NewHTMLParser(t, resp.Body) |
|
|
|
// Get first commit URL |
|
commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") |
|
assert.True(t, exists) |
|
assert.NotEmpty(t, commitURL) |
|
|
|
commitID := path.Base(commitURL) |
|
|
|
addCommitStatus := func(status commitstatus.CommitStatusState) func(*testing.T) { |
|
return doAPICreateCommitStatus(ctx, commitID, api.CreateStatusOption{ |
|
State: status, |
|
TargetURL: "http://test.ci/", |
|
Description: "", |
|
Context: "testci", |
|
}) |
|
} |
|
|
|
// Call API to add Pending status for commit |
|
t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusPending)) |
|
|
|
// Cancel not existing auto merge |
|
ctx.ExpectedCode = http.StatusNotFound |
|
t.Run("CancelAutoMergePRNotExist", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) |
|
|
|
// Add auto merge request |
|
ctx.ExpectedCode = http.StatusCreated |
|
t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) |
|
|
|
// Cannot create schedule twice |
|
ctx.ExpectedCode = http.StatusConflict |
|
t.Run("AutoMergePRTwice", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) |
|
|
|
// Cancel auto merge request |
|
ctx.ExpectedCode = http.StatusNoContent |
|
t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) |
|
|
|
// Add auto merge request |
|
ctx.ExpectedCode = http.StatusCreated |
|
t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) |
|
|
|
// Check pr status |
|
ctx.ExpectedCode = 0 |
|
pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) |
|
assert.NoError(t, err) |
|
assert.False(t, pr.HasMerged) |
|
|
|
// Call API to add Failure status for commit |
|
t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusFailure)) |
|
|
|
// Check pr status |
|
pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) |
|
assert.NoError(t, err) |
|
assert.False(t, pr.HasMerged) |
|
|
|
// Call API to add Success status for commit |
|
t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusSuccess)) |
|
|
|
// wait to let gitea merge stuff |
|
time.Sleep(time.Second) |
|
|
|
// test pr status |
|
pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) |
|
assert.NoError(t, err) |
|
assert.True(t, pr.HasMerged) |
|
} |
|
} |
|
|
|
func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string) func(t *testing.T) { |
|
return func(t *testing.T) { |
|
defer tests.PrintCurrentTest(t)() |
|
|
|
// skip this test if git version is low |
|
if !git.DefaultFeatures().SupportProcReceive { |
|
return |
|
} |
|
|
|
gitRepo, err := git.OpenRepository(t.Context(), dstPath) |
|
require.NoError(t, err) |
|
|
|
defer gitRepo.Close() |
|
|
|
var ( |
|
pr1, pr2 *issues_model.PullRequest |
|
commit string |
|
) |
|
repo, err := repo_model.GetRepositoryByOwnerAndName(t.Context(), ctx.Username, ctx.Reponame) |
|
require.NoError(t, err) |
|
|
|
pullNum := unittest.GetCount(t, &issues_model.PullRequest{}) |
|
|
|
t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) |
|
|
|
t.Run("AddCommit", func(t *testing.T) { |
|
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666) |
|
require.NoError(t, err) |
|
|
|
err = git.AddChanges(t.Context(), dstPath, true) |
|
assert.NoError(t, err) |
|
|
|
err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{ |
|
Committer: &git.Signature{ |
|
Email: "user2@example.com", |
|
Name: "user2", |
|
When: time.Now(), |
|
}, |
|
Author: &git.Signature{ |
|
Email: "user2@example.com", |
|
Name: "user2", |
|
When: time.Now(), |
|
}, |
|
Message: "Testing commit 1", |
|
}) |
|
assert.NoError(t, err) |
|
commit, err = gitRepo.GetRefCommitID("HEAD") |
|
assert.NoError(t, err) |
|
}) |
|
|
|
t.Run("Push", func(t *testing.T) { |
|
err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) |
|
require.NoError(t, err) |
|
|
|
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1) |
|
pr1 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ |
|
HeadRepoID: repo.ID, |
|
Flow: issues_model.PullRequestFlowAGit, |
|
}) |
|
require.NotEmpty(t, pr1) |
|
|
|
prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) |
|
require.NoError(t, err) |
|
|
|
assert.Equal(t, "user2/"+headBranch, pr1.HeadBranch) |
|
assert.False(t, prMsg.HasMerged) |
|
assert.Contains(t, "Testing commit 1", prMsg.Body) |
|
assert.Equal(t, commit, prMsg.Head.Sha) |
|
|
|
_, _, err = gitcmd.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) |
|
require.NoError(t, err) |
|
|
|
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) |
|
pr2 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ |
|
HeadRepoID: repo.ID, |
|
Index: pr1.Index + 1, |
|
Flow: issues_model.PullRequestFlowAGit, |
|
}) |
|
require.NotEmpty(t, pr2) |
|
|
|
prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) |
|
require.NoError(t, err) |
|
|
|
assert.Equal(t, "user2/test/"+headBranch, pr2.HeadBranch) |
|
assert.False(t, prMsg.HasMerged) |
|
}) |
|
|
|
if pr1 == nil || pr2 == nil { |
|
return |
|
} |
|
|
|
t.Run("AddCommit2", func(t *testing.T) { |
|
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0o666) |
|
require.NoError(t, err) |
|
|
|
err = git.AddChanges(t.Context(), dstPath, true) |
|
assert.NoError(t, err) |
|
|
|
err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{ |
|
Committer: &git.Signature{ |
|
Email: "user2@example.com", |
|
Name: "user2", |
|
When: time.Now(), |
|
}, |
|
Author: &git.Signature{ |
|
Email: "user2@example.com", |
|
Name: "user2", |
|
When: time.Now(), |
|
}, |
|
Message: "Testing commit 2", |
|
}) |
|
assert.NoError(t, err) |
|
commit, err = gitRepo.GetRefCommitID("HEAD") |
|
assert.NoError(t, err) |
|
}) |
|
|
|
t.Run("Push2", func(t *testing.T) { |
|
err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) |
|
require.NoError(t, err) |
|
|
|
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) |
|
prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) |
|
require.NoError(t, err) |
|
|
|
assert.False(t, prMsg.HasMerged) |
|
assert.Equal(t, commit, prMsg.Head.Sha) |
|
|
|
_, _, err = gitcmd.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) |
|
require.NoError(t, err) |
|
|
|
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) |
|
prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) |
|
require.NoError(t, err) |
|
|
|
assert.False(t, prMsg.HasMerged) |
|
assert.Equal(t, commit, prMsg.Head.Sha) |
|
}) |
|
t.Run("Merge", doAPIMergePullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)) |
|
t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master")) |
|
} |
|
}
|
|
|