1139 lines · 44.4 KB · Go
RawDownload
// Copyright 2017 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package integration import (	"bytes"	"crypto/rand"	"encoding/hex"	"fmt"	"io"	"net/http"	"net/url"	"os"	"os/exec"	"path"	"path/filepath"	"strconv"	"strings"	"testing"	"time" 	auth_model "forgejo.org/models/auth"	"forgejo.org/models/db"	git_model "forgejo.org/models/git"	issues_model "forgejo.org/models/issues"	"forgejo.org/models/perm"	repo_model "forgejo.org/models/repo"	"forgejo.org/models/unittest"	user_model "forgejo.org/models/user"	"forgejo.org/modules/git"	"forgejo.org/modules/gitrepo"	"forgejo.org/modules/lfs"	"forgejo.org/modules/setting"	api "forgejo.org/modules/structs"	gitea_context "forgejo.org/services/context"	files_service "forgejo.org/services/repository/files"	"forgejo.org/tests" 	"github.com/kballard/go-shellquote"	"github.com/stretchr/testify/assert"	"github.com/stretchr/testify/require") const (	littleSize = 1024             // 1KiB	bigSize    = 32 * 1024 * 1024 // 32MiB) 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, nil, 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, nil, 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) {				var publicKeyID int64				t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key-"+objectFormat.Name(), keyFile, func(t *testing.T, pk api.PublicKey) {					publicKeyID = pk.ID				})) 				// 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))				t.Run("LFS no access", doLFSNoAccess(sshContext, publicKeyID, 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})		require.NoError(t, err)		_, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("track").AddDynamicArguments(prefix + "*").RunStdString(&git.RunOpts{Dir: dstPath})		require.NoError(t, err)		err = git.AddChanges(dstPath, false, ".gitattributes")		require.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()),		})		require.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})	require.NoError(t, err)	_, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath})	require.NoError(t, err)	_, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath})	require.NoError(t, err)	_, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath})	require.NoError(t, err)} func doCommitAndPush(t *testing.T, size int64, repoPath, prefix string) string {	name := generateCommitWithNewData(t, size, repoPath, "user2@example.com", "User Two", prefix)	_, _, err := git.NewCommand(git.DefaultContext, "push", "origin", "master").RunStdString(&git.RunOpts{Dir: repoPath}) // Push	require.NoError(t, err)	return name} func generateCommitWithNewData(t *testing.T, size int64, repoPath, email, fullName, prefix string) string {	t.Helper()	tmpFile, err := os.CreateTemp(repoPath, prefix)	require.NoError(t, err)	defer tmpFile.Close()	_, err = io.CopyN(tmpFile, rand.Reader, size)	require.NoError(t, err) 	// Commit	// Now here we should explicitly allow lfs filters to run	globalArgs := git.AllowLFSFiltersArgs()	require.NoError(t, git.AddChangesWithArgs(repoPath, globalArgs, false, filepath.Base(tmpFile.Name())))	require.NoError(t, 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())} 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("PushToNewProtectedBranch", func(t *testing.T) {			defer tests.PrintCurrentTest(t)()			t.Run("CreateBranchProtected", doGitCreateBranch(dstPath, "before-create-1"))			t.Run("ProtectProtectedBranch", doProtectBranch(ctx, "before-create-1", parameterProtectBranch{				"enable_push":     "all",				"apply_to_admins": "on",			}))			t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "before-create-1")) 			t.Run("GenerateCommit", func(t *testing.T) {				generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "protected-file-data-")			}) 			t.Run("ProtectProtectedBranch", doProtectBranch(ctx, "before-create-2", parameterProtectBranch{				"enable_push":             "all",				"protected_file_patterns": "protected-file-data-*",				"apply_to_admins":         "on",			})) 			doGitPushTestRepositoryFail(dstPath, "origin", "HEAD:before-create-2")(t)		}) 		t.Run("FailToPushToProtectedBranch", func(t *testing.T) {			defer tests.PrintCurrentTest(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) {				generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")			}) 			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) {			defer tests.PrintCurrentTest(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) {				generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "protected-file-")			}) 			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) {			defer tests.PrintCurrentTest(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) {				generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "unprotected-file-")			})			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)		require.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) {			defer tests.PrintCurrentTest(t)()			t.Run("Create toforce", doGitCheckoutBranch(dstPath, "-b", "toforce", "master"))			t.Run("GenerateCommit", func(t *testing.T) {				generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")			})			doGitPushTestRepositoryFail(dstPath, "-f", "origin", "toforce:protected")(t)		}) 		t.Run("WhitelistedUserPushToProtectedBranch", func(t *testing.T) {			defer tests.PrintCurrentTest(t)()			t.Run("Create topush", doGitCheckoutBranch(dstPath, "-b", "topush", "protected"))			t.Run("GenerateCommit", func(t *testing.T) {				generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-")			})			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.Equal(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)			require.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))

Lines 1–500 of 1139.