mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-22 17:02:41 +00:00
1. use var names "reqOwnerName, reqRepoName", because these values are from request and should not be really used for path construction 2. simplify enable-pprof logic, don't "log.Fatal" 3. don't make runServe command to guess the repo storage path, instead, let server return RepoStoragePath 4. don't process lfs verbs when the repo is a wiki 5. construct the request URI path correctly for the lfs transfer backend (moved to the caller) 6. don't call "owner, err := user_model.GetUserByName", the "owner rename redirection" has been done before 7. fix incorrect "repo.OwnerName = ownerName", the real owner might have been "redirected" 8. fix incorrect "inactive owner" check, it should be checked even if the repo is redirected Use general error functions to handle responses, error handling code is hugely simplified.
958 lines
38 KiB
Go
958 lines
38 KiB
Go
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
mathRand "math/rand/v2"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
auth_model "gitea.dev/models/auth"
|
|
issues_model "gitea.dev/models/issues"
|
|
"gitea.dev/models/perm"
|
|
repo_model "gitea.dev/models/repo"
|
|
"gitea.dev/models/unittest"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/commitstatus"
|
|
"gitea.dev/modules/git"
|
|
"gitea.dev/modules/git/gitcmd"
|
|
"gitea.dev/modules/lfs"
|
|
"gitea.dev/modules/setting"
|
|
api "gitea.dev/modules/structs"
|
|
"gitea.dev/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 with key %d:test-key has no "write" permission for 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").WithDir(dstPath).Run(t.Context())
|
|
assert.NoError(t, err)
|
|
_, _, err = gitcmd.NewCommand("lfs").AddArguments("track").AddDynamicArguments(prefix + "*").
|
|
WithDir(dstPath).RunStdString(t.Context())
|
|
assert.NoError(t, err)
|
|
err = gitAddChangesDeprecated(t.Context(), dstPath, false, ".gitattributes")
|
|
assert.NoError(t, err)
|
|
|
|
err = gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
|
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.MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, testFileSizeSmall, resp.Body.Len())
|
|
|
|
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.MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, testFileSizeLarge, resp.Body.Len())
|
|
|
|
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.MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, testFileSizeSmall, resp.Body.Len())
|
|
|
|
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS))
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, testFileSizeSmall, resp.Body.Len())
|
|
|
|
if !testing.Short() {
|
|
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big))
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, testFileSizeLarge, resp.Body.Len())
|
|
|
|
if setting.LFS.StartServer {
|
|
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS))
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, testFileSizeLarge, resp.Body.Len())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
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").WithDir(repoPath).RunStdString(t.Context())
|
|
assert.NoError(t, err)
|
|
_, _, err = gitcmd.NewCommand("lfs").AddArguments("lock").AddDynamicArguments(filename).WithDir(repoPath).RunStdString(t.Context())
|
|
assert.NoError(t, err)
|
|
_, _, err = gitcmd.NewCommand("lfs").AddArguments("locks").WithDir(repoPath).RunStdString(t.Context())
|
|
assert.NoError(t, err)
|
|
_, _, err = gitcmd.NewCommand("lfs").AddArguments("unlock").AddDynamicArguments(filename).WithDir(repoPath).RunStdString(t.Context())
|
|
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").WithDir(repoPath).RunStdString(t.Context())
|
|
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 = gitAddChangesDeprecated(ctx, repoPath, false, filepath.Base(tmpFile.Name()))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = gitCommitChangesDeprecated(ctx, repoPath, gitCommitChangesOptions{
|
|
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").WithDir(dstPath).Run(t.Context())
|
|
_, 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) {
|
|
formData := map[string]string{
|
|
"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 diffContent string
|
|
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.MakeRequest(t, req, http.StatusOK)
|
|
diffContent = resp.Body.String()
|
|
})
|
|
|
|
// 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, diffContent))
|
|
|
|
// 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, diffContent))
|
|
|
|
// 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, diffContent))
|
|
}
|
|
}
|
|
|
|
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, diffContent string) 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.MakeRequest(t, req, http.StatusOK)
|
|
actual := resp.Body.String()
|
|
assert.Equal(t, diffContent, actual)
|
|
}
|
|
}
|
|
|
|
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) {
|
|
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/branches/delete?name=%s", url.PathEscape(owner), url.PathEscape(repo), url.QueryEscape(branch)))
|
|
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)
|
|
collaboratorCtx := NewAPITestContext(t, "user5", baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository)
|
|
readOnlyCtx := NewAPITestContext(t, "user4", baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository)
|
|
|
|
t.Run("AddAutoMergeCollaborator", doAPIAddCollaborator(*baseCtx, collaboratorCtx.Username, perm.AccessModeWrite))
|
|
t.Run("AddReadOnlyAutoMergeCollaborator", doAPIAddCollaborator(*baseCtx, readOnlyCtx.Username, perm.AccessModeRead))
|
|
|
|
// 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)
|
|
|
|
// Call API to add Pending status for commit
|
|
t.Run("CreateStatus", doAPICreateCommitStatusTest(ctx, commitID, commitstatus.CommitStatusPending, "testci"))
|
|
|
|
// 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))
|
|
|
|
// Read-only collaborator still cannot cancel
|
|
readOnlyCtx.ExpectedCode = http.StatusForbidden
|
|
t.Run("CancelAutoMergePRByReadOnlyCollaboratorForbidden", doAPICancelAutoMergePullRequest(readOnlyCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
|
|
readOnlyCtx.ExpectedCode = 0
|
|
|
|
// Collaborators with merge permissions can cancel a schedule made by someone else
|
|
collaboratorCtx.ExpectedCode = http.StatusNoContent
|
|
t.Run("CancelAutoMergePRByCollaborator", doAPICancelAutoMergePullRequest(collaboratorCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
|
|
collaboratorCtx.ExpectedCode = 0
|
|
|
|
// Re-add auto merge request so the repo owner can cancel it as well
|
|
ctx.ExpectedCode = http.StatusCreated
|
|
t.Run("AutoMergePRAfterCollaboratorCancel", 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))
|
|
|
|
// Collaborator can schedule but admins should still be able to cancel their schedule
|
|
collaboratorCtx.ExpectedCode = http.StatusCreated
|
|
t.Run("AutoMergePRByCollaborator", doAPIAutoMergePullRequest(collaboratorCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
|
|
collaboratorCtx.ExpectedCode = 0
|
|
|
|
ctx.ExpectedCode = http.StatusNoContent
|
|
t.Run("CancelAutoMergePRByAdmin", 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", doAPICreateCommitStatusTest(ctx, commitID, commitstatus.CommitStatusFailure, "testci"))
|
|
|
|
// 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", doAPICreateCommitStatusTest(ctx, commitID, commitstatus.CommitStatusSuccess, "testci"))
|
|
// 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(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 = gitAddChangesDeprecated(t.Context(), dstPath, true)
|
|
assert.NoError(t, err)
|
|
|
|
err = gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
|
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(t.Context(), "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).
|
|
WithDir(dstPath).
|
|
Run(t.Context())
|
|
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).
|
|
WithDir(dstPath).
|
|
RunStdString(t.Context())
|
|
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 = gitAddChangesDeprecated(t.Context(), dstPath, true)
|
|
assert.NoError(t, err)
|
|
|
|
err = gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
|
|
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(t.Context(), "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).
|
|
WithDir(dstPath).
|
|
Run(t.Context())
|
|
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).
|
|
WithDir(dstPath).
|
|
RunStdString(t.Context())
|
|
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"))
|
|
}
|
|
}
|