gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package issues
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "code.gitea.io/gitea/models/db"
  9. issue_model "code.gitea.io/gitea/models/issues"
  10. "code.gitea.io/gitea/modules/container"
  11. "code.gitea.io/gitea/modules/indexer/issues/internal"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/queue"
  14. )
  15. // getIssueIndexerData returns the indexer data of an issue and a bool value indicating whether the issue exists.
  16. func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerData, bool, error) {
  17. issue, err := issue_model.GetIssueByID(ctx, issueID)
  18. if err != nil {
  19. if issue_model.IsErrIssueNotExist(err) {
  20. return nil, false, nil
  21. }
  22. return nil, false, err
  23. }
  24. // FIXME: what if users want to search for a review comment of a pull request?
  25. // The comment type is CommentTypeCode or CommentTypeReview.
  26. // But LoadDiscussComments only loads CommentTypeComment.
  27. if err := issue.LoadDiscussComments(ctx); err != nil {
  28. return nil, false, err
  29. }
  30. comments := make([]string, 0, len(issue.Comments))
  31. for _, comment := range issue.Comments {
  32. if comment.Content != "" {
  33. // what ever the comment type is, index the content if it is not empty.
  34. comments = append(comments, comment.Content)
  35. }
  36. }
  37. if err := issue.LoadAttributes(ctx); err != nil {
  38. return nil, false, err
  39. }
  40. labels := make([]int64, 0, len(issue.Labels))
  41. for _, label := range issue.Labels {
  42. labels = append(labels, label.ID)
  43. }
  44. mentionIDs, err := issue_model.GetIssueMentionIDs(ctx, issueID)
  45. if err != nil {
  46. return nil, false, err
  47. }
  48. var (
  49. reviewedIDs []int64
  50. reviewRequestedIDs []int64
  51. )
  52. {
  53. reviews, err := issue_model.FindReviews(ctx, issue_model.FindReviewOptions{
  54. ListOptions: db.ListOptionsAll,
  55. IssueID: issueID,
  56. OfficialOnly: false,
  57. })
  58. if err != nil {
  59. return nil, false, err
  60. }
  61. reviewedIDsSet := make(container.Set[int64], len(reviews))
  62. reviewRequestedIDsSet := make(container.Set[int64], len(reviews))
  63. for _, review := range reviews {
  64. if review.Type == issue_model.ReviewTypeRequest {
  65. reviewRequestedIDsSet.Add(review.ReviewerID)
  66. } else {
  67. reviewedIDsSet.Add(review.ReviewerID)
  68. }
  69. }
  70. reviewedIDs = reviewedIDsSet.Values()
  71. reviewRequestedIDs = reviewRequestedIDsSet.Values()
  72. }
  73. subscriberIDs, err := issue_model.GetIssueWatchersIDs(ctx, issue.ID, true)
  74. if err != nil {
  75. return nil, false, err
  76. }
  77. var projectID int64
  78. if issue.Project != nil {
  79. projectID = issue.Project.ID
  80. }
  81. projectColumnID, err := issue.ProjectColumnID(ctx)
  82. if err != nil {
  83. return nil, false, err
  84. }
  85. if err := issue.Repo.LoadOwner(ctx); err != nil {
  86. return nil, false, fmt.Errorf("issue.Repo.LoadOwner: %w", err)
  87. }
  88. return &internal.IndexerData{
  89. ID: issue.ID,
  90. RepoID: issue.RepoID,
  91. IsPublic: !issue.Repo.IsPrivate && issue.Repo.Owner.Visibility.IsPublic(),
  92. Title: issue.Title,
  93. Content: issue.Content,
  94. Comments: comments,
  95. IsPull: issue.IsPull,
  96. IsClosed: issue.IsClosed,
  97. IsArchived: issue.Repo.IsArchived,
  98. LabelIDs: labels,
  99. NoLabel: len(labels) == 0,
  100. MilestoneID: issue.MilestoneID,
  101. ProjectID: projectID,
  102. ProjectColumnID: projectColumnID,
  103. PosterID: issue.PosterID,
  104. AssigneeID: issue.AssigneeID,
  105. MentionIDs: mentionIDs,
  106. ReviewedIDs: reviewedIDs,
  107. ReviewRequestedIDs: reviewRequestedIDs,
  108. SubscriberIDs: subscriberIDs,
  109. UpdatedUnix: issue.UpdatedUnix,
  110. CreatedUnix: issue.CreatedUnix,
  111. DeadlineUnix: issue.DeadlineUnix,
  112. CommentCount: int64(len(issue.Comments)),
  113. }, true, nil
  114. }
  115. func updateRepoIndexer(ctx context.Context, repoID int64) error {
  116. ids, err := issue_model.GetIssueIDsByRepoID(ctx, repoID)
  117. if err != nil {
  118. return fmt.Errorf("issue_model.GetIssueIDsByRepoID: %w", err)
  119. }
  120. for _, id := range ids {
  121. if err := updateIssueIndexer(ctx, id); err != nil {
  122. return err
  123. }
  124. }
  125. return nil
  126. }
  127. func updateIssueIndexer(ctx context.Context, issueID int64) error {
  128. return pushIssueIndexerQueue(ctx, &IndexerMetadata{ID: issueID})
  129. }
  130. func deleteRepoIssueIndexer(ctx context.Context, repoID int64) error {
  131. var ids []int64
  132. ids, err := issue_model.GetIssueIDsByRepoID(ctx, repoID)
  133. if err != nil {
  134. return fmt.Errorf("issue_model.GetIssueIDsByRepoID: %w", err)
  135. }
  136. if len(ids) == 0 {
  137. return nil
  138. }
  139. return pushIssueIndexerQueue(ctx, &IndexerMetadata{
  140. IDs: ids,
  141. IsDelete: true,
  142. })
  143. }
  144. type keepRetryKey struct{}
  145. // contextWithKeepRetry returns a context with a key indicating that the indexer should keep retrying.
  146. // Please note that it's for background tasks only, and it should not be used for user requests, or it may cause blocking.
  147. func contextWithKeepRetry(ctx context.Context) context.Context {
  148. return context.WithValue(ctx, keepRetryKey{}, true)
  149. }
  150. func pushIssueIndexerQueue(ctx context.Context, data *IndexerMetadata) error {
  151. if issueIndexerQueue == nil {
  152. // Some unit tests will trigger indexing, but the queue is not initialized.
  153. // It's OK to ignore it, but log a warning message in case it's not a unit test.
  154. log.Warn("Trying to push %+v to issue indexer queue, but the queue is not initialized, it's OK if it's a unit test", data)
  155. return nil
  156. }
  157. for {
  158. select {
  159. case <-ctx.Done():
  160. return ctx.Err()
  161. default:
  162. }
  163. err := issueIndexerQueue.Push(data)
  164. if errors.Is(err, queue.ErrAlreadyInQueue) {
  165. return nil
  166. }
  167. if errors.Is(err, context.DeadlineExceeded) { // the queue is full
  168. log.Warn("It seems that issue indexer is slow and the queue is full. Please check the issue indexer or increase the queue size.")
  169. if ctx.Value(keepRetryKey{}) == nil {
  170. return err
  171. }
  172. // It will be better to increase the queue size instead of retrying, but users may ignore the previous warning message.
  173. // However, even it retries, it may still cause index loss when there's a deadline in the context.
  174. log.Debug("Retry to push %+v to issue indexer queue", data)
  175. continue
  176. }
  177. return err
  178. }
  179. }