mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-31 06:21:11 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1114 lines
		
	
	
	
		
			42 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1114 lines
		
	
	
	
		
			42 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"
 | |
| 	git_model "code.gitea.io/gitea/models/git"
 | |
| 	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"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| 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) {
 | |
| 		ensureAnonymousClone(t, u)
 | |
| 		forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 			httpContext := baseAPITestContext
 | |
| 			httpContext.Reponame = "repo-tmp-17-" + objectFormat.Name()
 | |
| 			forkedUserCtx.Reponame = httpContext.Reponame
 | |
| 
 | |
| 			dstPath := t.TempDir()
 | |
| 
 | |
| 			t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false, objectFormat))
 | |
| 			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("InternalReferences", doInternalReferences(&httpContext, dstPath))
 | |
| 			t.Run("BranchProtect", doBranchProtect(&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, objectFormat))
 | |
| 		})
 | |
| 	})
 | |
| 	t.Run("SSH", func(t *testing.T) {
 | |
| 		forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 			sshContext := baseAPITestContext
 | |
| 			sshContext.Reponame = "repo-tmp-18-" + objectFormat.Name()
 | |
| 			keyname := "my-testing-key"
 | |
| 			forkedUserCtx.Reponame = sshContext.Reponame
 | |
| 			t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false, objectFormat))
 | |
| 			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-"+objectFormat.Name(), 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("InternalReferences", doInternalReferences(&sshContext, dstPath))
 | |
| 				t.Run("BranchProtect", doBranchProtect(&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, objectFormat))
 | |
| 			})
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 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 doBranchProtect(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)
 | |
| 
 | |
| 		t.Run("FailToPushToProtectedBranch", func(t *testing.T) {
 | |
| 			t.Run("ProtectProtectedBranch", doProtectBranch(ctx, "protected"))
 | |
| 			t.Run("Create modified-protected-branch", doGitCheckoutBranch(dstPath, "-b", "modified-protected-branch", "protected"))
 | |
| 			t.Run("GenerateCommit", func(t *testing.T) {
 | |
| 				_, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
 | |
| 				assert.NoError(t, err)
 | |
| 			})
 | |
| 
 | |
| 			doGitPushTestRepositoryFail(dstPath, "origin", "modified-protected-branch:protected")(t)
 | |
| 		})
 | |
| 
 | |
| 		t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "modified-protected-branch:unprotected"))
 | |
| 
 | |
| 		t.Run("FailToPushProtectedFilesToProtectedBranch", func(t *testing.T) {
 | |
| 			t.Run("Create modified-protected-file-protected-branch", doGitCheckoutBranch(dstPath, "-b", "modified-protected-file-protected-branch", "protected"))
 | |
| 			t.Run("GenerateCommit", func(t *testing.T) {
 | |
| 				_, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "protected-file-")
 | |
| 				assert.NoError(t, err)
 | |
| 			})
 | |
| 
 | |
| 			t.Run("ProtectedFilePathsApplyToAdmins", doProtectBranch(ctx, "protected"))
 | |
| 			doGitPushTestRepositoryFail(dstPath, "origin", "modified-protected-file-protected-branch:protected")(t)
 | |
| 
 | |
| 			doGitCheckoutBranch(dstPath, "protected")(t)
 | |
| 			doGitPull(dstPath, "origin", "protected")(t)
 | |
| 		})
 | |
| 
 | |
| 		t.Run("PushUnprotectedFilesToProtectedBranch", func(t *testing.T) {
 | |
| 			t.Run("Create modified-unprotected-file-protected-branch", doGitCheckoutBranch(dstPath, "-b", "modified-unprotected-file-protected-branch", "protected"))
 | |
| 			t.Run("UnprotectedFilePaths", doProtectBranch(ctx, "protected", parameterProtectBranch{
 | |
| 				"unprotected_file_patterns": "unprotected-file-*",
 | |
| 			}))
 | |
| 			t.Run("GenerateCommit", func(t *testing.T) {
 | |
| 				_, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "unprotected-file-")
 | |
| 				assert.NoError(t, err)
 | |
| 			})
 | |
| 			doGitPushTestRepository(dstPath, "origin", "modified-unprotected-file-protected-branch:protected")(t)
 | |
| 			doGitCheckoutBranch(dstPath, "protected")(t)
 | |
| 			doGitPull(dstPath, "origin", "protected")(t)
 | |
| 		})
 | |
| 
 | |
