summaryrefslogtreecommitdiffstats
path: root/models/issues/comment_code.go
diff options
context:
space:
mode:
Diffstat (limited to 'models/issues/comment_code.go')
-rw-r--r--models/issues/comment_code.go181
1 files changed, 181 insertions, 0 deletions
diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go
new file mode 100644
index 00000000..2f6f57e0
--- /dev/null
+++ b/models/issues/comment_code.go
@@ -0,0 +1,181 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package issues
+
+import (
+ "context"
+
+ "code.gitea.io/gitea/models/db"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/markup/markdown"
+
+ "xorm.io/builder"
+)
+
+// CodeConversation contains the comment of a given review
+type CodeConversation []*Comment
+
+// CodeConversationsAtLine contains the conversations for a given line
+type CodeConversationsAtLine map[int64][]CodeConversation
+
+// CodeConversationsAtLineAndTreePath contains the conversations for a given TreePath and line
+type CodeConversationsAtLineAndTreePath map[string]CodeConversationsAtLine
+
+func newCodeConversationsAtLineAndTreePath(comments []*Comment) CodeConversationsAtLineAndTreePath {
+ tree := make(CodeConversationsAtLineAndTreePath)
+ for _, comment := range comments {
+ tree.insertComment(comment)
+ }
+ return tree
+}
+
+func (tree CodeConversationsAtLineAndTreePath) insertComment(comment *Comment) {
+ // attempt to append comment to existing conversations (i.e. list of comments belonging to the same review)
+ for i, conversation := range tree[comment.TreePath][comment.Line] {
+ if conversation[0].ReviewID == comment.ReviewID {
+ tree[comment.TreePath][comment.Line][i] = append(conversation, comment)
+ return
+ }
+ }
+
+ // no previous conversation was found at this line, create it
+ if tree[comment.TreePath] == nil {
+ tree[comment.TreePath] = make(map[int64][]CodeConversation)
+ }
+
+ tree[comment.TreePath][comment.Line] = append(tree[comment.TreePath][comment.Line], CodeConversation{comment})
+}
+
+// FetchCodeConversations will return a 2d-map: ["Path"]["Line"] = List of CodeConversation (one per review) for this line
+func FetchCodeConversations(ctx context.Context, issue *Issue, doer *user_model.User, showOutdatedComments bool) (CodeConversationsAtLineAndTreePath, error) {
+ opts := FindCommentsOptions{
+ Type: CommentTypeCode,
+ IssueID: issue.ID,
+ }
+ comments, err := findCodeComments(ctx, opts, issue, doer, nil, showOutdatedComments)
+ if err != nil {
+ return nil, err
+ }
+
+ return newCodeConversationsAtLineAndTreePath(comments), nil
+}
+
+// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
+type CodeComments map[string]map[int64][]*Comment
+
+func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, doer *user_model.User, review *Review, showOutdatedComments bool) (CodeComments, error) {
+ pathToLineToComment := make(CodeComments)
+ if review == nil {
+ review = &Review{ID: 0}
+ }
+ opts := FindCommentsOptions{
+ Type: CommentTypeCode,
+ IssueID: issue.ID,
+ ReviewID: review.ID,
+ }
+
+ comments, err := findCodeComments(ctx, opts, issue, doer, review, showOutdatedComments)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, comment := range comments {
+ if pathToLineToComment[comment.TreePath] == nil {
+ pathToLineToComment[comment.TreePath] = make(map[int64][]*Comment)
+ }
+ pathToLineToComment[comment.TreePath][comment.Line] = append(pathToLineToComment[comment.TreePath][comment.Line], comment)
+ }
+ return pathToLineToComment, nil
+}
+
+func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, doer *user_model.User, review *Review, showOutdatedComments bool) (CommentList, error) {
+ var comments CommentList
+ if review == nil {
+ review = &Review{ID: 0}
+ }
+ conds := opts.ToConds()
+
+ if !showOutdatedComments && review.ID == 0 {
+ conds = conds.And(builder.Eq{"invalidated": false})
+ }
+
+ e := db.GetEngine(ctx)
+ if err := e.Where(conds).
+ Asc("comment.created_unix").
+ Asc("comment.id").
+ Find(&comments); err != nil {
+ return nil, err
+ }
+
+ if err := issue.LoadRepo(ctx); err != nil {
+ return nil, err
+ }
+
+ if err := comments.LoadPosters(ctx); err != nil {
+ return nil, err
+ }
+
+ if err := comments.LoadAttachments(ctx); err != nil {
+ return nil, err
+ }
+
+ // Find all reviews by ReviewID
+ reviews := make(map[int64]*Review)
+ ids := make([]int64, 0, len(comments))
+ for _, comment := range comments {
+ if comment.ReviewID != 0 {
+ ids = append(ids, comment.ReviewID)
+ }
+ }
+ if err := e.In("id", ids).Find(&reviews); err != nil {
+ return nil, err
+ }
+
+ n := 0
+ for _, comment := range comments {
+ if re, ok := reviews[comment.ReviewID]; ok && re != nil {
+ // If the review is pending only the author can see the comments (except if the review is set)
+ if review.ID == 0 && re.Type == ReviewTypePending &&
+ (doer == nil || doer.ID != re.ReviewerID) {
+ continue
+ }
+ comment.Review = re
+ }
+ comments[n] = comment
+ n++
+
+ if err := comment.LoadResolveDoer(ctx); err != nil {
+ return nil, err
+ }
+
+ if err := comment.LoadReactions(ctx, issue.Repo); err != nil {
+ return nil, err
+ }
+
+ var err error
+ if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: issue.Repo.Link(),
+ },
+ Metas: issue.Repo.ComposeMetas(ctx),
+ }, comment.Content); err != nil {
+ return nil, err
+ }
+ }
+ return comments[:n], nil
+}
+
+// FetchCodeConversation fetches the code conversation of a given comment (same review, treePath and line number)
+func FetchCodeConversation(ctx context.Context, comment *Comment, doer *user_model.User) (CommentList, error) {
+ opts := FindCommentsOptions{
+ Type: CommentTypeCode,
+ IssueID: comment.IssueID,
+ ReviewID: comment.ReviewID,
+ TreePath: comment.TreePath,
+ Line: comment.Line,
+ }
+ return findCodeComments(ctx, opts, comment.Issue, doer, nil, true)
+}