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