| 		user, err := user_model.GetUserByName(db.DefaultContext, baseCtx.Username)
 | |
| 		assert.NoError(t, err)
 | |
| 		t.Run("WhitelistUsers", doProtectBranch(ctx, "protected", parameterProtectBranch{
 | |
| 			"enable_push":      "whitelist",
 | |
| 			"enable_whitelist": "on",
 | |
| 			"whitelist_users":  strconv.FormatInt(user.ID, 10),
 | |
| 		}))
 | |
| 
 | |
| 		t.Run("WhitelistedUserFailToForcePushToProtectedBranch", func(t *testing.T) {
 | |
| 			t.Run("Create toforce", doGitCheckoutBranch(dstPath, "-b", "toforce", "master"))
 | |
| 			t.Run("GenerateCommit", func(t *testing.T) {
 | |
| 				_, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
 | |
| 				assert.NoError(t, err)
 | |
| 			})
 | |
| 			doGitPushTestRepositoryFail(dstPath, "-f", "origin", "toforce:protected")(t)
 | |
| 		})
 | |
| 
 | |
| 		t.Run("WhitelistedUserPushToProtectedBranch", func(t *testing.T) {
 | |
| 			t.Run("Create topush", doGitCheckoutBranch(dstPath, "-b", "topush", "protected"))
 | |
| 			t.Run("GenerateCommit", func(t *testing.T) {
 | |
| 				_, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")
 | |
| 				assert.NoError(t, err)
 | |
| 			})
 | |
| 			doGitPushTestRepository(dstPath, "origin", "topush:protected")(t)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type parameterProtectBranch map[string]string
 | |
| 
 | |
| func doProtectBranch(ctx APITestContext, branch string, addParameter ...parameterProtectBranch) func(t *testing.T) {
 | |
| 	// We are going to just use the owner to set the protection.
 | |
| 	return func(t *testing.T) {
 | |
| 		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: ctx.Reponame, OwnerName: ctx.Username})
 | |
| 		rule := &git_model.ProtectedBranch{RuleName: branch, RepoID: repo.ID}
 | |
| 		unittest.LoadBeanIfExists(rule)
 | |
| 
 | |
| 		csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings/branches", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)))
 | |
| 
 | |
| 		parameter := parameterProtectBranch{
 | |
| 			"_csrf":     csrf,
 | |
| 			"rule_id":   strconv.FormatInt(rule.ID, 10),
 | |
| 			"rule_name": branch,
 | |
| 		}
 | |
| 		if len(addParameter) > 0 {
 | |
| 			for k, v := range addParameter[0] {
 | |
| 				parameter[k] = v
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Change branch to protected
 | |
| 		req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), parameter)
 | |
| 		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 pull request
 | |
| 		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.
 | |
| 		// For the base repository owner, the PR is not editable (maintainer edits are not enabled):
 | |
| 		t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr, false))
 | |
| 		// For the head repository owner, the PR is editable:
 | |
| 		headSession := loginUser(t, "user2")
 | |
| 		headToken := getTokenForLoggedInUser(t, headSession, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopeReadUser)
 | |
| 		headCtx := APITestContext{
 | |
| 			Session:  headSession,
 | |
| 			Token:    headToken,
 | |
| 			Username: baseCtx.Username,
 | |
| 			Reponame: baseCtx.Reponame,
 | |
| 		}
 | |
| 		t.Run("EnsureCanSeePull", doEnsureCanSeePull(headCtx, pr, true))
 | |
| 
 | |
| 		// Confirm that there is no AGit Label
 | |
| 		// TODO: Refactor and move this check to a function
 | |
| 		t.Run("AGitLabelIsMissing", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			session := loginUser(t, ctx.Username)
 | |
| 
 | |
| 			req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", baseCtx.Username, baseCtx.Reponame, pr.Index))
 | |
| 			resp := session.MakeRequest(t, req, http.StatusOK)
 | |
| 			htmlDoc := NewHTMLParser(t, resp.Body)
 | |
| 			htmlDoc.AssertElement(t, "#agit-label", false)
 | |
| 		})
 | |
| 
 | |
| 		// 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))
 | |
| 		// for both users the PR is still visible but not editable anymore after it was merged
 | |
| 		t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr, false))
 | |
| 		t.Run("EnsureCanSeePull", doEnsureCanSeePull(headCtx, pr, false))
 | |
| 		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, false))
 | |
| 		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, false))
 | |
| 		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, editable bool) 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))
 | |
| 		resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
 | |
