gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package issues
  4. import (
  5. "context"
  6. "code.gitea.io/gitea/models/db"
  7. repo_model "code.gitea.io/gitea/models/repo"
  8. user_model "code.gitea.io/gitea/models/user"
  9. "code.gitea.io/gitea/modules/container"
  10. "code.gitea.io/gitea/modules/log"
  11. )
  12. // CommentList defines a list of comments
  13. type CommentList []*Comment
  14. // LoadPosters loads posters
  15. func (comments CommentList) LoadPosters(ctx context.Context) error {
  16. if len(comments) == 0 {
  17. return nil
  18. }
  19. posterIDs := container.FilterSlice(comments, func(c *Comment) (int64, bool) {
  20. return c.PosterID, c.Poster == nil && c.PosterID > 0
  21. })
  22. posterMaps, err := user_model.GetUsersMapByIDs(ctx, posterIDs)
  23. if err != nil {
  24. return err
  25. }
  26. for _, comment := range comments {
  27. if comment.Poster == nil {
  28. comment.Poster = user_model.GetPossibleUserFromMap(comment.PosterID, posterMaps)
  29. }
  30. }
  31. return nil
  32. }
  33. func (comments CommentList) getLabelIDs() []int64 {
  34. return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
  35. return comment.LabelID, comment.LabelID > 0 && comment.Label == nil
  36. })
  37. }
  38. func (comments CommentList) loadLabels(ctx context.Context) error {
  39. if len(comments) == 0 {
  40. return nil
  41. }
  42. labelIDs := comments.getLabelIDs()
  43. if len(labelIDs) == 0 {
  44. return nil
  45. }
  46. commentLabels := make(map[int64]*Label, len(labelIDs))
  47. left := len(labelIDs)
  48. for left > 0 {
  49. limit := min(left, db.DefaultMaxInSize)
  50. rows, err := db.GetEngine(ctx).
  51. In("id", labelIDs[:limit]).
  52. Rows(new(Label))
  53. if err != nil {
  54. return err
  55. }
  56. for rows.Next() {
  57. var label Label
  58. err = rows.Scan(&label)
  59. if err != nil {
  60. _ = rows.Close()
  61. return err
  62. }
  63. commentLabels[label.ID] = &label
  64. }
  65. _ = rows.Close()
  66. left -= limit
  67. labelIDs = labelIDs[limit:]
  68. }
  69. for _, comment := range comments {
  70. comment.Label = commentLabels[comment.ID]
  71. }
  72. return nil
  73. }
  74. func (comments CommentList) getMilestoneIDs() []int64 {
  75. return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
  76. return comment.MilestoneID, comment.MilestoneID > 0
  77. })
  78. }
  79. func (comments CommentList) loadMilestones(ctx context.Context) error {
  80. if len(comments) == 0 {
  81. return nil
  82. }
  83. milestoneIDs := comments.getMilestoneIDs()
  84. if len(milestoneIDs) == 0 {
  85. return nil
  86. }
  87. milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
  88. left := len(milestoneIDs)
  89. for left > 0 {
  90. limit := min(left, db.DefaultMaxInSize)
  91. err := db.GetEngine(ctx).
  92. In("id", milestoneIDs[:limit]).
  93. Find(&milestoneMaps)
  94. if err != nil {
  95. return err
  96. }
  97. left -= limit
  98. milestoneIDs = milestoneIDs[limit:]
  99. }
  100. for _, comment := range comments {
  101. comment.Milestone = milestoneMaps[comment.MilestoneID]
  102. }
  103. return nil
  104. }
  105. func (comments CommentList) getOldMilestoneIDs() []int64 {
  106. return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
  107. return comment.OldMilestoneID, comment.OldMilestoneID > 0
  108. })
  109. }
  110. func (comments CommentList) loadOldMilestones(ctx context.Context) error {
  111. if len(comments) == 0 {
  112. return nil
  113. }
  114. milestoneIDs := comments.getOldMilestoneIDs()
  115. if len(milestoneIDs) == 0 {
  116. return nil
  117. }
  118. milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
  119. left := len(milestoneIDs)
  120. for left > 0 {
  121. limit := min(left, db.DefaultMaxInSize)
  122. err := db.GetEngine(ctx).
  123. In("id", milestoneIDs[:limit]).
  124. Find(&milestoneMaps)
  125. if err != nil {
  126. return err
  127. }
  128. left -= limit
  129. milestoneIDs = milestoneIDs[limit:]
  130. }
  131. for _, issue := range comments {
  132. issue.OldMilestone = milestoneMaps[issue.MilestoneID]
  133. }
  134. return nil
  135. }
  136. func (comments CommentList) getAssigneeIDs() []int64 {
  137. return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
  138. return comment.AssigneeID, comment.AssigneeID > 0
  139. })
  140. }
  141. func (comments CommentList) loadAssignees(ctx context.Context) error {
  142. if len(comments) == 0 {
  143. return nil
  144. }
  145. assigneeIDs := comments.getAssigneeIDs()
  146. if len(assigneeIDs) == 0 {
  147. return nil
  148. }
  149. assignees := make(map[int64]*user_model.User, len(assigneeIDs))
  150. left := len(assigneeIDs)
  151. for left > 0 {
  152. limit := min(left, db.DefaultMaxInSize)
  153. rows, err := db.GetEngine(ctx).
  154. In("id", assigneeIDs[:limit]).
  155. Rows(new(user_model.User))
  156. if err != nil {
  157. return err
  158. }
  159. for rows.Next() {
  160. var user user_model.User
  161. err = rows.Scan(&user)
  162. if err != nil {
  163. rows.Close()
  164. return err
  165. }
  166. assignees[user.ID] = &user
  167. }
  168. _ = rows.Close()
  169. left -= limit
  170. assigneeIDs = assigneeIDs[limit:]
  171. }
  172. for _, comment := range comments {
  173. comment.Assignee = assignees[comment.AssigneeID]
  174. if comment.Assignee == nil {
  175. comment.AssigneeID = user_model.GhostUserID
  176. comment.Assignee = user_model.NewGhostUser()
  177. }
  178. }
  179. return nil
  180. }
  181. // getIssueIDs returns all the issue ids on this comment list which issue hasn't been loaded
  182. func (comments CommentList) getIssueIDs() []int64 {
  183. return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
  184. return comment.IssueID, comment.Issue == nil
  185. })
  186. }
  187. // Issues returns all the issues of comments
  188. func (comments CommentList) Issues() IssueList {
  189. issues := make(map[int64]*Issue, len(comments))
  190. for _, comment := range comments {
  191. if comment.Issue != nil {
  192. if _, ok := issues[comment.Issue.ID]; !ok {
  193. issues[comment.Issue.ID] = comment.Issue
  194. }
  195. }
  196. }
  197. issueList := make([]*Issue, 0, len(issues))
  198. for _, issue := range issues {
  199. issueList = append(issueList, issue)
  200. }
  201. return issueList
  202. }
  203. // LoadIssues loads issues of comments
  204. func (comments CommentList) LoadIssues(ctx context.Context) error {
  205. if len(comments) == 0 {
  206. return nil
  207. }
  208. issueIDs := comments.getIssueIDs()
  209. issues := make(map[int64]*Issue, len(issueIDs))
  210. left := len(issueIDs)
  211. for left > 0 {
  212. limit := min(left, db.DefaultMaxInSize)
  213. rows, err := db.GetEngine(ctx).
  214. In("id", issueIDs[:limit]).
  215. Rows(new(Issue))
  216. if err != nil {
  217. return err
  218. }
  219. for rows.Next() {
  220. var issue Issue
  221. err = rows.Scan(&issue)
  222. if err != nil {
  223. rows.Close()
  224. return err
  225. }
  226. issues[issue.ID] = &issue
  227. }
  228. _ = rows.Close()
  229. left -= limit
  230. issueIDs = issueIDs[limit:]
  231. }
  232. for _, comment := range comments {
  233. if comment.Issue == nil {
  234. comment.Issue = issues[comment.IssueID]
  235. }
  236. }
  237. return nil
  238. }
  239. func (comments CommentList) getDependentIssueIDs() []int64 {
  240. return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
  241. if comment.DependentIssue != nil {
  242. return 0, false
  243. }
  244. return comment.DependentIssueID, comment.DependentIssueID > 0
  245. })
  246. }
  247. func (comments CommentList) loadDependentIssues(ctx context.Context) error {
  248. if len(comments) == 0 {
  249. return nil
  250. }
  251. e := db.GetEngine(ctx)
  252. issueIDs := comments.getDependentIssueIDs()
  253. if len(issueIDs) == 0 {
  254. return nil
  255. }
  256. issues := make(map[int64]*Issue, len(issueIDs))
  257. left := len(issueIDs)
  258. for left > 0 {
  259. limit := min(left, db.DefaultMaxInSize)
  260. rows, err := e.
  261. In("id", issueIDs[:limit]).
  262. Rows(new(Issue))
  263. if err != nil {
  264. return err
  265. }
  266. for rows.Next() {
  267. var issue Issue
  268. err = rows.Scan(&issue)
  269. if err != nil {
  270. _ = rows.Close()
  271. return err
  272. }
  273. issues[issue.ID] = &issue
  274. }
  275. _ = rows.Close()
  276. left -= limit
  277. issueIDs = issueIDs[limit:]
  278. }
  279. for _, comment := range comments {
  280. if comment.DependentIssue == nil {
  281. comment.DependentIssue = issues[comment.DependentIssueID]
  282. if comment.DependentIssue != nil {
  283. if err := comment.DependentIssue.LoadRepo(ctx); err != nil {
  284. return err
  285. }
  286. }
  287. }
  288. }
  289. return nil
  290. }
  291. // getAttachmentCommentIDs only return the comment ids which possibly has attachments
  292. func (comments CommentList) getAttachmentCommentIDs() []int64 {
  293. return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
  294. return comment.ID, comment.Type.HasAttachmentSupport()
  295. })
  296. }
  297. // LoadAttachmentsByIssue loads attachments by issue id
  298. func (comments CommentList) LoadAttachmentsByIssue(ctx context.Context) error {
  299. if len(comments) == 0 {
  300. return nil
  301. }
  302. attachments := make([]*repo_model.Attachment, 0, len(comments)/2)
  303. if err := db.GetEngine(ctx).Where("issue_id=? AND comment_id>0", comments[0].IssueID).Find(&attachments); err != nil {
  304. return err
  305. }
  306. commentAttachmentsMap := make(map[int64][]*repo_model.Attachment, len(comments))
  307. for _, attach := range attachments {
  308. commentAttachmentsMap[attach.CommentID] = append(commentAttachmentsMap[attach.CommentID], attach)
  309. }
  310. for _, comment := range comments {
  311. comment.Attachments = commentAttachmentsMap[comment.ID]
  312. }
  313. return nil
  314. }
  315. // LoadAttachments loads attachments
  316. func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
  317. if len(comments) == 0 {
  318. return nil
  319. }
  320. attachments := make(map[int64][]*repo_model.Attachment, len(comments))
  321. commentsIDs := comments.getAttachmentCommentIDs()
  322. left := len(commentsIDs)
  323. for left > 0 {
  324. limit := min(left, db.DefaultMaxInSize)
  325. rows, err := db.GetEngine(ctx).
  326. In("comment_id", commentsIDs[:limit]).
  327. Rows(new(repo_model.Attachment))
  328. if err != nil {
  329. return err
  330. }
  331. for rows.Next() {
  332. var attachment repo_model.Attachment
  333. err = rows.Scan(&attachment)
  334. if err != nil {
  335. _ = rows.Close()
  336. return err
  337. }
  338. attachments[attachment.CommentID] = append(attachments[attachment.CommentID], &attachment)
  339. }
  340. _ = rows.Close()
  341. left -= limit
  342. commentsIDs = commentsIDs[limit:]
  343. }
  344. for _, comment := range comments {
  345. comment.Attachments = attachments[comment.ID]
  346. }
  347. return nil
  348. }
  349. func (comments CommentList) getReviewIDs() []int64 {
  350. return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
  351. return comment.ReviewID, comment.ReviewID > 0
  352. })
  353. }
  354. func (comments CommentList) loadReviews(ctx context.Context) error {
  355. if len(comments) == 0 {
  356. return nil
  357. }
  358. reviewIDs := comments.getReviewIDs()
  359. if len(reviewIDs) == 0 {
  360. return nil
  361. }
  362. reviews := make(map[int64]*Review, len(reviewIDs))
  363. if err := db.GetEngine(ctx).In("id", reviewIDs).Find(&reviews); err != nil {
  364. return err
  365. }
  366. for _, comment := range comments {
  367. comment.Review = reviews[comment.ReviewID]
  368. if comment.Review == nil {
  369. // review request which has been replaced by actual reviews doesn't exist in database anymore, so don't log errors for them.
  370. if comment.ReviewID > 0 && comment.Type != CommentTypeReviewRequest {
  371. log.Error("comment with review id [%d] but has no review record", comment.ReviewID)
  372. }
  373. continue
  374. }
  375. // If the comment dismisses a review, we need to load the reviewer to show whose review has been dismissed.
  376. // Otherwise, the reviewer is the poster of the comment, so we don't need to load it.
  377. if comment.Type == CommentTypeDismissReview {
  378. if err := comment.Review.LoadReviewer(ctx); err != nil {
  379. return err
  380. }
  381. }
  382. }
  383. return nil
  384. }
  385. // LoadAttributes loads attributes of the comments, except for attachments and
  386. // comments
  387. func (comments CommentList) LoadAttributes(ctx context.Context) (err error) {
  388. if err = comments.LoadPosters(ctx); err != nil {
  389. return err
  390. }
  391. if err = comments.loadLabels(ctx); err != nil {
  392. return err
  393. }
  394. if err = comments.loadMilestones(ctx); err != nil {
  395. return err
  396. }
  397. if err = comments.loadOldMilestones(ctx); err != nil {
  398. return err
  399. }
  400. if err = comments.loadAssignees(ctx); err != nil {
  401. return err
  402. }
  403. if err = comments.LoadAttachments(ctx); err != nil {
  404. return err
  405. }
  406. if err = comments.loadReviews(ctx); err != nil {
  407. return err
  408. }
  409. if err = comments.LoadIssues(ctx); err != nil {
  410. return err
  411. }
  412. return comments.loadDependentIssues(ctx)
  413. }