loading up the forgejo repo on tangled to test page performance
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}