| 		doc := NewHTMLParser(t, resp.Body)
 | |
| 		editButtonCount := doc.doc.Find("div.diff-file-header-actions a[href*='/_edit/']").Length()
 | |
| 		if editable {
 | |
| 			assert.Greater(t, editButtonCount, 0, "Expected to find a button to edit a file in the PR diff view but there were none")
 | |
| 		} else {
 | |
| 			assert.Equal(t, 0, editButtonCount, "Expected not to find any buttons to edit files in PR diff view but there were some")
 | |
| 		}
 | |
| 		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, objectFormat git.ObjectFormat) func(t *testing.T) {
 | |
| 	return func(t *testing.T) {
 | |
| 		if objectFormat == git.Sha256ObjectFormat {
 | |
| 			t.Skipf("push-create not supported for %s, see https://codeberg.org/forgejo/forgejo/issues/3783", objectFormat)
 | |
| 		}
 | |
| 		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, objectFormat))
 | |
| 		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 doInternalReferences(ctx *APITestContext, dstPath string) func(t *testing.T) {
 | |
| 	return func(t *testing.T) {
 | |
| 		defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: ctx.Username, Name: ctx.Reponame})
 | |
| 		pr1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{HeadRepoID: repo.ID})
 | |
| 
 | |
| 		_, stdErr, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments(fmt.Sprintf(":refs/pull/%d/head", pr1.Index)).RunStdString(&git.RunOpts{Dir: dstPath})
 | |
| 		assert.Error(t, gitErr)
 | |
| 		assert.Contains(t, stdErr, fmt.Sprintf("remote: Forgejo: The deletion of refs/pull/%d/head is skipped as it's an internal reference.", pr1.Index))
 | |
| 		assert.Contains(t, stdErr, fmt.Sprintf("[remote rejected] refs/pull/%d/head (hook declined)", pr1.Index))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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.CheckGitVersionAtLeast("2.29") != nil {
 | |
| 			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("AGitLabelIsPresent", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			session := loginUser(t, ctx.Username)
 | |
| 
 | |
| 			req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr2.Index))
 | |
| 			resp := session.MakeRequest(t, req, http.StatusOK)
 | |
| 			htmlDoc := NewHTMLParser(t, resp.Body)
 | |
| 			htmlDoc.AssertElement(t, "#agit-label", true)
 | |
| 		})
 | |
| 
 | |
| 		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\n\nLonger description.",
 | |
| 			})
 | |
| 			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("PushParams", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			t.Run("NoParams", func(t *testing.T) {
 | |
| 				defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 				_, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-implicit").RunStdString(&git.RunOpts{Dir: dstPath})
 | |
| 				assert.NoError(t, gitErr)
 | |
| 
 | |
| 				unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+3)
 | |
| 				pr3 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
 | |
| 					HeadRepoID: repo.ID,
 | |
| 					Flow:       issues_model.PullRequestFlowAGit,
 | |
| 					Index:      pr1.Index + 2,
 | |
| 				})
 | |
| 				assert.NotEmpty(t, pr3)
 | |
| 				err := pr3.LoadIssue(db.DefaultContext)
 | |
| 				assert.NoError(t, err)
 | |
| 
 | |
| 				_, err2 := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr3.Index)(t)
 | |
| 				require.NoError(t, err2)
 | |
| 
 | |
| 				assert.Equal(t, "Testing commit 2", pr3.Issue.Title)
 | |
| 				assert.Contains(t, pr3.Issue.Content, "Longer description.")
 | |
| 			})
 | |
| 			t.Run("TitleOverride", func(t *testing.T) {
 | |
| 				defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 				_, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin", "-o", "title=my-shiny-title").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-implicit-2").RunStdString(&git.RunOpts{Dir: dstPath})
 | |
| 				assert.NoError(t, gitErr)
 | |
| 
 | |
| 				unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+4)
 | |
| 				pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
 | |
| 					HeadRepoID: repo.ID,
 | |
| 					Flow:       issues_model.PullRequestFlowAGit,
 | |
| 					Index:      pr1.Index + 3,
 | |
| 				})
 | |
| 				assert.NotEmpty(t, pr)
 | |
| 				err := pr.LoadIssue(db.DefaultContext)
 | |
| 				assert.NoError(t, err)
 | |
| 
 | |
| 				_, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t)
 | |
| 				require.NoError(t, err)
 | |
| 
 | |
| 				assert.Equal(t, "my-shiny-title", pr.Issue.Title)
 | |
