code.gitea.io/gitea@v1.22.3/tests/integration/git_test.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package integration 5 6 import ( 7 "bytes" 8 "encoding/hex" 9 "fmt" 10 "math/rand" 11 "net/http" 12 "net/url" 13 "os" 14 "path" 15 "path/filepath" 16 "strconv" 17 "testing" 18 "time" 19 20 auth_model "code.gitea.io/gitea/models/auth" 21 "code.gitea.io/gitea/models/db" 22 issues_model "code.gitea.io/gitea/models/issues" 23 "code.gitea.io/gitea/models/perm" 24 repo_model "code.gitea.io/gitea/models/repo" 25 "code.gitea.io/gitea/models/unittest" 26 user_model "code.gitea.io/gitea/models/user" 27 "code.gitea.io/gitea/modules/git" 28 "code.gitea.io/gitea/modules/gitrepo" 29 "code.gitea.io/gitea/modules/lfs" 30 "code.gitea.io/gitea/modules/setting" 31 api "code.gitea.io/gitea/modules/structs" 32 gitea_context "code.gitea.io/gitea/services/context" 33 files_service "code.gitea.io/gitea/services/repository/files" 34 "code.gitea.io/gitea/tests" 35 36 "github.com/stretchr/testify/assert" 37 ) 38 39 const ( 40 littleSize = 1024 // 1ko 41 bigSize = 128 * 1024 * 1024 // 128Mo 42 ) 43 44 func TestGit(t *testing.T) { 45 onGiteaRun(t, testGit) 46 } 47 48 func testGit(t *testing.T, u *url.URL) { 49 username := "user2" 50 baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 51 52 u.Path = baseAPITestContext.GitPath() 53 54 forkedUserCtx := NewAPITestContext(t, "user4", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 55 56 t.Run("HTTP", func(t *testing.T) { 57 defer tests.PrintCurrentTest(t)() 58 ensureAnonymousClone(t, u) 59 httpContext := baseAPITestContext 60 httpContext.Reponame = "repo-tmp-17" 61 forkedUserCtx.Reponame = httpContext.Reponame 62 63 dstPath := t.TempDir() 64 65 t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false)) 66 t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, httpContext.Username, perm.AccessModeRead)) 67 68 t.Run("ForkFromDifferentUser", doAPIForkRepository(httpContext, forkedUserCtx.Username)) 69 70 u.Path = httpContext.GitPath() 71 u.User = url.UserPassword(username, userPassword) 72 73 t.Run("Clone", doGitClone(dstPath, u)) 74 75 dstPath2 := t.TempDir() 76 77 t.Run("Partial Clone", doPartialGitClone(dstPath2, u)) 78 79 little, big := standardCommitAndPushTest(t, dstPath) 80 littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath) 81 rawTest(t, &httpContext, little, big, littleLFS, bigLFS) 82 mediaTest(t, &httpContext, little, big, littleLFS, bigLFS) 83 84 t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "master", "test/head")) 85 t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath)) 86 t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath)) 87 t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge")) 88 t.Run("MergeFork", func(t *testing.T) { 89 defer tests.PrintCurrentTest(t)() 90 t.Run("CreatePRAndMerge", doMergeFork(httpContext, forkedUserCtx, "master", httpContext.Username+":master")) 91 rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) 92 mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) 93 }) 94 95 t.Run("PushCreate", doPushCreate(httpContext, u)) 96 }) 97 t.Run("SSH", func(t *testing.T) { 98 defer tests.PrintCurrentTest(t)() 99 sshContext := baseAPITestContext 100 sshContext.Reponame = "repo-tmp-18" 101 keyname := "my-testing-key" 102 forkedUserCtx.Reponame = sshContext.Reponame 103 t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false)) 104 t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, sshContext.Username, perm.AccessModeRead)) 105 t.Run("ForkFromDifferentUser", doAPIForkRepository(sshContext, forkedUserCtx.Username)) 106 107 // Setup key the user ssh key 108 withKeyFile(t, keyname, func(keyFile string) { 109 t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile)) 110 111 // Setup remote link 112 // TODO: get url from api 113 sshURL := createSSHUrl(sshContext.GitPath(), u) 114 115 // Setup clone folder 116 dstPath := t.TempDir() 117 118 t.Run("Clone", doGitClone(dstPath, sshURL)) 119 120 little, big := standardCommitAndPushTest(t, dstPath) 121 littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath) 122 rawTest(t, &sshContext, little, big, littleLFS, bigLFS) 123 mediaTest(t, &sshContext, little, big, littleLFS, bigLFS) 124 125 t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "master", "test/head2")) 126 t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath)) 127 t.Run("MergeFork", func(t *testing.T) { 128 defer tests.PrintCurrentTest(t)() 129 t.Run("CreatePRAndMerge", doMergeFork(sshContext, forkedUserCtx, "master", sshContext.Username+":master")) 130 rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) 131 mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) 132 }) 133 134 t.Run("PushCreate", doPushCreate(sshContext, sshURL)) 135 }) 136 }) 137 } 138 139 func ensureAnonymousClone(t *testing.T, u *url.URL) { 140 dstLocalPath := t.TempDir() 141 t.Run("CloneAnonymous", doGitClone(dstLocalPath, u)) 142 } 143 144 func standardCommitAndPushTest(t *testing.T, dstPath string) (little, big string) { 145 t.Run("Standard", func(t *testing.T) { 146 defer tests.PrintCurrentTest(t)() 147 little, big = commitAndPushTest(t, dstPath, "data-file-") 148 }) 149 return little, big 150 } 151 152 func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS string) { 153 t.Run("LFS", func(t *testing.T) { 154 defer tests.PrintCurrentTest(t)() 155 prefix := "lfs-data-file-" 156 err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").Run(&git.RunOpts{Dir: dstPath}) 157 assert.NoError(t, err) 158 _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("track").AddDynamicArguments(prefix + "*").RunStdString(&git.RunOpts{Dir: dstPath}) 159 assert.NoError(t, err) 160 err = git.AddChanges(dstPath, false, ".gitattributes") 161 assert.NoError(t, err) 162 163 err = git.CommitChangesWithArgs(dstPath, git.AllowLFSFiltersArgs(), git.CommitChangesOptions{ 164 Committer: &git.Signature{ 165 Email: "user2@example.com", 166 Name: "User Two", 167 When: time.Now(), 168 }, 169 Author: &git.Signature{ 170 Email: "user2@example.com", 171 Name: "User Two", 172 When: time.Now(), 173 }, 174 Message: fmt.Sprintf("Testing commit @ %v", time.Now()), 175 }) 176 assert.NoError(t, err) 177 178 littleLFS, bigLFS = commitAndPushTest(t, dstPath, prefix) 179 180 t.Run("Locks", func(t *testing.T) { 181 defer tests.PrintCurrentTest(t)() 182 lockTest(t, dstPath) 183 }) 184 }) 185 return littleLFS, bigLFS 186 } 187 188 func commitAndPushTest(t *testing.T, dstPath, prefix string) (little, big string) { 189 t.Run("PushCommit", func(t *testing.T) { 190 defer tests.PrintCurrentTest(t)() 191 t.Run("Little", func(t *testing.T) { 192 defer tests.PrintCurrentTest(t)() 193 little = doCommitAndPush(t, littleSize, dstPath, prefix) 194 }) 195 t.Run("Big", func(t *testing.T) { 196 if testing.Short() { 197 t.Skip("Skipping test in short mode.") 198 return 199 } 200 defer tests.PrintCurrentTest(t)() 201 big = doCommitAndPush(t, bigSize, dstPath, prefix) 202 }) 203 }) 204 return little, big 205 } 206 207 func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) { 208 t.Run("Raw", func(t *testing.T) { 209 defer tests.PrintCurrentTest(t)() 210 username := ctx.Username 211 reponame := ctx.Reponame 212 213 session := loginUser(t, username) 214 215 // Request raw paths 216 req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little)) 217 resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) 218 assert.Equal(t, littleSize, resp.Length) 219 220 if setting.LFS.StartServer { 221 req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS)) 222 resp := session.MakeRequest(t, req, http.StatusOK) 223 assert.NotEqual(t, littleSize, resp.Body.Len()) 224 assert.LessOrEqual(t, resp.Body.Len(), 1024) 225 if resp.Body.Len() != littleSize && resp.Body.Len() <= 1024 { 226 assert.Contains(t, resp.Body.String(), lfs.MetaFileIdentifier) 227 } 228 } 229 230 if !testing.Short() { 231 req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big)) 232 resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) 233 assert.Equal(t, bigSize, resp.Length) 234 235 if setting.LFS.StartServer { 236 req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS)) 237 resp := session.MakeRequest(t, req, http.StatusOK) 238 assert.NotEqual(t, bigSize, resp.Body.Len()) 239 if resp.Body.Len() != bigSize && resp.Body.Len() <= 1024 { 240 assert.Contains(t, resp.Body.String(), lfs.MetaFileIdentifier) 241 } 242 } 243 } 244 }) 245 } 246 247 func mediaTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) { 248 t.Run("Media", func(t *testing.T) { 249 defer tests.PrintCurrentTest(t)() 250 251 username := ctx.Username 252 reponame := ctx.Reponame 253 254 session := loginUser(t, username) 255 256 // Request media paths 257 req := NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", little)) 258 resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) 259 assert.Equal(t, littleSize, resp.Length) 260 261 req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS)) 262 resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) 263 assert.Equal(t, littleSize, resp.Length) 264 265 if !testing.Short() { 266 req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big)) 267 resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) 268 assert.Equal(t, bigSize, resp.Length) 269 270 if setting.LFS.StartServer { 271 req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS)) 272 resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) 273 assert.Equal(t, bigSize, resp.Length) 274 } 275 } 276 }) 277 } 278 279 func lockTest(t *testing.T, repoPath string) { 280 lockFileTest(t, "README.md", repoPath) 281 } 282 283 func lockFileTest(t *testing.T, filename, repoPath string) { 284 _, _, err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) 285 assert.NoError(t, err) 286 _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath}) 287 assert.NoError(t, err) 288 _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) 289 assert.NoError(t, err) 290 _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath}) 291 assert.NoError(t, err) 292 } 293 294 func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string { 295 name, err := generateCommitWithNewData(size, repoPath, "user2@example.com", "User Two", prefix) 296 assert.NoError(t, err) 297 _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "master").RunStdString(&git.RunOpts{Dir: repoPath}) // Push 298 assert.NoError(t, err) 299 return name 300 } 301 302 func generateCommitWithNewData(size int, repoPath, email, fullName, prefix string) (string, error) { 303 // Generate random file 304 bufSize := 4 * 1024 305 if bufSize > size { 306 bufSize = size 307 } 308 309 buffer := make([]byte, bufSize) 310 311 tmpFile, err := os.CreateTemp(repoPath, prefix) 312 if err != nil { 313 return "", err 314 } 315 defer tmpFile.Close() 316 written := 0 317 for written < size { 318 n := size - written 319 if n > bufSize { 320 n = bufSize 321 } 322 _, err := rand.Read(buffer[:n]) 323 if err != nil { 324 return "", err 325 } 326 n, err = tmpFile.Write(buffer[:n]) 327 if err != nil { 328 return "", err 329 } 330 written += n 331 } 332 if err != nil { 333 return "", err 334 } 335 336 // Commit 337 // Now here we should explicitly allow lfs filters to run 338 globalArgs := git.AllowLFSFiltersArgs() 339 err = git.AddChangesWithArgs(repoPath, globalArgs, false, filepath.Base(tmpFile.Name())) 340 if err != nil { 341 return "", err 342 } 343 err = git.CommitChangesWithArgs(repoPath, globalArgs, git.CommitChangesOptions{ 344 Committer: &git.Signature{ 345 Email: email, 346 Name: fullName, 347 When: time.Now(), 348 }, 349 Author: &git.Signature{ 350 Email: email, 351 Name: fullName, 352 When: time.Now(), 353 }, 354 Message: fmt.Sprintf("Testing commit @ %v", time.Now()), 355 }) 356 return filepath.Base(tmpFile.Name()), err 357 } 358 359 func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { 360 return func(t *testing.T) { 361 defer tests.PrintCurrentTest(t)() 362 t.Run("CreateBranchProtected", doGitCreateBranch(dstPath, "protected")) 363 t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) 364 365 ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) 366 t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "")) 367 t.Run("GenerateCommit", func(t *testing.T) { 368 _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") 369 assert.NoError(t, err) 370 }) 371 t.Run("FailToPushToProtectedBranch", doGitPushTestRepositoryFail(dstPath, "origin", "protected")) 372 t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected")) 373 var pr api.PullRequest 374 var err error 375 t.Run("CreatePullRequest", func(t *testing.T) { 376 pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected")(t) 377 assert.NoError(t, err) 378 }) 379 t.Run("GenerateCommit", func(t *testing.T) { 380 _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") 381 assert.NoError(t, err) 382 }) 383 t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected-2")) 384 var pr2 api.PullRequest 385 t.Run("CreatePullRequest", func(t *testing.T) { 386 pr2, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "unprotected", "unprotected-2")(t) 387 assert.NoError(t, err) 388 }) 389 t.Run("MergePR2", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr2.Index)) 390 t.Run("MergePR", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 391 t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) 392 393 t.Run("ProtectProtectedBranchUnprotectedFilePaths", doProtectBranch(ctx, "protected", "", "unprotected-file-*")) 394 t.Run("GenerateCommit", func(t *testing.T) { 395 _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "unprotected-file-") 396 assert.NoError(t, err) 397 }) 398 t.Run("PushUnprotectedFilesToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) 399 400 t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username, "")) 401 402 t.Run("CheckoutMaster", doGitCheckoutBranch(dstPath, "master")) 403 t.Run("CreateBranchForced", doGitCreateBranch(dstPath, "toforce")) 404 t.Run("GenerateCommit", func(t *testing.T) { 405 _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") 406 assert.NoError(t, err) 407 }) 408 t.Run("FailToForcePushToProtectedBranch", doGitPushTestRepositoryFail(dstPath, "-f", "origin", "toforce:protected")) 409 t.Run("MergeProtectedToToforce", doGitMerge(dstPath, "protected")) 410 t.Run("PushToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "toforce:protected")) 411 t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master")) 412 } 413 } 414 415 func doProtectBranch(ctx APITestContext, branch, userToWhitelist, unprotectedFilePatterns string) func(t *testing.T) { 416 // We are going to just use the owner to set the protection. 417 return func(t *testing.T) { 418 csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings/branches", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame))) 419 420 if userToWhitelist == "" { 421 // Change branch to protected 422 req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), map[string]string{ 423 "_csrf": csrf, 424 "rule_name": branch, 425 "unprotected_file_patterns": unprotectedFilePatterns, 426 }) 427 ctx.Session.MakeRequest(t, req, http.StatusSeeOther) 428 } else { 429 user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelist) 430 assert.NoError(t, err) 431 // Change branch to protected 432 req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), map[string]string{ 433 "_csrf": csrf, 434 "rule_name": branch, 435 "enable_push": "whitelist", 436 "enable_whitelist": "on", 437 "whitelist_users": strconv.FormatInt(user.ID, 10), 438 "unprotected_file_patterns": unprotectedFilePatterns, 439 }) 440 ctx.Session.MakeRequest(t, req, http.StatusSeeOther) 441 } 442 // Check if master branch has been locked successfully 443 flashCookie := ctx.Session.GetCookie(gitea_context.CookieNameFlash) 444 assert.NotNil(t, flashCookie) 445 assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522"+url.QueryEscape(branch)+"%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) 446 } 447 } 448 449 func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) func(t *testing.T) { 450 return func(t *testing.T) { 451 defer tests.PrintCurrentTest(t)() 452 var pr api.PullRequest 453 var err error 454 455 // Create a test pullrequest 456 t.Run("CreatePullRequest", func(t *testing.T) { 457 pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t) 458 assert.NoError(t, err) 459 }) 460 461 // Ensure the PR page works 462 t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) 463 464 // Then get the diff string 465 var diffHash string 466 var diffLength int 467 t.Run("GetDiff", func(t *testing.T) { 468 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(baseCtx.Username), url.PathEscape(baseCtx.Reponame), pr.Index)) 469 resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK) 470 diffHash = string(resp.Hash.Sum(nil)) 471 diffLength = resp.Length 472 }) 473 474 // Now: Merge the PR & make sure that doesn't break the PR page or change its diff 475 t.Run("MergePR", doAPIMergePullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 476 t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) 477 t.Run("CheckPR", func(t *testing.T) { 478 oldMergeBase := pr.MergeBase 479 pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) 480 assert.NoError(t, err) 481 assert.Equal(t, oldMergeBase, pr2.MergeBase) 482 }) 483 t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) 484 485 // Then: Delete the head branch & make sure that doesn't break the PR page or change its diff 486 t.Run("DeleteHeadBranch", doBranchDelete(baseCtx, baseCtx.Username, baseCtx.Reponame, headBranch)) 487 t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) 488 t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) 489 490 // Delete the head repository & make sure that doesn't break the PR page or change its diff 491 t.Run("DeleteHeadRepository", doAPIDeleteRepository(ctx)) 492 t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr)) 493 t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) 494 } 495 } 496 497 func doCreatePRAndSetManuallyMerged(ctx, baseCtx APITestContext, dstPath, baseBranch, headBranch string) func(t *testing.T) { 498 return func(t *testing.T) { 499 defer tests.PrintCurrentTest(t)() 500 var ( 501 pr api.PullRequest 502 err error 503 lastCommitID string 504 ) 505 506 trueBool := true 507 falseBool := false 508 509 t.Run("AllowSetManuallyMergedAndSwitchOffAutodetectManualMerge", doAPIEditRepository(baseCtx, &api.EditRepoOption{ 510 HasPullRequests: &trueBool, 511 AllowManualMerge: &trueBool, 512 AutodetectManualMerge: &falseBool, 513 })) 514 515 t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) 516 t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", headBranch)) 517 t.Run("CreateEmptyPullRequest", func(t *testing.T) { 518 pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t) 519 assert.NoError(t, err) 520 }) 521 lastCommitID = pr.Base.Sha 522 t.Run("ManuallyMergePR", doAPIManuallyMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, lastCommitID, pr.Index)) 523 } 524 } 525 526 func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest) func(t *testing.T) { 527 return func(t *testing.T) { 528 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) 529 ctx.Session.MakeRequest(t, req, http.StatusOK) 530 req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/files", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) 531 ctx.Session.MakeRequest(t, req, http.StatusOK) 532 req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) 533 ctx.Session.MakeRequest(t, req, http.StatusOK) 534 } 535 } 536 537 func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffHash string, diffLength int) func(t *testing.T) { 538 return func(t *testing.T) { 539 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) 540 resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK) 541 actual := string(resp.Hash.Sum(nil)) 542 actualLength := resp.Length 543 544 equal := diffHash == actual 545 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) 546 } 547 } 548 549 func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { 550 return func(t *testing.T) { 551 defer tests.PrintCurrentTest(t)() 552 553 // create a context for a currently non-existent repository 554 ctx.Reponame = fmt.Sprintf("repo-tmp-push-create-%s", u.Scheme) 555 u.Path = ctx.GitPath() 556 557 // Create a temporary directory 558 tmpDir := t.TempDir() 559 560 // Now create local repository to push as our test and set its origin 561 t.Run("InitTestRepository", doGitInitTestRepository(tmpDir)) 562 t.Run("AddRemote", doGitAddRemote(tmpDir, "origin", u)) 563 564 // Disable "Push To Create" and attempt to push 565 setting.Repository.EnablePushCreateUser = false 566 t.Run("FailToPushAndCreateTestRepository", doGitPushTestRepositoryFail(tmpDir, "origin", "master")) 567 568 // Enable "Push To Create" 569 setting.Repository.EnablePushCreateUser = true 570 571 // Assert that cloning from a non-existent repository does not create it and that it definitely wasn't create above 572 t.Run("FailToCloneFromNonExistentRepository", doGitCloneFail(u)) 573 574 // Then "Push To Create"x 575 t.Run("SuccessfullyPushAndCreateTestRepository", doGitPushTestRepository(tmpDir, "origin", "master")) 576 577 // Finally, fetch repo from database and ensure the correct repository has been created 578 repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ctx.Username, ctx.Reponame) 579 assert.NoError(t, err) 580 assert.False(t, repo.IsEmpty) 581 assert.True(t, repo.IsPrivate) 582 583 // Now add a remote that is invalid to "Push To Create" 584 invalidCtx := ctx 585 invalidCtx.Reponame = fmt.Sprintf("invalid/repo-tmp-push-create-%s", u.Scheme) 586 u.Path = invalidCtx.GitPath() 587 t.Run("AddInvalidRemote", doGitAddRemote(tmpDir, "invalid", u)) 588 589 // Fail to "Push To Create" the invalid 590 t.Run("FailToPushAndCreateInvalidTestRepository", doGitPushTestRepositoryFail(tmpDir, "invalid", "master")) 591 } 592 } 593 594 func doBranchDelete(ctx APITestContext, owner, repo, branch string) func(*testing.T) { 595 return func(t *testing.T) { 596 csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/branches", url.PathEscape(owner), url.PathEscape(repo))) 597 598 req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/branches/delete?name=%s", url.PathEscape(owner), url.PathEscape(repo), url.QueryEscape(branch)), map[string]string{ 599 "_csrf": csrf, 600 }) 601 ctx.Session.MakeRequest(t, req, http.StatusOK) 602 } 603 } 604 605 func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { 606 return func(t *testing.T) { 607 defer tests.PrintCurrentTest(t)() 608 609 ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) 610 611 t.Run("CheckoutProtected", doGitCheckoutBranch(dstPath, "protected")) 612 t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) 613 t.Run("GenerateCommit", func(t *testing.T) { 614 _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") 615 assert.NoError(t, err) 616 }) 617 t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected3")) 618 var pr api.PullRequest 619 var err error 620 t.Run("CreatePullRequest", func(t *testing.T) { 621 pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected3")(t) 622 assert.NoError(t, err) 623 }) 624 625 // Request repository commits page 626 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", baseCtx.Username, baseCtx.Reponame, pr.Index)) 627 resp := ctx.Session.MakeRequest(t, req, http.StatusOK) 628 doc := NewHTMLParser(t, resp.Body) 629 630 // Get first commit URL 631 commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") 632 assert.True(t, exists) 633 assert.NotEmpty(t, commitURL) 634 635 commitID := path.Base(commitURL) 636 637 addCommitStatus := func(status api.CommitStatusState) func(*testing.T) { 638 return doAPICreateCommitStatus(ctx, commitID, api.CreateStatusOption{ 639 State: status, 640 TargetURL: "http://test.ci/", 641 Description: "", 642 Context: "testci", 643 }) 644 } 645 646 // Call API to add Pending status for commit 647 t.Run("CreateStatus", addCommitStatus(api.CommitStatusPending)) 648 649 // Cancel not existing auto merge 650 ctx.ExpectedCode = http.StatusNotFound 651 t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 652 653 // Add auto merge request 654 ctx.ExpectedCode = http.StatusCreated 655 t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 656 657 // Can not create schedule twice 658 ctx.ExpectedCode = http.StatusConflict 659 t.Run("AutoMergePRTwice", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 660 661 // Cancel auto merge request 662 ctx.ExpectedCode = http.StatusNoContent 663 t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 664 665 // Add auto merge request 666 ctx.ExpectedCode = http.StatusCreated 667 t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 668 669 // Check pr status 670 ctx.ExpectedCode = 0 671 pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) 672 assert.NoError(t, err) 673 assert.False(t, pr.HasMerged) 674 675 // Call API to add Failure status for commit 676 t.Run("CreateStatus", addCommitStatus(api.CommitStatusFailure)) 677 678 // Check pr status 679 pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) 680 assert.NoError(t, err) 681 assert.False(t, pr.HasMerged) 682 683 // Call API to add Success status for commit 684 t.Run("CreateStatus", addCommitStatus(api.CommitStatusSuccess)) 685 686 // wait to let gitea merge stuff 687 time.Sleep(time.Second) 688 689 // test pr status 690 pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) 691 assert.NoError(t, err) 692 assert.True(t, pr.HasMerged) 693 } 694 } 695 696 func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headBranch string) func(t *testing.T) { 697 return func(t *testing.T) { 698 defer tests.PrintCurrentTest(t)() 699 700 // skip this test if git version is low 701 if !git.DefaultFeatures().SupportProcReceive { 702 return 703 } 704 705 gitRepo, err := git.OpenRepository(git.DefaultContext, dstPath) 706 if !assert.NoError(t, err) { 707 return 708 } 709 defer gitRepo.Close() 710 711 var ( 712 pr1, pr2 *issues_model.PullRequest 713 commit string 714 ) 715 repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ctx.Username, ctx.Reponame) 716 if !assert.NoError(t, err) { 717 return 718 } 719 720 pullNum := unittest.GetCount(t, &issues_model.PullRequest{}) 721 722 t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) 723 724 t.Run("AddCommit", func(t *testing.T) { 725 err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666) 726 if !assert.NoError(t, err) { 727 return 728 } 729 730 err = git.AddChanges(dstPath, true) 731 assert.NoError(t, err) 732 733 err = git.CommitChanges(dstPath, git.CommitChangesOptions{ 734 Committer: &git.Signature{ 735 Email: "user2@example.com", 736 Name: "user2", 737 When: time.Now(), 738 }, 739 Author: &git.Signature{ 740 Email: "user2@example.com", 741 Name: "user2", 742 When: time.Now(), 743 }, 744 Message: "Testing commit 1", 745 }) 746 assert.NoError(t, err) 747 commit, err = gitRepo.GetRefCommitID("HEAD") 748 assert.NoError(t, err) 749 }) 750 751 t.Run("Push", func(t *testing.T) { 752 err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + headBranch).Run(&git.RunOpts{Dir: dstPath}) 753 if !assert.NoError(t, err) { 754 return 755 } 756 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1) 757 pr1 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ 758 HeadRepoID: repo.ID, 759 Flow: issues_model.PullRequestFlowAGit, 760 }) 761 if !assert.NotEmpty(t, pr1) { 762 return 763 } 764 prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) 765 if !assert.NoError(t, err) { 766 return 767 } 768 assert.Equal(t, "user2/"+headBranch, pr1.HeadBranch) 769 assert.False(t, prMsg.HasMerged) 770 assert.Contains(t, "Testing commit 1", prMsg.Body) 771 assert.Equal(t, commit, prMsg.Head.Sha) 772 773 _, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) 774 if !assert.NoError(t, err) { 775 return 776 } 777 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) 778 pr2 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ 779 HeadRepoID: repo.ID, 780 Index: pr1.Index + 1, 781 Flow: issues_model.PullRequestFlowAGit, 782 }) 783 if !assert.NotEmpty(t, pr2) { 784 return 785 } 786 prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) 787 if !assert.NoError(t, err) { 788 return 789 } 790 assert.Equal(t, "user2/test/"+headBranch, pr2.HeadBranch) 791 assert.False(t, prMsg.HasMerged) 792 }) 793 794 if pr1 == nil || pr2 == nil { 795 return 796 } 797 798 t.Run("AddCommit2", func(t *testing.T) { 799 err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0o666) 800 if !assert.NoError(t, err) { 801 return 802 } 803 804 err = git.AddChanges(dstPath, true) 805 assert.NoError(t, err) 806 807 err = git.CommitChanges(dstPath, git.CommitChangesOptions{ 808 Committer: &git.Signature{ 809 Email: "user2@example.com", 810 Name: "user2", 811 When: time.Now(), 812 }, 813 Author: &git.Signature{ 814 Email: "user2@example.com", 815 Name: "user2", 816 When: time.Now(), 817 }, 818 Message: "Testing commit 2", 819 }) 820 assert.NoError(t, err) 821 commit, err = gitRepo.GetRefCommitID("HEAD") 822 assert.NoError(t, err) 823 }) 824 825 t.Run("Push2", func(t *testing.T) { 826 err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + headBranch).Run(&git.RunOpts{Dir: dstPath}) 827 if !assert.NoError(t, err) { 828 return 829 } 830 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) 831 prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) 832 if !assert.NoError(t, err) { 833 return 834 } 835 assert.False(t, prMsg.HasMerged) 836 assert.Equal(t, commit, prMsg.Head.Sha) 837 838 _, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) 839 if !assert.NoError(t, err) { 840 return 841 } 842 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) 843 prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) 844 if !assert.NoError(t, err) { 845 return 846 } 847 assert.False(t, prMsg.HasMerged) 848 assert.Equal(t, commit, prMsg.Head.Sha) 849 }) 850 t.Run("Merge", doAPIMergePullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)) 851 t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master")) 852 } 853 } 854 855 func TestDataAsync_Issue29101(t *testing.T) { 856 onGiteaRun(t, func(t *testing.T, u *url.URL) { 857 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 858 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 859 860 resp, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, user, &files_service.ChangeRepoFilesOptions{ 861 Files: []*files_service.ChangeRepoFile{ 862 { 863 Operation: "create", 864 TreePath: "test.txt", 865 ContentReader: bytes.NewReader(make([]byte, 10000)), 866 }, 867 }, 868 OldBranch: repo.DefaultBranch, 869 NewBranch: repo.DefaultBranch, 870 }) 871 assert.NoError(t, err) 872 873 sha := resp.Commit.SHA 874 875 gitRepo, err := gitrepo.OpenRepository(db.DefaultContext, repo) 876 assert.NoError(t, err) 877 878 commit, err := gitRepo.GetCommit(sha) 879 assert.NoError(t, err) 880 881 entry, err := commit.GetTreeEntryByPath("test.txt") 882 assert.NoError(t, err) 883 884 b := entry.Blob() 885 886 r, err := b.DataAsync() 887 assert.NoError(t, err) 888 defer r.Close() 889 890 r2, err := b.DataAsync() 891 assert.NoError(t, err) 892 defer r2.Close() 893 }) 894 }