gitea源码

pull.go 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package issue
  4. import (
  5. "context"
  6. "fmt"
  7. "slices"
  8. "time"
  9. issues_model "code.gitea.io/gitea/models/issues"
  10. org_model "code.gitea.io/gitea/models/organization"
  11. repo_model "code.gitea.io/gitea/models/repo"
  12. user_model "code.gitea.io/gitea/models/user"
  13. "code.gitea.io/gitea/modules/git"
  14. "code.gitea.io/gitea/modules/gitrepo"
  15. "code.gitea.io/gitea/modules/graceful"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/setting"
  18. )
  19. func getMergeBase(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, pr *issues_model.PullRequest, baseBranch, headBranch string) (string, error) {
  20. // Add a temporary remote
  21. tmpRemote := fmt.Sprintf("mergebase-%d-%d", pr.ID, time.Now().UnixNano())
  22. if err := gitrepo.GitRemoteAdd(ctx, repo, tmpRemote, gitRepo.Path); err != nil {
  23. return "", fmt.Errorf("GitRemoteAdd: %w", err)
  24. }
  25. defer func() {
  26. if err := gitrepo.GitRemoteRemove(graceful.GetManager().ShutdownContext(), repo, tmpRemote); err != nil {
  27. log.Error("getMergeBase: GitRemoteRemove: %v", err)
  28. }
  29. }()
  30. mergeBase, _, err := gitRepo.GetMergeBase(tmpRemote, baseBranch, headBranch)
  31. return mergeBase, err
  32. }
  33. type ReviewRequestNotifier struct {
  34. Comment *issues_model.Comment
  35. IsAdd bool
  36. Reviewer *user_model.User
  37. ReviewTeam *org_model.Team
  38. }
  39. var codeOwnerFiles = []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}
  40. func IsCodeOwnerFile(f string) bool {
  41. return slices.Contains(codeOwnerFiles, f)
  42. }
  43. func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
  44. if err := pr.LoadIssue(ctx); err != nil {
  45. return nil, err
  46. }
  47. issue := pr.Issue
  48. if pr.IsWorkInProgress(ctx) {
  49. return nil, nil
  50. }
  51. if err := pr.LoadHeadRepo(ctx); err != nil {
  52. return nil, err
  53. }
  54. if err := pr.LoadBaseRepo(ctx); err != nil {
  55. return nil, err
  56. }
  57. pr.Issue.Repo = pr.BaseRepo
  58. if pr.BaseRepo.IsFork {
  59. return nil, nil
  60. }
  61. repo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
  62. if err != nil {
  63. return nil, err
  64. }
  65. defer repo.Close()
  66. commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
  67. if err != nil {
  68. return nil, err
  69. }
  70. var data string
  71. for _, file := range codeOwnerFiles {
  72. if blob, err := commit.GetBlobByPath(file); err == nil {
  73. data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
  74. if err == nil {
  75. break
  76. }
  77. }
  78. }
  79. if data == "" {
  80. return nil, nil
  81. }
  82. rules, _ := issues_model.GetCodeOwnersFromContent(ctx, data)
  83. if len(rules) == 0 {
  84. return nil, nil
  85. }
  86. // get the mergebase
  87. mergeBase, err := getMergeBase(ctx, pr.BaseRepo, repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName())
  88. if err != nil {
  89. return nil, err
  90. }
  91. // https://github.com/go-gitea/gitea/issues/29763, we need to get the files changed
  92. // between the merge base and the head commit but not the base branch and the head commit
  93. changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.GetGitHeadRefName())
  94. if err != nil {
  95. return nil, err
  96. }
  97. uniqUsers := make(map[int64]*user_model.User)
  98. uniqTeams := make(map[string]*org_model.Team)
  99. for _, rule := range rules {
  100. for _, f := range changedFiles {
  101. if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) {
  102. for _, u := range rule.Users {
  103. uniqUsers[u.ID] = u
  104. }
  105. for _, t := range rule.Teams {
  106. uniqTeams[fmt.Sprintf("%d/%d", t.OrgID, t.ID)] = t
  107. }
  108. }
  109. }
  110. }
  111. notifiers := make([]*ReviewRequestNotifier, 0, len(uniqUsers)+len(uniqTeams))
  112. if err := issue.LoadPoster(ctx); err != nil {
  113. return nil, err
  114. }
  115. // load all reviews from database
  116. latestReivews, _, err := issues_model.GetReviewsByIssueID(ctx, pr.IssueID)
  117. if err != nil {
  118. return nil, err
  119. }
  120. contain := func(list issues_model.ReviewList, u *user_model.User) bool {
  121. for _, review := range list {
  122. if review.ReviewerTeamID == 0 && review.ReviewerID == u.ID {
  123. return true
  124. }
  125. }
  126. return false
  127. }
  128. for _, u := range uniqUsers {
  129. if u.ID != issue.Poster.ID && !contain(latestReivews, u) {
  130. comment, err := issues_model.AddReviewRequest(ctx, issue, u, issue.Poster)
  131. if err != nil {
  132. log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
  133. return nil, err
  134. }
  135. if comment == nil { // comment maybe nil if review type is ReviewTypeRequest
  136. continue
  137. }
  138. notifiers = append(notifiers, &ReviewRequestNotifier{
  139. Comment: comment,
  140. IsAdd: true,
  141. Reviewer: u,
  142. })
  143. }
  144. }
  145. for _, t := range uniqTeams {
  146. comment, err := issues_model.AddTeamReviewRequest(ctx, issue, t, issue.Poster)
  147. if err != nil {
  148. log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
  149. return nil, err
  150. }
  151. if comment == nil { // comment maybe nil if review type is ReviewTypeRequest
  152. continue
  153. }
  154. notifiers = append(notifiers, &ReviewRequestNotifier{
  155. Comment: comment,
  156. IsAdd: true,
  157. ReviewTeam: t,
  158. })
  159. }
  160. return notifiers, nil
  161. }