| 				assert.Contains(t, pr.Issue.Content, "Longer description.")
 | |
| 			})
 | |
| 
 | |
| 			t.Run("DescriptionOverride", func(t *testing.T) {
 | |
| 				defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 				_, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin", "-o", "description=custom").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-implicit-3").RunStdString(&git.RunOpts{Dir: dstPath})
 | |
| 				assert.NoError(t, gitErr)
 | |
| 
 | |
| 				unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+5)
 | |
| 				pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
 | |
| 					HeadRepoID: repo.ID,
 | |
| 					Flow:       issues_model.PullRequestFlowAGit,
 | |
| 					Index:      pr1.Index + 4,
 | |
| 				})
 | |
| 				assert.NotEmpty(t, pr)
 | |
| 				err := pr.LoadIssue(db.DefaultContext)
 | |
| 				assert.NoError(t, err)
 | |
| 
 | |
| 				_, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t)
 | |
| 				require.NoError(t, err)
 | |
| 
 | |
| 				assert.Equal(t, "Testing commit 2", pr.Issue.Title)
 | |
| 				assert.Contains(t, pr.Issue.Content, "custom")
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		upstreamGitRepo, err := git.OpenRepository(git.DefaultContext, filepath.Join(setting.RepoRootPath, ctx.Username, ctx.Reponame+".git"))
 | |
| 		require.NoError(t, err)
 | |
| 		defer upstreamGitRepo.Close()
 | |
| 
 | |
| 		t.Run("Force push", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			_, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-force-push").RunStdString(&git.RunOpts{Dir: dstPath})
 | |
| 			require.NoError(t, gitErr)
 | |
| 
 | |
| 			unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+6)
 | |
| 			pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
 | |
| 				HeadRepoID: repo.ID,
 | |
| 				Flow:       issues_model.PullRequestFlowAGit,
 | |
| 				Index:      pr1.Index + 5,
 | |
| 			})
 | |
| 
 | |
| 			headCommitID, err := upstreamGitRepo.GetRefCommitID(pr.GetGitRefName())
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			_, _, gitErr = git.NewCommand(git.DefaultContext, "reset", "--hard", "HEAD~1").RunStdString(&git.RunOpts{Dir: dstPath})
 | |
| 			require.NoError(t, gitErr)
 | |
| 
 | |
| 			t.Run("Fails", func(t *testing.T) {
 | |
| 				defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 				_, stdErr, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-force-push").RunStdString(&git.RunOpts{Dir: dstPath})
 | |
| 				assert.Error(t, gitErr)
 | |
| 
 | |
| 				assert.Contains(t, stdErr, "-o force-push=true")
 | |
| 
 | |
| 				currentHeadCommitID, err := upstreamGitRepo.GetRefCommitID(pr.GetGitRefName())
 | |
| 				assert.NoError(t, err)
 | |
| 				assert.EqualValues(t, headCommitID, currentHeadCommitID)
 | |
| 			})
 | |
| 			t.Run("Succeeds", func(t *testing.T) {
 | |
| 				defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 				_, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin", "-o", "force-push").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-force-push").RunStdString(&git.RunOpts{Dir: dstPath})
 | |
| 				assert.NoError(t, gitErr)
 | |
| 
 | |
| 				currentHeadCommitID, err := upstreamGitRepo.GetRefCommitID(pr.GetGitRefName())
 | |
| 				assert.NoError(t, err)
 | |
| 				assert.NotEqualValues(t, headCommitID, currentHeadCommitID)
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		t.Run("Branch already contains commit", func(t *testing.T) {
 | |
| 			defer tests.PrintCurrentTest(t)()
 | |
| 
 | |
| 			branchCommit, err := upstreamGitRepo.GetBranchCommit("master")
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			_, _, gitErr := git.NewCommand(git.DefaultContext, "reset", "--hard").AddDynamicArguments(branchCommit.ID.String() + "~1").RunStdString(&git.RunOpts{Dir: dstPath})
 | |
| 			require.NoError(t, gitErr)
 | |
| 
 | |
| 			_, stdErr, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-already-contains").RunStdString(&git.RunOpts{Dir: dstPath})
 | |
| 			assert.Error(t, gitErr)
 | |
| 
 | |
| 			assert.Contains(t, stdErr, "already contains this commit")
 | |
| 		})
 | |
| 
 | |
| 		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)
 | |
| 		defer gitRepo.Close()
 | |
| 
 | |
| 		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()
 | |
| 	})
 | |
| }
 |