mirror of
https://github.com/go-gitea/gitea
synced 2024-12-22 16:07:52 +01:00
12cb1d2998
Fixes #22722 ### Problem Currently, it is not possible to force push to a branch with branch protection rules in place. There are often times where this is necessary (CI workflows/administrative tasks etc). The current workaround is to rename/remove the branch protection, perform the force push, and then reinstate the protections. ### Solution Provide an additional section in the branch protection rules to allow users to specify which users with push access can also force push to the branch. The default value of the rule will be set to `Disabled`, and the UI is intuitive and very similar to the `Push` section. It is worth noting in this implementation that allowing force push does not override regular push access, and both will need to be enabled for a user to force push. This applies to manual force push to a remote, and also in Gitea UI updating a PR by rebase (which requires force push) This modifies the `BranchProtection` API structs to add: - `enable_force_push bool` - `enable_force_push_whitelist bool` - `force_push_whitelist_usernames string[]` - `force_push_whitelist_teams string[]` - `force_push_whitelist_deploy_keys bool` ### Updated Branch Protection UI: <img width="943" alt="image" src="https://github.com/go-gitea/gitea/assets/79623665/7491899c-d816-45d5-be84-8512abd156bf"> ### Pull Request `Update branch by Rebase` option enabled with source branch `test` being a protected branch: ![image](https://github.com/go-gitea/gitea/assets/79623665/e018e6e9-b7b2-4bd3-808e-4947d7da35cc) <img width="1038" alt="image" src="https://github.com/go-gitea/gitea/assets/79623665/57ead13e-9006-459f-b83c-7079e6f4c654"> --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
946 lines
35 KiB
Go
946 lines
35 KiB
Go
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
auth_model "code.gitea.io/gitea/models/auth"
|
|
"code.gitea.io/gitea/models/db"
|
|
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/git"
|
|
"code.gitea.io/gitea/modules/gitrepo"
|
|
"code.gitea.io/gitea/modules/lfs"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
gitea_context "code.gitea.io/gitea/services/context"
|
|
files_service "code.gitea.io/gitea/services/repository/files"
|
|
"code.gitea.io/gitea/tests"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const (
|
|
littleSize = 1024 // 1ko
|
|
bigSize = 128 * 1024 * 1024 // 128Mo
|
|
)
|
|
|
|
func TestGit(t *testing.T) {
|
|
onGiteaRun(t, testGit)
|
|
}
|
|
|
|
func testGit(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))
|
|
|
|
little, big := standardCommitAndPushTest(t, dstPath)
|
|
littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
|
|
rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
|
|
mediaTest(t, &httpContext, little, big, littleLFS, bigLFS)
|
|
|
|
t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "test/head"))
|
|
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, little, big, littleLFS, bigLFS)
|
|
mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
|
|
})
|
|
|
|
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) {
|
|
t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile))
|
|
|
|
// 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))
|
|
|
|
little, big := standardCommitAndPushTest(t, dstPath)
|
|
littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
|
|
rawTest(t, &sshContext, little, big, littleLFS, bigLFS)
|
|
mediaTest(t, &sshContext, little, big, littleLFS, bigLFS)
|
|
|
|
t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "test/head2"))
|
|
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, little, big, littleLFS, bigLFS)
|
|
mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS)
|
|
})
|
|
|
|
t.Run("PushCreate", doPushCreate(sshContext, sshURL))
|
|
})
|
|
})
|
|
}
|
|
|
|
func ensureAnonymousClone(t *testing.T, u *url.URL) {
|
|
dstLocalPath := t.TempDir()
|
|
t.Run("CloneAnonymous", doGitClone(dstLocalPath, u))
|
|
}
|
|
|
|
func standardCommitAndPushTest(t *testing.T, dstPath string) (little, big string) {
|
|
t.Run("Standard", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
little, big = commitAndPushTest(t, dstPath, "data-file-")
|
|
})
|
|
return little, big
|
|
}
|
|
|
|
func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS string) {
|
|
t.Run("LFS", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
prefix := "lfs-data-file-"
|
|
err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").Run(&git.RunOpts{Dir: dstPath})
|
|
assert.NoError(t, err)
|
|
_, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("track").AddDynamicArguments(prefix + "*").RunStdString(&git.RunOpts{Dir: dstPath})
|
|
assert.NoError(t, err)
|
|
err = git.AddChanges(dstPath, false, ".gitattributes")
|
|
assert.NoError(t, err)
|
|
|
|
err = git.CommitChangesWithArgs(dstPath, git.AllowLFSFiltersArgs(), 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)
|
|
|
|
littleLFS, bigLFS = commitAndPushTest(t, dstPath, prefix)
|
|
|
|
t.Run("Locks", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
lockTest(t, dstPath)
|
|
})
|
|
})
|
|
return littleLFS, bigLFS
|
|
}
|
|
|
|
func commitAndPushTest(t *testing.T, dstPath, prefix string) (little, big string) {
|
|
t.Run("PushCommit", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
t.Run("Little", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
little = doCommitAndPush(t, littleSize, dstPath, prefix)
|
|
})
|
|
t.Run("Big", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping test in short mode.")
|
|
return
|
|
}
|
|
defer tests.PrintCurrentTest(t)()
|
|
big = doCommitAndPush(t, bigSize, dstPath, prefix)
|
|
})
|
|
})
|
|
return little, big
|
|
}
|
|
|
|
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, littleSize, 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, littleSize, resp.Body.Len())
|
|
assert.LessOrEqual(t, resp.Body.Len(), 1024)
|
|
if resp.Body.Len() != littleSize && 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, bigSize, 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, bigSize, resp.Body.Len())
|
|
if resp.Body.Len() != bigSize && 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, littleSize, resp.Length)
|
|
|
|
req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS))
|
|
resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
|
|
assert.Equal(t, littleSize, 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, bigSize, 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, bigSize, resp.Length)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func lockTest(t *testing.T, repoPath string) {
|
|
lockFileTest(t, "README.md", repoPath)
|
|
}
|
|
|
|
func lockFileTest(t *testing.T, filename, repoPath string) {
|
|
_, _, err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath})
|
|
assert.NoError(t, err)
|
|
_, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath})
|
|
assert.NoError(t, err)
|
|
_, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath})
|
|
assert.NoError(t, err)
|
|
_, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string {
|
|
name, err := generateCommitWithNewData(size, repoPath, "user2@example.com", "User Two", prefix)
|
|
assert.NoError(t, err)
|
|
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "master").RunStdString(&git.RunOpts{Dir: repoPath}) // Push
|
|
assert.NoError(t, err)
|
|
return name
|
|
}
|
|
|
|
func generateCommitWithNewData(size int, repoPath, email, fullName, prefix string) (string, error) {
|
|
// Generate random file
|
|
bufSize := 4 * 1024
|
|
if bufSize > size {
|
|
bufSize = size
|
|
}
|
|
|
|
buffer := make([]byte, bufSize)
|
|
|
|
tmpFile, err := os.CreateTemp(repoPath, prefix)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer tmpFile.Close()
|
|
written := 0
|
|
for written < size {
|
|
n := size - written
|
|
if n > bufSize {
|
|
n = bufSize
|
|
}
|
|
_, err := rand.Read(buffer[:n])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
n, err = tmpFile.Write(buffer[:n])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
written += n
|
|
}
|
|
|
|
// Commit
|
|
// Now here we should explicitly allow lfs filters to run
|
|
globalArgs := git.AllowLFSFiltersArgs()
|
|
err = git.AddChangesWithArgs(repoPath, globalArgs, false, filepath.Base(tmpFile.Name()))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = git.CommitChangesWithArgs(repoPath, globalArgs, 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 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", func(t *testing.T) {
|
|
doProtectBranch(ctx, "protected", "", "", "")
|
|
})
|
|
|
|
// Try to push without permissions, which should fail
|
|
t.Run("TryPushWithoutPermissions", func(t *testing.T) {
|
|
_, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
|
|
assert.NoError(t, err)
|
|
doGitPushTestRepositoryFail(dstPath, "origin", "protected")
|
|
})
|
|
|
|
// Set up permissions for normal push but not force push
|
|
t.Run("SetupNormalPushPermissions", func(t *testing.T) {
|
|
doProtectBranch(ctx, "protected", baseCtx.Username, "", "")
|
|
})
|
|
|
|
// Normal push should work
|
|
t.Run("NormalPushWithPermissions", func(t *testing.T) {
|
|
_, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
|
|
assert.NoError(t, err)
|
|
doGitPushTestRepository(dstPath, "origin", "protected")
|
|
})
|
|
|
|
// 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) {
|
|
git.NewCommand(git.DefaultContext, "reset", "--hard", "HEAD~1").Run(&git.RunOpts{Dir: dstPath})
|
|
_, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-new")
|
|
assert.NoError(t, err)
|
|
})
|
|
doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")
|
|
})
|
|
|
|
// Set up permissions for force push but not normal push
|
|
t.Run("SetupForcePushPermissions", func(t *testing.T) {
|
|
doProtectBranch(ctx, "protected", "", baseCtx.Username, "")
|
|
})
|
|
|
|
// Try to force push without normal push permissions, which should fail
|
|
t.Run("ForcePushWithoutNormalPermissions", func(t *testing.T) {
|
|
doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")
|
|
})
|
|
|
|
// Set up permissions for normal and force push (both are required to force push)
|
|
t.Run("SetupNormalAndForcePushPermissions", func(t *testing.T) {
|
|
doProtectBranch(ctx, "protected", baseCtx.Username, baseCtx.Username, "")
|
|
})
|
|
|
|
// Force push should now work
|
|
t.Run("ForcePushWithPermissions", func(t *testing.T) {
|
|
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(littleSize, 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(littleSize, 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(littleSize, 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 string) func(t *testing.T) {
|
|
// We are going to just use the owner to set the protection.
|
|
return func(t *testing.T) {
|
|
csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings/branches", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)))
|
|
|
|
formData := map[string]string{
|
|
"_csrf": csrf,
|
|
"rule_name": branch,
|
|
"unprotected_file_patterns": unprotectedFilePatterns,
|
|
}
|
|
|
|
if userToWhitelistPush != "" {
|
|
user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelistPush)
|
|
assert.NoError(t, err)
|
|
formData["whitelist_users"] = strconv.FormatInt(user.ID, 10)
|
|
formData["enable_push"] = "whitelist"
|
|
formData["enable_whitelist"] = "on"
|
|
}
|
|
|
|
if userToWhitelistForcePush != "" {
|
|
user, err := user_model.GetUserByName(db.DefaultContext, 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"
|
|
}
|
|
|
|
// 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 master branch has been locked successfully
|
|
flashCookie := ctx.Session.GetCookie(gitea_context.CookieNameFlash)
|
|
assert.NotNil(t, flashCookie)
|
|
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522"+url.QueryEscape(branch)+"%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
|
|
}
|
|
}
|
|
|
|
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 = fmt.Sprintf("repo-tmp-push-create-%s", 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(db.DefaultContext, 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 = fmt.Sprintf("invalid/repo-tmp-push-create-%s", 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 := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/branches", url.PathEscape(owner), url.PathEscape(repo)))
|
|
|
|
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)
|
|
|
|
t.Run("CheckoutProtected", doGitCheckoutBranch(dstPath, "protected"))
|
|
t.Run("PullProtected", doGitPull(dstPath, "origin", "protected"))
|
|
t.Run("GenerateCommit", func(t *testing.T) {
|
|
_, err := generateCommitWithNewData(littleSize, 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 api.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(api.CommitStatusPending))
|
|
|
|
// Cancel not existing auto merge
|
|
ctx.ExpectedCode = http.StatusNotFound
|
|
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))
|
|
|
|
// Can not 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(api.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(api.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(git.DefaultContext, dstPath)
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
defer gitRepo.Close()
|
|
|
|
var (
|
|
pr1, pr2 *issues_model.PullRequest
|
|
commit string
|
|
)
|
|
repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ctx.Username, ctx.Reponame)
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
|
|
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)
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
|
|
err = git.AddChanges(dstPath, true)
|
|
assert.NoError(t, err)
|
|
|
|
err = git.CommitChanges(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 := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + headBranch).Run(&git.RunOpts{Dir: dstPath})
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1)
|
|
pr1 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
|
|
HeadRepoID: repo.ID,
|
|
Flow: issues_model.PullRequestFlowAGit,
|
|
})
|
|
if !assert.NotEmpty(t, pr1) {
|
|
return
|
|
}
|
|
prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t)
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
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 = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).RunStdString(&git.RunOpts{Dir: dstPath})
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
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,
|
|
})
|
|
if !assert.NotEmpty(t, pr2) {
|
|
return
|
|
}
|
|
prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t)
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
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)
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
|
|
err = git.AddChanges(dstPath, true)
|
|
assert.NoError(t, err)
|
|
|
|
err = git.CommitChanges(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 := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + headBranch).Run(&git.RunOpts{Dir: dstPath})
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
|
|
prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t)
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
assert.False(t, prMsg.HasMerged)
|
|
assert.Equal(t, commit, prMsg.Head.Sha)
|
|
|
|
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).RunStdString(&git.RunOpts{Dir: dstPath})
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
|
|
prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t)
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
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"))
|
|
}
|
|
}
|
|
|
|
func TestDataAsync_Issue29101(t *testing.T) {
|
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
|
|
resp, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, user, &files_service.ChangeRepoFilesOptions{
|
|
Files: []*files_service.ChangeRepoFile{
|
|
{
|
|
Operation: "create",
|
|
TreePath: "test.txt",
|
|
ContentReader: bytes.NewReader(make([]byte, 10000)),
|
|
},
|
|
},
|
|
OldBranch: repo.DefaultBranch,
|
|
NewBranch: repo.DefaultBranch,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
sha := resp.Commit.SHA
|
|
|
|
gitRepo, err := gitrepo.OpenRepository(db.DefaultContext, repo)
|
|
assert.NoError(t, err)
|
|
|
|
commit, err := gitRepo.GetCommit(sha)
|
|
assert.NoError(t, err)
|
|
|
|
entry, err := commit.GetTreeEntryByPath("test.txt")
|
|
assert.NoError(t, err)
|
|
|
|
b := entry.Blob()
|
|
|
|
r, err := b.DataAsync()
|
|
assert.NoError(t, err)
|
|
defer r.Close()
|
|
|
|
r2, err := b.DataAsync()
|
|
assert.NoError(t, err)
|
|
defer r2.Close()
|
|
})
|
|
}
|