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