loading up the forgejo repo on tangled to test page performance
at forgejo 1124 lines 45 kB view raw
1// Copyright 2017 The Gitea Authors. All rights reserved. 2// SPDX-License-Identifier: MIT 3 4package integration 5 6import ( 7 "bytes" 8 "crypto/rand" 9 "encoding/hex" 10 "fmt" 11 "io" 12 "net/http" 13 "net/url" 14 "os" 15 "path" 16 "path/filepath" 17 "strconv" 18 "testing" 19 "time" 20 21 auth_model "forgejo.org/models/auth" 22 "forgejo.org/models/db" 23 git_model "forgejo.org/models/git" 24 issues_model "forgejo.org/models/issues" 25 "forgejo.org/models/perm" 26 repo_model "forgejo.org/models/repo" 27 "forgejo.org/models/unittest" 28 user_model "forgejo.org/models/user" 29 "forgejo.org/modules/git" 30 "forgejo.org/modules/gitrepo" 31 "forgejo.org/modules/lfs" 32 "forgejo.org/modules/setting" 33 api "forgejo.org/modules/structs" 34 gitea_context "forgejo.org/services/context" 35 files_service "forgejo.org/services/repository/files" 36 "forgejo.org/tests" 37 38 "github.com/stretchr/testify/assert" 39 "github.com/stretchr/testify/require" 40) 41 42const ( 43 littleSize = 1024 // 1KiB 44 bigSize = 32 * 1024 * 1024 // 32MiB 45) 46 47func TestGit(t *testing.T) { 48 onGiteaRun(t, testGit) 49} 50 51func testGit(t *testing.T, u *url.URL) { 52 username := "user2" 53 baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 54 55 u.Path = baseAPITestContext.GitPath() 56 57 forkedUserCtx := NewAPITestContext(t, "user4", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) 58 59 t.Run("HTTP", func(t *testing.T) { 60 ensureAnonymousClone(t, u) 61 forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) { 62 defer tests.PrintCurrentTest(t)() 63 httpContext := baseAPITestContext 64 httpContext.Reponame = "repo-tmp-17-" + objectFormat.Name() 65 forkedUserCtx.Reponame = httpContext.Reponame 66 67 dstPath := t.TempDir() 68 69 t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false, objectFormat)) 70 t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, httpContext.Username, perm.AccessModeRead)) 71 72 t.Run("ForkFromDifferentUser", doAPIForkRepository(httpContext, forkedUserCtx.Username)) 73 74 u.Path = httpContext.GitPath() 75 u.User = url.UserPassword(username, userPassword) 76 77 t.Run("Clone", doGitClone(dstPath, u)) 78 79 dstPath2 := t.TempDir() 80 81 t.Run("Partial Clone", doPartialGitClone(dstPath2, u)) 82 83 little, big := standardCommitAndPushTest(t, dstPath) 84 littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath) 85 rawTest(t, &httpContext, little, big, littleLFS, bigLFS) 86 mediaTest(t, &httpContext, little, big, littleLFS, bigLFS) 87 88 t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "test/head")) 89 t.Run("InternalReferences", doInternalReferences(&httpContext, dstPath)) 90 t.Run("BranchProtect", doBranchProtect(&httpContext, dstPath)) 91 t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath)) 92 t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge")) 93 t.Run("MergeFork", func(t *testing.T) { 94 defer tests.PrintCurrentTest(t)() 95 t.Run("CreatePRAndMerge", doMergeFork(httpContext, forkedUserCtx, "master", httpContext.Username+":master")) 96 rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) 97 mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) 98 }) 99 100 t.Run("PushCreate", doPushCreate(httpContext, u, objectFormat)) 101 }) 102 }) 103 t.Run("SSH", func(t *testing.T) { 104 forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) { 105 defer tests.PrintCurrentTest(t)() 106 sshContext := baseAPITestContext 107 sshContext.Reponame = "repo-tmp-18-" + objectFormat.Name() 108 keyname := "my-testing-key" 109 forkedUserCtx.Reponame = sshContext.Reponame 110 t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false, objectFormat)) 111 t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, sshContext.Username, perm.AccessModeRead)) 112 t.Run("ForkFromDifferentUser", doAPIForkRepository(sshContext, forkedUserCtx.Username)) 113 114 // Setup key the user ssh key 115 withKeyFile(t, keyname, func(keyFile string) { 116 t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key-"+objectFormat.Name(), keyFile)) 117 118 // Setup remote link 119 // TODO: get url from api 120 sshURL := createSSHUrl(sshContext.GitPath(), u) 121 122 // Setup clone folder 123 dstPath := t.TempDir() 124 125 t.Run("Clone", doGitClone(dstPath, sshURL)) 126 127 little, big := standardCommitAndPushTest(t, dstPath) 128 littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath) 129 rawTest(t, &sshContext, little, big, littleLFS, bigLFS) 130 mediaTest(t, &sshContext, little, big, littleLFS, bigLFS) 131 132 t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "test/head2")) 133 t.Run("InternalReferences", doInternalReferences(&sshContext, dstPath)) 134 t.Run("BranchProtect", doBranchProtect(&sshContext, dstPath)) 135 t.Run("MergeFork", func(t *testing.T) { 136 defer tests.PrintCurrentTest(t)() 137 t.Run("CreatePRAndMerge", doMergeFork(sshContext, forkedUserCtx, "master", sshContext.Username+":master")) 138 rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) 139 mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) 140 }) 141 142 t.Run("PushCreate", doPushCreate(sshContext, sshURL, objectFormat)) 143 }) 144 }) 145 }) 146} 147 148func ensureAnonymousClone(t *testing.T, u *url.URL) { 149 dstLocalPath := t.TempDir() 150 t.Run("CloneAnonymous", doGitClone(dstLocalPath, u)) 151} 152 153func standardCommitAndPushTest(t *testing.T, dstPath string) (little, big string) { 154 t.Run("Standard", func(t *testing.T) { 155 defer tests.PrintCurrentTest(t)() 156 little, big = commitAndPushTest(t, dstPath, "data-file-") 157 }) 158 return little, big 159} 160 161func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS string) { 162 t.Run("LFS", func(t *testing.T) { 163 defer tests.PrintCurrentTest(t)() 164 prefix := "lfs-data-file-" 165 err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").Run(&git.RunOpts{Dir: dstPath}) 166 require.NoError(t, err) 167 _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("track").AddDynamicArguments(prefix + "*").RunStdString(&git.RunOpts{Dir: dstPath}) 168 require.NoError(t, err) 169 err = git.AddChanges(dstPath, false, ".gitattributes") 170 require.NoError(t, err) 171 172 err = git.CommitChangesWithArgs(dstPath, git.AllowLFSFiltersArgs(), git.CommitChangesOptions{ 173 Committer: &git.Signature{ 174 Email: "user2@example.com", 175 Name: "User Two", 176 When: time.Now(), 177 }, 178 Author: &git.Signature{ 179 Email: "user2@example.com", 180 Name: "User Two", 181 When: time.Now(), 182 }, 183 Message: fmt.Sprintf("Testing commit @ %v", time.Now()), 184 }) 185 require.NoError(t, err) 186 187 littleLFS, bigLFS = commitAndPushTest(t, dstPath, prefix) 188 189 t.Run("Locks", func(t *testing.T) { 190 defer tests.PrintCurrentTest(t)() 191 lockTest(t, dstPath) 192 }) 193 }) 194 return littleLFS, bigLFS 195} 196 197func commitAndPushTest(t *testing.T, dstPath, prefix string) (little, big string) { 198 t.Run("PushCommit", func(t *testing.T) { 199 defer tests.PrintCurrentTest(t)() 200 t.Run("Little", func(t *testing.T) { 201 defer tests.PrintCurrentTest(t)() 202 little = doCommitAndPush(t, littleSize, dstPath, prefix) 203 }) 204 t.Run("Big", func(t *testing.T) { 205 if testing.Short() { 206 t.Skip("Skipping test in short mode.") 207 return 208 } 209 defer tests.PrintCurrentTest(t)() 210 big = doCommitAndPush(t, bigSize, dstPath, prefix) 211 }) 212 }) 213 return little, big 214} 215 216func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) { 217 t.Run("Raw", func(t *testing.T) { 218 defer tests.PrintCurrentTest(t)() 219 username := ctx.Username 220 reponame := ctx.Reponame 221 222 session := loginUser(t, username) 223 224 // Request raw paths 225 req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little)) 226 resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) 227 assert.Equal(t, littleSize, resp.Length) 228 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 tests.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 req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS)) 271 resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) 272 assert.Equal(t, littleSize, resp.Length) 273 274 if !testing.Short() { 275 req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big)) 276 resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) 277 assert.Equal(t, bigSize, resp.Length) 278 279 if setting.LFS.StartServer { 280 req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS)) 281 resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK) 282 assert.Equal(t, bigSize, resp.Length) 283 } 284 } 285 }) 286} 287 288func lockTest(t *testing.T, repoPath string) { 289 lockFileTest(t, "README.md", repoPath) 290} 291 292func lockFileTest(t *testing.T, filename, repoPath string) { 293 _, _, err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) 294 require.NoError(t, err) 295 _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath}) 296 require.NoError(t, err) 297 _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) 298 require.NoError(t, err) 299 _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath}) 300 require.NoError(t, err) 301} 302 303func doCommitAndPush(t *testing.T, size int64, repoPath, prefix string) string { 304 name := generateCommitWithNewData(t, size, repoPath, "user2@example.com", "User Two", prefix) 305 _, _, err := git.NewCommand(git.DefaultContext, "push", "origin", "master").RunStdString(&git.RunOpts{Dir: repoPath}) // Push 306 require.NoError(t, err) 307 return name 308} 309 310func generateCommitWithNewData(t *testing.T, size int64, repoPath, email, fullName, prefix string) string { 311 t.Helper() 312 tmpFile, err := os.CreateTemp(repoPath, prefix) 313 require.NoError(t, err) 314 defer tmpFile.Close() 315 _, err = io.CopyN(tmpFile, rand.Reader, size) 316 require.NoError(t, err) 317 318 // Commit 319 // Now here we should explicitly allow lfs filters to run 320 globalArgs := git.AllowLFSFiltersArgs() 321 require.NoError(t, git.AddChangesWithArgs(repoPath, globalArgs, false, filepath.Base(tmpFile.Name()))) 322 require.NoError(t, git.CommitChangesWithArgs(repoPath, globalArgs, git.CommitChangesOptions{ 323 Committer: &git.Signature{ 324 Email: email, 325 Name: fullName, 326 When: time.Now(), 327 }, 328 Author: &git.Signature{ 329 Email: email, 330 Name: fullName, 331 When: time.Now(), 332 }, 333 Message: fmt.Sprintf("Testing commit @ %v", time.Now()), 334 })) 335 return filepath.Base(tmpFile.Name()) 336} 337 338func doBranchProtect(baseCtx *APITestContext, dstPath string) func(t *testing.T) { 339 return func(t *testing.T) { 340 defer tests.PrintCurrentTest(t)() 341 t.Run("CreateBranchProtected", doGitCreateBranch(dstPath, "protected")) 342 t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) 343 344 ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) 345 346 t.Run("PushToNewProtectedBranch", func(t *testing.T) { 347 defer tests.PrintCurrentTest(t)() 348 t.Run("CreateBranchProtected", doGitCreateBranch(dstPath, "before-create-1")) 349 t.Run("ProtectProtectedBranch", doProtectBranch(ctx, "before-create-1", parameterProtectBranch{ 350 "enable_push": "all", 351 "apply_to_admins": "on", 352 })) 353 t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "before-create-1")) 354 355 t.Run("GenerateCommit", func(t *testing.T) { 356 generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "protected-file-data-") 357 }) 358 359 t.Run("ProtectProtectedBranch", doProtectBranch(ctx, "before-create-2", parameterProtectBranch{ 360 "enable_push": "all", 361 "protected_file_patterns": "protected-file-data-*", 362 "apply_to_admins": "on", 363 })) 364 365 doGitPushTestRepositoryFail(dstPath, "origin", "HEAD:before-create-2")(t) 366 }) 367 368 t.Run("FailToPushToProtectedBranch", func(t *testing.T) { 369 defer tests.PrintCurrentTest(t)() 370 t.Run("ProtectProtectedBranch", doProtectBranch(ctx, "protected")) 371 t.Run("Create modified-protected-branch", doGitCheckoutBranch(dstPath, "-b", "modified-protected-branch", "protected")) 372 t.Run("GenerateCommit", func(t *testing.T) { 373 generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") 374 }) 375 376 doGitPushTestRepositoryFail(dstPath, "origin", "modified-protected-branch:protected")(t) 377 }) 378 379 t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "modified-protected-branch:unprotected")) 380 381 t.Run("FailToPushProtectedFilesToProtectedBranch", func(t *testing.T) { 382 defer tests.PrintCurrentTest(t)() 383 t.Run("Create modified-protected-file-protected-branch", doGitCheckoutBranch(dstPath, "-b", "modified-protected-file-protected-branch", "protected")) 384 t.Run("GenerateCommit", func(t *testing.T) { 385 generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "protected-file-") 386 }) 387 388 t.Run("ProtectedFilePathsApplyToAdmins", doProtectBranch(ctx, "protected")) 389 doGitPushTestRepositoryFail(dstPath, "origin", "modified-protected-file-protected-branch:protected")(t) 390 391 doGitCheckoutBranch(dstPath, "protected")(t) 392 doGitPull(dstPath, "origin", "protected")(t) 393 }) 394 395 t.Run("PushUnprotectedFilesToProtectedBranch", func(t *testing.T) { 396 defer tests.PrintCurrentTest(t)() 397 t.Run("Create modified-unprotected-file-protected-branch", doGitCheckoutBranch(dstPath, "-b", "modified-unprotected-file-protected-branch", "protected")) 398 t.Run("UnprotectedFilePaths", doProtectBranch(ctx, "protected", parameterProtectBranch{ 399 "unprotected_file_patterns": "unprotected-file-*", 400 })) 401 t.Run("GenerateCommit", func(t *testing.T) { 402 generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "unprotected-file-") 403 }) 404 doGitPushTestRepository(dstPath, "origin", "modified-unprotected-file-protected-branch:protected")(t) 405 doGitCheckoutBranch(dstPath, "protected")(t) 406 doGitPull(dstPath, "origin", "protected")(t) 407 }) 408 409 user, err := user_model.GetUserByName(db.DefaultContext, baseCtx.Username) 410 require.NoError(t, err) 411 t.Run("WhitelistUsers", doProtectBranch(ctx, "protected", parameterProtectBranch{ 412 "enable_push": "whitelist", 413 "enable_whitelist": "on", 414 "whitelist_users": strconv.FormatInt(user.ID, 10), 415 })) 416 417 t.Run("WhitelistedUserFailToForcePushToProtectedBranch", func(t *testing.T) { 418 defer tests.PrintCurrentTest(t)() 419 t.Run("Create toforce", doGitCheckoutBranch(dstPath, "-b", "toforce", "master")) 420 t.Run("GenerateCommit", func(t *testing.T) { 421 generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") 422 }) 423 doGitPushTestRepositoryFail(dstPath, "-f", "origin", "toforce:protected")(t) 424 }) 425 426 t.Run("WhitelistedUserPushToProtectedBranch", func(t *testing.T) { 427 defer tests.PrintCurrentTest(t)() 428 t.Run("Create topush", doGitCheckoutBranch(dstPath, "-b", "topush", "protected")) 429 t.Run("GenerateCommit", func(t *testing.T) { 430 generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") 431 }) 432 doGitPushTestRepository(dstPath, "origin", "topush:protected")(t) 433 }) 434 } 435} 436 437type parameterProtectBranch map[string]string 438 439func doProtectBranch(ctx APITestContext, branch string, addParameter ...parameterProtectBranch) func(t *testing.T) { 440 // We are going to just use the owner to set the protection. 441 return func(t *testing.T) { 442 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: ctx.Reponame, OwnerName: ctx.Username}) 443 rule := &git_model.ProtectedBranch{RuleName: branch, RepoID: repo.ID} 444 unittest.LoadBeanIfExists(rule) 445 446 csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings/branches", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame))) 447 448 parameter := parameterProtectBranch{ 449 "_csrf": csrf, 450 "rule_id": strconv.FormatInt(rule.ID, 10), 451 "rule_name": branch, 452 } 453 if len(addParameter) > 0 { 454 for k, v := range addParameter[0] { 455 parameter[k] = v 456 } 457 } 458 459 // Change branch to protected 460 req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), parameter) 461 ctx.Session.MakeRequest(t, req, http.StatusSeeOther) 462 // Check if master branch has been locked successfully 463 flashCookie := ctx.Session.GetCookie(gitea_context.CookieNameFlash) 464 assert.NotNil(t, flashCookie) 465 assert.Equal(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522"+url.QueryEscape(branch)+"%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) 466 } 467} 468 469func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) func(t *testing.T) { 470 return func(t *testing.T) { 471 defer tests.PrintCurrentTest(t)() 472 var pr api.PullRequest 473 var err error 474 475 // Create a test pull request 476 t.Run("CreatePullRequest", func(t *testing.T) { 477 pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t) 478 require.NoError(t, err) 479 }) 480 481 // Ensure the PR page works. 482 // For the base repository owner, the PR is not editable (maintainer edits are not enabled): 483 t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr, false)) 484 // For the head repository owner, the PR is editable: 485 headSession := loginUser(t, "user2") 486 headToken := getTokenForLoggedInUser(t, headSession, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopeReadUser) 487 headCtx := APITestContext{ 488 Session: headSession, 489 Token: headToken, 490 Username: baseCtx.Username, 491 Reponame: baseCtx.Reponame, 492 } 493 t.Run("EnsureCanSeePull", doEnsureCanSeePull(headCtx, pr, true)) 494 495 // Confirm that there is no AGit Label 496 // TODO: Refactor and move this check to a function 497 t.Run("AGitLabelIsMissing", func(t *testing.T) { 498 defer tests.PrintCurrentTest(t)() 499 500 session := loginUser(t, ctx.Username) 501 502 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", baseCtx.Username, baseCtx.Reponame, pr.Index)) 503 resp := session.MakeRequest(t, req, http.StatusOK) 504 htmlDoc := NewHTMLParser(t, resp.Body) 505 htmlDoc.AssertElement(t, "#agit-label", false) 506 }) 507 508 // Then get the diff string 509 var diffHash string 510 var diffLength int 511 t.Run("GetDiff", func(t *testing.T) { 512 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(baseCtx.Username), url.PathEscape(baseCtx.Reponame), pr.Index)) 513 resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK) 514 diffHash = string(resp.Hash.Sum(nil)) 515 diffLength = resp.Length 516 }) 517 518 // Now: Merge the PR & make sure that doesn't break the PR page or change its diff 519 t.Run("MergePR", doAPIMergePullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 520 // for both users the PR is still visible but not editable anymore after it was merged 521 t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr, false)) 522 t.Run("EnsureCanSeePull", doEnsureCanSeePull(headCtx, pr, false)) 523 t.Run("CheckPR", func(t *testing.T) { 524 oldMergeBase := pr.MergeBase 525 pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) 526 require.NoError(t, err) 527 assert.Equal(t, oldMergeBase, pr2.MergeBase) 528 }) 529 t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) 530 531 // Then: Delete the head branch & make sure that doesn't break the PR page or change its diff 532 t.Run("DeleteHeadBranch", doBranchDelete(baseCtx, baseCtx.Username, baseCtx.Reponame, headBranch)) 533 t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr, false)) 534 t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) 535 536 // Delete the head repository & make sure that doesn't break the PR page or change its diff 537 t.Run("DeleteHeadRepository", doAPIDeleteRepository(ctx)) 538 t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr, false)) 539 t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) 540 } 541} 542 543func doCreatePRAndSetManuallyMerged(ctx, baseCtx APITestContext, dstPath, baseBranch, headBranch string) func(t *testing.T) { 544 return func(t *testing.T) { 545 defer tests.PrintCurrentTest(t)() 546 var ( 547 pr api.PullRequest 548 err error 549 lastCommitID string 550 ) 551 552 trueBool := true 553 falseBool := false 554 555 t.Run("AllowSetManuallyMergedAndSwitchOffAutodetectManualMerge", doAPIEditRepository(baseCtx, &api.EditRepoOption{ 556 HasPullRequests: &trueBool, 557 AllowManualMerge: &trueBool, 558 AutodetectManualMerge: &falseBool, 559 })) 560 561 t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) 562 t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", headBranch)) 563 t.Run("CreateEmptyPullRequest", func(t *testing.T) { 564 pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t) 565 require.NoError(t, err) 566 }) 567 lastCommitID = pr.Base.Sha 568 t.Run("ManuallyMergePR", doAPIManuallyMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, lastCommitID, pr.Index)) 569 } 570} 571 572func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest, editable bool) func(t *testing.T) { 573 return func(t *testing.T) { 574 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) 575 ctx.Session.MakeRequest(t, req, http.StatusOK) 576 req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/files", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) 577 resp := ctx.Session.MakeRequest(t, req, http.StatusOK) 578 doc := NewHTMLParser(t, resp.Body) 579 editButtonCount := doc.doc.Find("div.diff-file-header-actions a[href*='/_edit/']").Length() 580 if editable { 581 assert.Positive(t, editButtonCount, "Expected to find a button to edit a file in the PR diff view but there were none") 582 } else { 583 assert.Equal(t, 0, editButtonCount, "Expected not to find any buttons to edit files in PR diff view but there were some") 584 } 585 req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) 586 ctx.Session.MakeRequest(t, req, http.StatusOK) 587 } 588} 589 590func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffHash string, diffLength int) func(t *testing.T) { 591 return func(t *testing.T) { 592 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index)) 593 resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK) 594 actual := string(resp.Hash.Sum(nil)) 595 actualLength := resp.Length 596 597 equal := diffHash == actual 598 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) 599 } 600} 601 602func doPushCreate(ctx APITestContext, u *url.URL, objectFormat git.ObjectFormat) func(t *testing.T) { 603 return func(t *testing.T) { 604 if objectFormat == git.Sha256ObjectFormat { 605 t.Skipf("push-create not supported for %s, see https://codeberg.org/forgejo/forgejo/issues/3783", objectFormat) 606 } 607 defer tests.PrintCurrentTest(t)() 608 609 // create a context for a currently non-existent repository 610 ctx.Reponame = fmt.Sprintf("repo-tmp-push-create-%s", u.Scheme) 611 u.Path = ctx.GitPath() 612 613 // Create a temporary directory 614 tmpDir := t.TempDir() 615 616 // Now create local repository to push as our test and set its origin 617 t.Run("InitTestRepository", doGitInitTestRepository(tmpDir, objectFormat)) 618 t.Run("AddRemote", doGitAddRemote(tmpDir, "origin", u)) 619 620 // Disable "Push To Create" and attempt to push 621 setting.Repository.EnablePushCreateUser = false 622 t.Run("FailToPushAndCreateTestRepository", doGitPushTestRepositoryFail(tmpDir, "origin", "master")) 623 624 // Enable "Push To Create" 625 setting.Repository.EnablePushCreateUser = true 626 627 // Assert that cloning from a non-existent repository does not create it and that it definitely wasn't create above 628 t.Run("FailToCloneFromNonExistentRepository", doGitCloneFail(u)) 629 630 // Then "Push To Create"x 631 t.Run("SuccessfullyPushAndCreateTestRepository", doGitPushTestRepository(tmpDir, "origin", "master")) 632 633 // Finally, fetch repo from database and ensure the correct repository has been created 634 repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ctx.Username, ctx.Reponame) 635 require.NoError(t, err) 636 assert.False(t, repo.IsEmpty) 637 assert.True(t, repo.IsPrivate) 638 639 // Now add a remote that is invalid to "Push To Create" 640 invalidCtx := ctx 641 invalidCtx.Reponame = fmt.Sprintf("invalid/repo-tmp-push-create-%s", u.Scheme) 642 u.Path = invalidCtx.GitPath() 643 t.Run("AddInvalidRemote", doGitAddRemote(tmpDir, "invalid", u)) 644 645 // Fail to "Push To Create" the invalid 646 t.Run("FailToPushAndCreateInvalidTestRepository", doGitPushTestRepositoryFail(tmpDir, "invalid", "master")) 647 } 648} 649 650func doBranchDelete(ctx APITestContext, owner, repo, branch string) func(*testing.T) { 651 return func(t *testing.T) { 652 csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/branches", url.PathEscape(owner), url.PathEscape(repo))) 653 654 req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/branches/delete?name=%s", url.PathEscape(owner), url.PathEscape(repo), url.QueryEscape(branch)), map[string]string{ 655 "_csrf": csrf, 656 }) 657 ctx.Session.MakeRequest(t, req, http.StatusOK) 658 } 659} 660 661func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { 662 return func(t *testing.T) { 663 defer tests.PrintCurrentTest(t)() 664 665 ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) 666 667 t.Run("CheckoutProtected", doGitCheckoutBranch(dstPath, "protected")) 668 t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) 669 t.Run("GenerateCommit", func(t *testing.T) { 670 generateCommitWithNewData(t, littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") 671 }) 672 t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected3")) 673 var pr api.PullRequest 674 var err error 675 t.Run("CreatePullRequest", func(t *testing.T) { 676 pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected3")(t) 677 require.NoError(t, err) 678 }) 679 680 // Request repository commits page 681 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", baseCtx.Username, baseCtx.Reponame, pr.Index)) 682 resp := ctx.Session.MakeRequest(t, req, http.StatusOK) 683 doc := NewHTMLParser(t, resp.Body) 684 685 // Get first commit URL 686 commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") 687 assert.True(t, exists) 688 assert.NotEmpty(t, commitURL) 689 690 commitID := path.Base(commitURL) 691 692 addCommitStatus := func(status api.CommitStatusState) func(*testing.T) { 693 return doAPICreateCommitStatus(ctx, commitID, api.CreateStatusOption{ 694 State: status, 695 TargetURL: "http://test.ci/", 696 Description: "", 697 Context: "testci", 698 }) 699 } 700 701 // Call API to add Pending status for commit 702 t.Run("CreateStatus", addCommitStatus(api.CommitStatusPending)) 703 704 // Cancel not existing auto merge 705 ctx.ExpectedCode = http.StatusNotFound 706 t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 707 708 // Add auto merge request 709 ctx.ExpectedCode = http.StatusCreated 710 t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 711 712 // Can not create schedule twice 713 ctx.ExpectedCode = http.StatusConflict 714 t.Run("AutoMergePRTwice", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 715 716 // Cancel auto merge request 717 ctx.ExpectedCode = http.StatusNoContent 718 t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 719 720 // Add auto merge request 721 ctx.ExpectedCode = http.StatusCreated 722 t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)) 723 724 // Check pr status 725 ctx.ExpectedCode = 0 726 pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) 727 require.NoError(t, err) 728 assert.False(t, pr.HasMerged) 729 730 // Call API to add Failure status for commit 731 t.Run("CreateStatus", addCommitStatus(api.CommitStatusFailure)) 732 733 // Check pr status 734 pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) 735 require.NoError(t, err) 736 assert.False(t, pr.HasMerged) 737 738 // Call API to add Success status for commit 739 t.Run("CreateStatus", addCommitStatus(api.CommitStatusSuccess)) 740 741 // wait to let gitea merge stuff 742 time.Sleep(time.Second) 743 744 // test pr status 745 pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) 746 require.NoError(t, err) 747 assert.True(t, pr.HasMerged) 748 } 749} 750 751func doInternalReferences(ctx *APITestContext, dstPath string) func(t *testing.T) { 752 return func(t *testing.T) { 753 defer tests.PrintCurrentTest(t)() 754 755 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: ctx.Username, Name: ctx.Reponame}) 756 pr1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{HeadRepoID: repo.ID}) 757 758 _, stdErr, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments(fmt.Sprintf(":refs/pull/%d/head", pr1.Index)).RunStdString(&git.RunOpts{Dir: dstPath}) 759 require.Error(t, gitErr) 760 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)) 761 assert.Contains(t, stdErr, fmt.Sprintf("[remote rejected] refs/pull/%d/head (hook declined)", pr1.Index)) 762 763 _, stdErr, gitErr = git.NewCommand(git.DefaultContext, "push", "origin", "--force").AddDynamicArguments(fmt.Sprintf("HEAD~1:refs/pull/%d/head", pr1.Index)).RunStdString(&git.RunOpts{Dir: dstPath}) 764 require.Error(t, gitErr) 765 assert.Contains(t, stdErr, fmt.Sprintf("remote: Forgejo: The modification of refs/pull/%d/head is skipped as it's an internal reference.", pr1.Index)) 766 assert.Contains(t, stdErr, fmt.Sprintf("[remote rejected] HEAD~1 -> refs/pull/%d/head (hook declined)", pr1.Index)) 767 } 768} 769 770func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string) func(t *testing.T) { 771 return func(t *testing.T) { 772 defer tests.PrintCurrentTest(t)() 773 774 // skip this test if git version is low 775 if git.CheckGitVersionAtLeast("2.29") != nil { 776 return 777 } 778 779 gitRepo, err := git.OpenRepository(git.DefaultContext, dstPath) 780 require.NoError(t, err) 781 782 defer gitRepo.Close() 783 784 var ( 785 pr1, pr2 *issues_model.PullRequest 786 commit string 787 ) 788 repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ctx.Username, ctx.Reponame) 789 require.NoError(t, err) 790 791 pullNum := unittest.GetCount(t, &issues_model.PullRequest{}) 792 793 t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) 794 795 t.Run("AddCommit", func(t *testing.T) { 796 err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666) 797 require.NoError(t, err) 798 799 err = git.AddChanges(dstPath, true) 800 require.NoError(t, err) 801 802 err = git.CommitChanges(dstPath, git.CommitChangesOptions{ 803 Committer: &git.Signature{ 804 Email: "user2@example.com", 805 Name: "user2", 806 When: time.Now(), 807 }, 808 Author: &git.Signature{ 809 Email: "user2@example.com", 810 Name: "user2", 811 When: time.Now(), 812 }, 813 Message: "Testing commit 1", 814 }) 815 require.NoError(t, err) 816 commit, err = gitRepo.GetRefCommitID("HEAD") 817 require.NoError(t, err) 818 }) 819 820 t.Run("Push", func(t *testing.T) { 821 err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + headBranch).Run(&git.RunOpts{Dir: dstPath}) 822 require.NoError(t, err) 823 824 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1) 825 pr1 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ 826 HeadRepoID: repo.ID, 827 Flow: issues_model.PullRequestFlowAGit, 828 }) 829 if !assert.NotEmpty(t, pr1) { 830 return 831 } 832 assert.Equal(t, 1, pr1.CommitsAhead) 833 assert.Equal(t, 0, pr1.CommitsBehind) 834 835 prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) 836 require.NoError(t, err) 837 838 assert.Equal(t, "user2/"+headBranch, pr1.HeadBranch) 839 assert.False(t, prMsg.HasMerged) 840 assert.Contains(t, "Testing commit 1", prMsg.Body) 841 assert.Equal(t, commit, prMsg.Head.Sha) 842 843 _, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) 844 require.NoError(t, err) 845 846 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) 847 pr2 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ 848 HeadRepoID: repo.ID, 849 Index: pr1.Index + 1, 850 Flow: issues_model.PullRequestFlowAGit, 851 }) 852 if !assert.NotEmpty(t, pr2) { 853 return 854 } 855 assert.Equal(t, 1, pr2.CommitsAhead) 856 assert.Equal(t, 0, pr2.CommitsBehind) 857 prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) 858 require.NoError(t, err) 859 860 assert.Equal(t, "user2/test/"+headBranch, pr2.HeadBranch) 861 assert.False(t, prMsg.HasMerged) 862 }) 863 864 if pr1 == nil || pr2 == nil { 865 return 866 } 867 868 t.Run("AGitLabelIsPresent", func(t *testing.T) { 869 defer tests.PrintCurrentTest(t)() 870 871 session := loginUser(t, ctx.Username) 872 873 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr2.Index)) 874 resp := session.MakeRequest(t, req, http.StatusOK) 875 htmlDoc := NewHTMLParser(t, resp.Body) 876 htmlDoc.AssertElement(t, "#agit-label", true) 877 }) 878 879 t.Run("AddCommit2", func(t *testing.T) { 880 err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0o666) 881 require.NoError(t, err) 882 883 err = git.AddChanges(dstPath, true) 884 require.NoError(t, err) 885 886 err = git.CommitChanges(dstPath, git.CommitChangesOptions{ 887 Committer: &git.Signature{ 888 Email: "user2@example.com", 889 Name: "user2", 890 When: time.Now(), 891 }, 892 Author: &git.Signature{ 893 Email: "user2@example.com", 894 Name: "user2", 895 When: time.Now(), 896 }, 897 Message: "Testing commit 2\n\nLonger description.", 898 }) 899 require.NoError(t, err) 900 commit, err = gitRepo.GetRefCommitID("HEAD") 901 require.NoError(t, err) 902 }) 903 904 t.Run("Push2", func(t *testing.T) { 905 err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + headBranch).Run(&git.RunOpts{Dir: dstPath}) 906 require.NoError(t, err) 907 908 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) 909 prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) 910 require.NoError(t, err) 911 912 assert.False(t, prMsg.HasMerged) 913 assert.Equal(t, commit, prMsg.Head.Sha) 914 915 pr1 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ 916 HeadRepoID: repo.ID, 917 Flow: issues_model.PullRequestFlowAGit, 918 Index: pr1.Index, 919 }) 920 assert.Equal(t, 2, pr1.CommitsAhead) 921 assert.Equal(t, 0, pr1.CommitsBehind) 922 923 _, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) 924 require.NoError(t, err) 925 926 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) 927 prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) 928 require.NoError(t, err) 929 930 assert.False(t, prMsg.HasMerged) 931 assert.Equal(t, commit, prMsg.Head.Sha) 932 }) 933 t.Run("PushParams", func(t *testing.T) { 934 defer tests.PrintCurrentTest(t)() 935 936 t.Run("NoParams", func(t *testing.T) { 937 defer tests.PrintCurrentTest(t)() 938 939 _, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-implicit").RunStdString(&git.RunOpts{Dir: dstPath}) 940 require.NoError(t, gitErr) 941 942 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+3) 943 pr3 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ 944 HeadRepoID: repo.ID, 945 Flow: issues_model.PullRequestFlowAGit, 946 Index: pr1.Index + 2, 947 }) 948 assert.NotEmpty(t, pr3) 949 err := pr3.LoadIssue(db.DefaultContext) 950 require.NoError(t, err) 951 952 _, err2 := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr3.Index)(t) 953 require.NoError(t, err2) 954 955 assert.Equal(t, "Testing commit 2", pr3.Issue.Title) 956 assert.Contains(t, pr3.Issue.Content, "Longer description.") 957 }) 958 t.Run("TitleOverride", func(t *testing.T) { 959 defer tests.PrintCurrentTest(t)() 960 961 _, _, 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}) 962 require.NoError(t, gitErr) 963 964 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+4) 965 pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ 966 HeadRepoID: repo.ID, 967 Flow: issues_model.PullRequestFlowAGit, 968 Index: pr1.Index + 3, 969 }) 970 assert.NotEmpty(t, pr) 971 err := pr.LoadIssue(db.DefaultContext) 972 require.NoError(t, err) 973 974 _, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t) 975 require.NoError(t, err) 976 977 assert.Equal(t, "my-shiny-title", pr.Issue.Title) 978 assert.Contains(t, pr.Issue.Content, "Longer description.") 979 }) 980 981 t.Run("DescriptionOverride", func(t *testing.T) { 982 defer tests.PrintCurrentTest(t)() 983 984 _, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin", "-o", "description=custom").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-implicit-3").RunStdString(&git.RunOpts{Dir: dstPath}) 985 require.NoError(t, gitErr) 986 987 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+5) 988 pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ 989 HeadRepoID: repo.ID, 990 Flow: issues_model.PullRequestFlowAGit, 991 Index: pr1.Index + 4, 992 }) 993 assert.NotEmpty(t, pr) 994 err := pr.LoadIssue(db.DefaultContext) 995 require.NoError(t, err) 996 997 _, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t) 998 require.NoError(t, err) 999 1000 assert.Equal(t, "Testing commit 2", pr.Issue.Title) 1001 assert.Contains(t, pr.Issue.Content, "custom") 1002 }) 1003 }) 1004 1005 upstreamGitRepo, err := git.OpenRepository(git.DefaultContext, filepath.Join(setting.RepoRootPath, ctx.Username, ctx.Reponame+".git")) 1006 require.NoError(t, err) 1007 defer upstreamGitRepo.Close() 1008 1009 t.Run("Force push", func(t *testing.T) { 1010 defer tests.PrintCurrentTest(t)() 1011 1012 _, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-force-push").RunStdString(&git.RunOpts{Dir: dstPath}) 1013 require.NoError(t, gitErr) 1014 1015 unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+6) 1016 pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ 1017 HeadRepoID: repo.ID, 1018 Flow: issues_model.PullRequestFlowAGit, 1019 Index: pr1.Index + 5, 1020 }) 1021 1022 headCommitID, err := upstreamGitRepo.GetRefCommitID(pr.GetGitRefName()) 1023 require.NoError(t, err) 1024 1025 _, _, gitErr = git.NewCommand(git.DefaultContext, "reset", "--hard", "HEAD~1").RunStdString(&git.RunOpts{Dir: dstPath}) 1026 require.NoError(t, gitErr) 1027 1028 t.Run("Fails", func(t *testing.T) { 1029 defer tests.PrintCurrentTest(t)() 1030 1031 _, stdErr, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-force-push").RunStdString(&git.RunOpts{Dir: dstPath}) 1032 require.Error(t, gitErr) 1033 1034 assert.Contains(t, stdErr, "-o force-push=true") 1035 1036 currentHeadCommitID, err := upstreamGitRepo.GetRefCommitID(pr.GetGitRefName()) 1037 require.NoError(t, err) 1038 assert.Equal(t, headCommitID, currentHeadCommitID) 1039 }) 1040 t.Run("Succeeds", func(t *testing.T) { 1041 defer tests.PrintCurrentTest(t)() 1042 1043 _, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin", "-o", "force-push").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-force-push").RunStdString(&git.RunOpts{Dir: dstPath}) 1044 require.NoError(t, gitErr) 1045 1046 currentHeadCommitID, err := upstreamGitRepo.GetRefCommitID(pr.GetGitRefName()) 1047 require.NoError(t, err) 1048 assert.NotEqual(t, headCommitID, currentHeadCommitID) 1049 }) 1050 }) 1051 1052 t.Run("Branch already contains commit", func(t *testing.T) { 1053 defer tests.PrintCurrentTest(t)() 1054 1055 branchCommit, err := upstreamGitRepo.GetBranchCommit("master") 1056 require.NoError(t, err) 1057 1058 _, _, gitErr := git.NewCommand(git.DefaultContext, "reset", "--hard").AddDynamicArguments(branchCommit.ID.String() + "~1").RunStdString(&git.RunOpts{Dir: dstPath}) 1059 require.NoError(t, gitErr) 1060 1061 _, stdErr, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-already-contains").RunStdString(&git.RunOpts{Dir: dstPath}) 1062 require.Error(t, gitErr) 1063 1064 assert.Contains(t, stdErr, "already contains this commit") 1065 }) 1066 1067 t.Run("Merge", doAPIMergePullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)) 1068 1069 t.Run("AGitLabelIsPresent Merged", func(t *testing.T) { 1070 defer tests.PrintCurrentTest(t)() 1071 1072 session := loginUser(t, ctx.Username) 1073 1074 req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr2.Index)) 1075 resp := session.MakeRequest(t, req, http.StatusOK) 1076 htmlDoc := NewHTMLParser(t, resp.Body) 1077 htmlDoc.AssertElement(t, "#agit-label", true) 1078 }) 1079 1080 t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master")) 1081 } 1082} 1083 1084func TestDataAsync_Issue29101(t *testing.T) { 1085 onGiteaRun(t, func(t *testing.T, u *url.URL) { 1086 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 1087 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 1088 1089 resp, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, user, &files_service.ChangeRepoFilesOptions{ 1090 Files: []*files_service.ChangeRepoFile{ 1091 { 1092 Operation: "create", 1093 TreePath: "test.txt", 1094 ContentReader: bytes.NewReader(make([]byte, 10000)), 1095 }, 1096 }, 1097 OldBranch: repo.DefaultBranch, 1098 NewBranch: repo.DefaultBranch, 1099 }) 1100 require.NoError(t, err) 1101 1102 sha := resp.Commit.SHA 1103 1104 gitRepo, err := gitrepo.OpenRepository(db.DefaultContext, repo) 1105 require.NoError(t, err) 1106 defer gitRepo.Close() 1107 1108 commit, err := gitRepo.GetCommit(sha) 1109 require.NoError(t, err) 1110 1111 entry, err := commit.GetTreeEntryByPath("test.txt") 1112 require.NoError(t, err) 1113 1114 b := entry.Blob() 1115 1116 r, err := b.DataAsync() 1117 require.NoError(t, err) 1118 defer r.Close() 1119 1120 r2, err := b.DataAsync() 1121 require.NoError(t, err) 1122 defer r2.Close() 1123 }) 1124}