gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "bytes"
  6. "io"
  7. "net/url"
  8. "sync"
  9. "testing"
  10. auth_model "code.gitea.io/gitea/models/auth"
  11. issues_model "code.gitea.io/gitea/models/issues"
  12. repo_model "code.gitea.io/gitea/models/repo"
  13. "code.gitea.io/gitea/models/unittest"
  14. user_model "code.gitea.io/gitea/models/user"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/git/gitcmd"
  17. "code.gitea.io/gitea/modules/gitrepo"
  18. files_service "code.gitea.io/gitea/services/repository/files"
  19. "github.com/stretchr/testify/assert"
  20. )
  21. func TestDataAsyncDoubleRead_Issue29101(t *testing.T) {
  22. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  23. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  24. repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  25. testContent := bytes.Repeat([]byte{'a'}, 10000)
  26. resp, err := files_service.ChangeRepoFiles(t.Context(), repo, user, &files_service.ChangeRepoFilesOptions{
  27. Files: []*files_service.ChangeRepoFile{
  28. {
  29. Operation: "create",
  30. TreePath: "test.txt",
  31. ContentReader: bytes.NewReader(testContent),
  32. },
  33. },
  34. OldBranch: repo.DefaultBranch,
  35. NewBranch: repo.DefaultBranch,
  36. })
  37. assert.NoError(t, err)
  38. sha := resp.Commit.SHA
  39. gitRepo, err := gitrepo.OpenRepository(t.Context(), repo)
  40. assert.NoError(t, err)
  41. commit, err := gitRepo.GetCommit(sha)
  42. assert.NoError(t, err)
  43. entry, err := commit.GetTreeEntryByPath("test.txt")
  44. assert.NoError(t, err)
  45. b := entry.Blob()
  46. r1, err := b.DataAsync()
  47. assert.NoError(t, err)
  48. defer r1.Close()
  49. r2, err := b.DataAsync()
  50. assert.NoError(t, err)
  51. defer r2.Close()
  52. var data1, data2 []byte
  53. wg := sync.WaitGroup{}
  54. wg.Add(2)
  55. go func() {
  56. data1, _ = io.ReadAll(r1)
  57. assert.NoError(t, err)
  58. wg.Done()
  59. }()
  60. go func() {
  61. data2, _ = io.ReadAll(r2)
  62. assert.NoError(t, err)
  63. wg.Done()
  64. }()
  65. wg.Wait()
  66. assert.Equal(t, testContent, data1)
  67. assert.Equal(t, testContent, data2)
  68. })
  69. }
  70. func TestAgitPullPush(t *testing.T) {
  71. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  72. baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  73. u.Path = baseAPITestContext.GitPath()
  74. u.User = url.UserPassword("user2", userPassword)
  75. dstPath := t.TempDir()
  76. doGitClone(dstPath, u)(t)
  77. gitRepo, err := git.OpenRepository(t.Context(), dstPath)
  78. assert.NoError(t, err)
  79. defer gitRepo.Close()
  80. doGitCreateBranch(dstPath, "test-agit-push")
  81. // commit 1
  82. _, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-")
  83. assert.NoError(t, err)
  84. // push to create an agit pull request
  85. err = gitcmd.NewCommand("push", "origin",
  86. "-o", "title=test-title", "-o", "description=test-description",
  87. "HEAD:refs/for/master/test-agit-push",
  88. ).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  89. assert.NoError(t, err)
  90. // check pull request exist
  91. pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: 1, Flow: issues_model.PullRequestFlowAGit, HeadBranch: "user2/test-agit-push"})
  92. assert.NoError(t, pr.LoadIssue(t.Context()))
  93. assert.Equal(t, "test-title", pr.Issue.Title)
  94. assert.Equal(t, "test-description", pr.Issue.Content)
  95. // commit 2
  96. _, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-2-")
  97. assert.NoError(t, err)
  98. // push 2
  99. err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  100. assert.NoError(t, err)
  101. // reset to first commit
  102. err = gitcmd.NewCommand("reset", "--hard", "HEAD~1").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  103. assert.NoError(t, err)
  104. // test force push without confirm
  105. _, stderr, err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  106. assert.Error(t, err)
  107. assert.Contains(t, stderr, "[remote rejected] HEAD -> refs/for/master/test-agit-push (request `force-push` push option)")
  108. // test force push with confirm
  109. err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  110. assert.NoError(t, err)
  111. })
  112. }
  113. func TestAgitReviewStaleness(t *testing.T) {
  114. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  115. baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  116. u.Path = baseAPITestContext.GitPath()
  117. u.User = url.UserPassword("user2", userPassword)
  118. dstPath := t.TempDir()
  119. doGitClone(dstPath, u)(t)
  120. gitRepo, err := git.OpenRepository(t.Context(), dstPath)
  121. assert.NoError(t, err)
  122. defer gitRepo.Close()
  123. doGitCreateBranch(dstPath, "test-agit-review")
  124. // Create initial commit
  125. _, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "initial-")
  126. assert.NoError(t, err)
  127. // create PR via agit
  128. err = gitcmd.NewCommand("push", "origin",
  129. "-o", "title=Test agit Review Staleness", "-o", "description=Testing review staleness",
  130. "HEAD:refs/for/master/test-agit-review",
  131. ).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  132. assert.NoError(t, err)
  133. pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
  134. BaseRepoID: 1,
  135. Flow: issues_model.PullRequestFlowAGit,
  136. HeadBranch: "user2/test-agit-review",
  137. })
  138. assert.NoError(t, pr.LoadIssue(t.Context()))
  139. // Get initial commit ID for the review
  140. initialCommitID := pr.HeadCommitID
  141. t.Logf("Initial commit ID: %s", initialCommitID)
  142. // Create a review on the PR (as user1 reviewing user2's PR)
  143. reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
  144. review, err := issues_model.CreateReview(t.Context(), issues_model.CreateReviewOptions{
  145. Type: issues_model.ReviewTypeApprove,
  146. Reviewer: reviewer,
  147. Issue: pr.Issue,
  148. CommitID: initialCommitID,
  149. Content: "LGTM! Looks good to merge.",
  150. Official: false,
  151. })
  152. assert.NoError(t, err)
  153. assert.False(t, review.Stale, "New review should not be stale")
  154. // Verify review exists and is not stale
  155. reviews, err := issues_model.FindReviews(t.Context(), issues_model.FindReviewOptions{
  156. IssueID: pr.IssueID,
  157. })
  158. assert.NoError(t, err)
  159. assert.Len(t, reviews, 1)
  160. assert.Equal(t, initialCommitID, reviews[0].CommitID)
  161. assert.False(t, reviews[0].Stale, "Review should not be stale initially")
  162. // Create a new commit and update the agit PR
  163. _, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "updated-")
  164. assert.NoError(t, err)
  165. err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-review").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  166. assert.NoError(t, err)
  167. // Reload PR to get updated commit ID
  168. pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
  169. BaseRepoID: 1,
  170. Flow: issues_model.PullRequestFlowAGit,
  171. HeadBranch: "user2/test-agit-review",
  172. })
  173. assert.NoError(t, pr.LoadIssue(t.Context()))
  174. // For AGit PRs, HeadCommitID must be loaded from git references
  175. baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  176. baseGitRepo, err := gitrepo.OpenRepository(t.Context(), baseRepo)
  177. assert.NoError(t, err)
  178. defer baseGitRepo.Close()
  179. updatedCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitHeadRefName())
  180. assert.NoError(t, err)
  181. t.Logf("Updated commit ID: %s", updatedCommitID)
  182. // Verify the PR was updated with new commit
  183. assert.NotEqual(t, initialCommitID, updatedCommitID, "PR should have new commit ID after update")
  184. // Check that the review is now marked as stale
  185. reviews, err = issues_model.FindReviews(t.Context(), issues_model.FindReviewOptions{
  186. IssueID: pr.IssueID,
  187. })
  188. assert.NoError(t, err)
  189. assert.Len(t, reviews, 1)
  190. assert.True(t, reviews[0].Stale, "Review should be marked as stale after AGit PR update")
  191. // The review commit ID should remain the same (pointing to the original commit)
  192. assert.Equal(t, initialCommitID, reviews[0].CommitID, "Review commit ID should remain unchanged and point to original commit")
  193. })
  194. }