mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	some refactor about code comments (#20821)
This commit is contained in:
		@@ -4,8 +4,11 @@
 | 
				
			|||||||
package db
 | 
					package db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"xorm.io/builder"
 | 
				
			||||||
	"xorm.io/xorm"
 | 
						"xorm.io/xorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -18,6 +21,7 @@ const (
 | 
				
			|||||||
type Paginator interface {
 | 
					type Paginator interface {
 | 
				
			||||||
	GetSkipTake() (skip, take int)
 | 
						GetSkipTake() (skip, take int)
 | 
				
			||||||
	GetStartEnd() (start, end int)
 | 
						GetStartEnd() (start, end int)
 | 
				
			||||||
 | 
						IsListAll() bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetPaginatedSession creates a paginated database session
 | 
					// GetPaginatedSession creates a paginated database session
 | 
				
			||||||
@@ -44,9 +48,12 @@ func SetEnginePagination(e Engine, p Paginator) Engine {
 | 
				
			|||||||
// ListOptions options to paginate results
 | 
					// ListOptions options to paginate results
 | 
				
			||||||
type ListOptions struct {
 | 
					type ListOptions struct {
 | 
				
			||||||
	PageSize int
 | 
						PageSize int
 | 
				
			||||||
	Page     int // start from 1
 | 
						Page     int  // start from 1
 | 
				
			||||||
 | 
						ListAll  bool // if true, then PageSize and Page will not be taken
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ Paginator = &ListOptions{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetSkipTake returns the skip and take values
 | 
					// GetSkipTake returns the skip and take values
 | 
				
			||||||
func (opts *ListOptions) GetSkipTake() (skip, take int) {
 | 
					func (opts *ListOptions) GetSkipTake() (skip, take int) {
 | 
				
			||||||
	opts.SetDefaultValues()
 | 
						opts.SetDefaultValues()
 | 
				
			||||||
@@ -60,6 +67,11 @@ func (opts *ListOptions) GetStartEnd() (start, end int) {
 | 
				
			|||||||
	return start, end
 | 
						return start, end
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsListAll indicates PageSize and Page will be ignored
 | 
				
			||||||
 | 
					func (opts *ListOptions) IsListAll() bool {
 | 
				
			||||||
 | 
						return opts.ListAll
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SetDefaultValues sets default values
 | 
					// SetDefaultValues sets default values
 | 
				
			||||||
func (opts *ListOptions) SetDefaultValues() {
 | 
					func (opts *ListOptions) SetDefaultValues() {
 | 
				
			||||||
	if opts.PageSize <= 0 {
 | 
						if opts.PageSize <= 0 {
 | 
				
			||||||
@@ -79,6 +91,8 @@ type AbsoluteListOptions struct {
 | 
				
			|||||||
	take int
 | 
						take int
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ Paginator = &AbsoluteListOptions{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewAbsoluteListOptions creates a list option with applied limits
 | 
					// NewAbsoluteListOptions creates a list option with applied limits
 | 
				
			||||||
func NewAbsoluteListOptions(skip, take int) *AbsoluteListOptions {
 | 
					func NewAbsoluteListOptions(skip, take int) *AbsoluteListOptions {
 | 
				
			||||||
	if skip < 0 {
 | 
						if skip < 0 {
 | 
				
			||||||
@@ -93,6 +107,11 @@ func NewAbsoluteListOptions(skip, take int) *AbsoluteListOptions {
 | 
				
			|||||||
	return &AbsoluteListOptions{skip, take}
 | 
						return &AbsoluteListOptions{skip, take}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsListAll will always return false
 | 
				
			||||||
 | 
					func (opts *AbsoluteListOptions) IsListAll() bool {
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetSkipTake returns the skip and take values
 | 
					// GetSkipTake returns the skip and take values
 | 
				
			||||||
func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) {
 | 
					func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) {
 | 
				
			||||||
	return opts.skip, opts.take
 | 
						return opts.skip, opts.take
 | 
				
			||||||
@@ -102,3 +121,32 @@ func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) {
 | 
				
			|||||||
func (opts *AbsoluteListOptions) GetStartEnd() (start, end int) {
 | 
					func (opts *AbsoluteListOptions) GetStartEnd() (start, end int) {
 | 
				
			||||||
	return opts.skip, opts.skip + opts.take
 | 
						return opts.skip, opts.skip + opts.take
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FindOptions represents a find options
 | 
				
			||||||
 | 
					type FindOptions interface {
 | 
				
			||||||
 | 
						Paginator
 | 
				
			||||||
 | 
						ToConds() builder.Cond
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Find represents a common find function which accept an options interface
 | 
				
			||||||
 | 
					func Find[T any](ctx context.Context, opts FindOptions, objects *[]T) error {
 | 
				
			||||||
 | 
						sess := GetEngine(ctx).Where(opts.ToConds())
 | 
				
			||||||
 | 
						if !opts.IsListAll() {
 | 
				
			||||||
 | 
							sess.Limit(opts.GetSkipTake())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return sess.Find(&objects)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Count represents a common count function which accept an options interface
 | 
				
			||||||
 | 
					func Count[T any](ctx context.Context, opts FindOptions, object T) (int64, error) {
 | 
				
			||||||
 | 
						return GetEngine(ctx).Where(opts.ToConds()).Count(object)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FindAndCount represents a common findandcount function which accept an options interface
 | 
				
			||||||
 | 
					func FindAndCount[T any](ctx context.Context, opts FindOptions, objects *[]T) (int64, error) {
 | 
				
			||||||
 | 
						sess := GetEngine(ctx).Where(opts.ToConds())
 | 
				
			||||||
 | 
						if !opts.IsListAll() {
 | 
				
			||||||
 | 
							sess.Limit(opts.GetSkipTake())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return sess.FindAndCount(&objects)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,9 +8,7 @@ package issues
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"regexp"
 | 
					 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
	"unicode/utf8"
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
@@ -22,8 +20,6 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/json"
 | 
						"code.gitea.io/gitea/modules/json"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/markup"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/markup/markdown"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/references"
 | 
						"code.gitea.io/gitea/modules/references"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/structs"
 | 
						"code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
@@ -687,31 +683,6 @@ func (c *Comment) LoadReview() error {
 | 
				
			|||||||
	return c.loadReview(db.DefaultContext)
 | 
						return c.loadReview(db.DefaultContext)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var notEnoughLines = regexp.MustCompile(`fatal: file .* has only \d+ lines?`)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Comment) checkInvalidation(doer *user_model.User, repo *git.Repository, branch string) error {
 | 
					 | 
				
			||||||
	// FIXME differentiate between previous and proposed line
 | 
					 | 
				
			||||||
	commit, err := repo.LineBlame(branch, repo.Path, c.TreePath, uint(c.UnsignedLine()))
 | 
					 | 
				
			||||||
	if err != nil && (strings.Contains(err.Error(), "fatal: no such path") || notEnoughLines.MatchString(err.Error())) {
 | 
					 | 
				
			||||||
		c.Invalidated = true
 | 
					 | 
				
			||||||
		return UpdateComment(c, doer)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if c.CommitSHA != "" && c.CommitSHA != commit.ID.String() {
 | 
					 | 
				
			||||||
		c.Invalidated = true
 | 
					 | 
				
			||||||
		return UpdateComment(c, doer)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// CheckInvalidation checks if the line of code comment got changed by another commit.
 | 
					 | 
				
			||||||
// If the line got changed the comment is going to be invalidated.
 | 
					 | 
				
			||||||
func (c *Comment) CheckInvalidation(repo *git.Repository, doer *user_model.User, branch string) error {
 | 
					 | 
				
			||||||
	return c.checkInvalidation(doer, repo, branch)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
 | 
					// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
 | 
				
			||||||
func (c *Comment) DiffSide() string {
 | 
					func (c *Comment) DiffSide() string {
 | 
				
			||||||
	if c.Line < 0 {
 | 
						if c.Line < 0 {
 | 
				
			||||||
@@ -1008,23 +979,28 @@ func GetCommentByID(ctx context.Context, id int64) (*Comment, error) {
 | 
				
			|||||||
// FindCommentsOptions describes the conditions to Find comments
 | 
					// FindCommentsOptions describes the conditions to Find comments
 | 
				
			||||||
type FindCommentsOptions struct {
 | 
					type FindCommentsOptions struct {
 | 
				
			||||||
	db.ListOptions
 | 
						db.ListOptions
 | 
				
			||||||
	RepoID   int64
 | 
						RepoID      int64
 | 
				
			||||||
	IssueID  int64
 | 
						IssueID     int64
 | 
				
			||||||
	ReviewID int64
 | 
						ReviewID    int64
 | 
				
			||||||
	Since    int64
 | 
						Since       int64
 | 
				
			||||||
	Before   int64
 | 
						Before      int64
 | 
				
			||||||
	Line     int64
 | 
						Line        int64
 | 
				
			||||||
	TreePath string
 | 
						TreePath    string
 | 
				
			||||||
	Type     CommentType
 | 
						Type        CommentType
 | 
				
			||||||
 | 
						IssueIDs    []int64
 | 
				
			||||||
 | 
						Invalidated util.OptionalBool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (opts *FindCommentsOptions) toConds() builder.Cond {
 | 
					// ToConds implements FindOptions interface
 | 
				
			||||||
 | 
					func (opts *FindCommentsOptions) ToConds() builder.Cond {
 | 
				
			||||||
	cond := builder.NewCond()
 | 
						cond := builder.NewCond()
 | 
				
			||||||
	if opts.RepoID > 0 {
 | 
						if opts.RepoID > 0 {
 | 
				
			||||||
		cond = cond.And(builder.Eq{"issue.repo_id": opts.RepoID})
 | 
							cond = cond.And(builder.Eq{"issue.repo_id": opts.RepoID})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if opts.IssueID > 0 {
 | 
						if opts.IssueID > 0 {
 | 
				
			||||||
		cond = cond.And(builder.Eq{"comment.issue_id": opts.IssueID})
 | 
							cond = cond.And(builder.Eq{"comment.issue_id": opts.IssueID})
 | 
				
			||||||
 | 
						} else if len(opts.IssueIDs) > 0 {
 | 
				
			||||||
 | 
							cond = cond.And(builder.In("comment.issue_id", opts.IssueIDs))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if opts.ReviewID > 0 {
 | 
						if opts.ReviewID > 0 {
 | 
				
			||||||
		cond = cond.And(builder.Eq{"comment.review_id": opts.ReviewID})
 | 
							cond = cond.And(builder.Eq{"comment.review_id": opts.ReviewID})
 | 
				
			||||||
@@ -1044,13 +1020,16 @@ func (opts *FindCommentsOptions) toConds() builder.Cond {
 | 
				
			|||||||
	if len(opts.TreePath) > 0 {
 | 
						if len(opts.TreePath) > 0 {
 | 
				
			||||||
		cond = cond.And(builder.Eq{"comment.tree_path": opts.TreePath})
 | 
							cond = cond.And(builder.Eq{"comment.tree_path": opts.TreePath})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if !opts.Invalidated.IsNone() {
 | 
				
			||||||
 | 
							cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return cond
 | 
						return cond
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FindComments returns all comments according options
 | 
					// FindComments returns all comments according options
 | 
				
			||||||
func FindComments(ctx context.Context, opts *FindCommentsOptions) ([]*Comment, error) {
 | 
					func FindComments(ctx context.Context, opts *FindCommentsOptions) ([]*Comment, error) {
 | 
				
			||||||
	comments := make([]*Comment, 0, 10)
 | 
						comments := make([]*Comment, 0, 10)
 | 
				
			||||||
	sess := db.GetEngine(ctx).Where(opts.toConds())
 | 
						sess := db.GetEngine(ctx).Where(opts.ToConds())
 | 
				
			||||||
	if opts.RepoID > 0 {
 | 
						if opts.RepoID > 0 {
 | 
				
			||||||
		sess.Join("INNER", "issue", "issue.id = comment.issue_id")
 | 
							sess.Join("INNER", "issue", "issue.id = comment.issue_id")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -1069,13 +1048,19 @@ func FindComments(ctx context.Context, opts *FindCommentsOptions) ([]*Comment, e
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// CountComments count all comments according options by ignoring pagination
 | 
					// CountComments count all comments according options by ignoring pagination
 | 
				
			||||||
func CountComments(opts *FindCommentsOptions) (int64, error) {
 | 
					func CountComments(opts *FindCommentsOptions) (int64, error) {
 | 
				
			||||||
	sess := db.GetEngine(db.DefaultContext).Where(opts.toConds())
 | 
						sess := db.GetEngine(db.DefaultContext).Where(opts.ToConds())
 | 
				
			||||||
	if opts.RepoID > 0 {
 | 
						if opts.RepoID > 0 {
 | 
				
			||||||
		sess.Join("INNER", "issue", "issue.id = comment.issue_id")
 | 
							sess.Join("INNER", "issue", "issue.id = comment.issue_id")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return sess.Count(&Comment{})
 | 
						return sess.Count(&Comment{})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateCommentInvalidate updates comment invalidated column
 | 
				
			||||||
 | 
					func UpdateCommentInvalidate(ctx context.Context, c *Comment) error {
 | 
				
			||||||
 | 
						_, err := db.GetEngine(ctx).ID(c.ID).Cols("invalidated").Update(c)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateComment updates information of comment.
 | 
					// UpdateComment updates information of comment.
 | 
				
			||||||
func UpdateComment(c *Comment, doer *user_model.User) error {
 | 
					func UpdateComment(c *Comment, doer *user_model.User) error {
 | 
				
			||||||
	ctx, committer, err := db.TxContext(db.DefaultContext)
 | 
						ctx, committer, err := db.TxContext(db.DefaultContext)
 | 
				
			||||||
@@ -1134,120 +1119,6 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
 | 
				
			|||||||
	return DeleteReaction(ctx, &ReactionOptions{CommentID: comment.ID})
 | 
						return DeleteReaction(ctx, &ReactionOptions{CommentID: comment.ID})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
 | 
					 | 
				
			||||||
type CodeComments map[string]map[int64][]*Comment
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
 | 
					 | 
				
			||||||
func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User) (CodeComments, error) {
 | 
					 | 
				
			||||||
	return fetchCodeCommentsByReview(ctx, issue, currentUser, nil)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review) (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, currentUser, review)
 | 
					 | 
				
			||||||
	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, currentUser *user_model.User, review *Review) ([]*Comment, error) {
 | 
					 | 
				
			||||||
	var comments []*Comment
 | 
					 | 
				
			||||||
	if review == nil {
 | 
					 | 
				
			||||||
		review = &Review{ID: 0}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	conds := opts.toConds()
 | 
					 | 
				
			||||||
	if 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 := CommentList(comments).LoadPosters(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 &&
 | 
					 | 
				
			||||||
				(currentUser == nil || currentUser.ID != re.ReviewerID) {
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			comment.Review = re
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		comments[n] = comment
 | 
					 | 
				
			||||||
		n++
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err := comment.LoadResolveDoer(); err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err := comment.LoadReactions(issue.Repo); err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var err error
 | 
					 | 
				
			||||||
		if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
 | 
					 | 
				
			||||||
			Ctx:       ctx,
 | 
					 | 
				
			||||||
			URLPrefix: issue.Repo.Link(),
 | 
					 | 
				
			||||||
			Metas:     issue.Repo.ComposeMetas(),
 | 
					 | 
				
			||||||
		}, comment.Content); err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return comments[:n], nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FetchCodeCommentsByLine fetches the code comments for a given treePath and line number
 | 
					 | 
				
			||||||
func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64) ([]*Comment, error) {
 | 
					 | 
				
			||||||
	opts := FindCommentsOptions{
 | 
					 | 
				
			||||||
		Type:     CommentTypeCode,
 | 
					 | 
				
			||||||
		IssueID:  issue.ID,
 | 
					 | 
				
			||||||
		TreePath: treePath,
 | 
					 | 
				
			||||||
		Line:     line,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return findCodeComments(ctx, opts, issue, currentUser, nil)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
 | 
					// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
 | 
				
			||||||
func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error {
 | 
					func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error {
 | 
				
			||||||
	_, err := db.GetEngine(db.DefaultContext).Table("comment").
 | 
						_, err := db.GetEngine(db.DefaultContext).Table("comment").
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										129
									
								
								models/issues/comment_code.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								models/issues/comment_code.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					// 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"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
 | 
				
			||||||
 | 
					type CodeComments map[string]map[int64][]*Comment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
 | 
				
			||||||
 | 
					func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User) (CodeComments, error) {
 | 
				
			||||||
 | 
						return fetchCodeCommentsByReview(ctx, issue, currentUser, nil)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review) (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, currentUser, review)
 | 
				
			||||||
 | 
						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, currentUser *user_model.User, review *Review) ([]*Comment, error) {
 | 
				
			||||||
 | 
						var comments []*Comment
 | 
				
			||||||
 | 
						if review == nil {
 | 
				
			||||||
 | 
							review = &Review{ID: 0}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						conds := opts.ToConds()
 | 
				
			||||||
 | 
						if 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 := CommentList(comments).LoadPosters(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 &&
 | 
				
			||||||
 | 
									(currentUser == nil || currentUser.ID != re.ReviewerID) {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								comment.Review = re
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							comments[n] = comment
 | 
				
			||||||
 | 
							n++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := comment.LoadResolveDoer(); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := comment.LoadReactions(issue.Repo); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var err error
 | 
				
			||||||
 | 
							if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
 | 
				
			||||||
 | 
								Ctx:       ctx,
 | 
				
			||||||
 | 
								URLPrefix: issue.Repo.Link(),
 | 
				
			||||||
 | 
								Metas:     issue.Repo.ComposeMetas(),
 | 
				
			||||||
 | 
							}, comment.Content); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return comments[:n], nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FetchCodeCommentsByLine fetches the code comments for a given treePath and line number
 | 
				
			||||||
 | 
					func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64) ([]*Comment, error) {
 | 
				
			||||||
 | 
						opts := FindCommentsOptions{
 | 
				
			||||||
 | 
							Type:     CommentTypeCode,
 | 
				
			||||||
 | 
							IssueID:  issue.ID,
 | 
				
			||||||
 | 
							TreePath: treePath,
 | 
				
			||||||
 | 
							Line:     line,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return findCodeComments(ctx, opts, issue, currentUser, nil)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -12,7 +12,6 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"xorm.io/xorm"
 | 
						"xorm.io/xorm"
 | 
				
			||||||
@@ -161,7 +160,7 @@ func (prs PullRequestList) loadAttributes(ctx context.Context) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Load issues.
 | 
						// Load issues.
 | 
				
			||||||
	issueIDs := prs.getIssueIDs()
 | 
						issueIDs := prs.GetIssueIDs()
 | 
				
			||||||
	issues := make([]*Issue, 0, len(issueIDs))
 | 
						issues := make([]*Issue, 0, len(issueIDs))
 | 
				
			||||||
	if err := db.GetEngine(ctx).
 | 
						if err := db.GetEngine(ctx).
 | 
				
			||||||
		Where("id > 0").
 | 
							Where("id > 0").
 | 
				
			||||||
@@ -180,7 +179,8 @@ func (prs PullRequestList) loadAttributes(ctx context.Context) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (prs PullRequestList) getIssueIDs() []int64 {
 | 
					// GetIssueIDs returns all issue ids
 | 
				
			||||||
 | 
					func (prs PullRequestList) GetIssueIDs() []int64 {
 | 
				
			||||||
	issueIDs := make([]int64, 0, len(prs))
 | 
						issueIDs := make([]int64, 0, len(prs))
 | 
				
			||||||
	for i := range prs {
 | 
						for i := range prs {
 | 
				
			||||||
		issueIDs = append(issueIDs, prs[i].IssueID)
 | 
							issueIDs = append(issueIDs, prs[i].IssueID)
 | 
				
			||||||
@@ -192,24 +192,3 @@ func (prs PullRequestList) getIssueIDs() []int64 {
 | 
				
			|||||||
func (prs PullRequestList) LoadAttributes() error {
 | 
					func (prs PullRequestList) LoadAttributes() error {
 | 
				
			||||||
	return prs.loadAttributes(db.DefaultContext)
 | 
						return prs.loadAttributes(db.DefaultContext)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// InvalidateCodeComments will lookup the prs for code comments which got invalidated by change
 | 
					 | 
				
			||||||
func (prs PullRequestList) InvalidateCodeComments(ctx context.Context, doer *user_model.User, repo *git.Repository, branch string) error {
 | 
					 | 
				
			||||||
	if len(prs) == 0 {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	issueIDs := prs.getIssueIDs()
 | 
					 | 
				
			||||||
	var codeComments []*Comment
 | 
					 | 
				
			||||||
	if err := db.GetEngine(ctx).
 | 
					 | 
				
			||||||
		Where("type = ? and invalidated = ?", CommentTypeCode, false).
 | 
					 | 
				
			||||||
		In("issue_id", issueIDs).
 | 
					 | 
				
			||||||
		Find(&codeComments); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("find code comments: %w", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, comment := range codeComments {
 | 
					 | 
				
			||||||
		if err := comment.CheckInvalidation(repo, doer, branch); err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -972,7 +972,7 @@ func DeleteReview(r *Review) error {
 | 
				
			|||||||
		ReviewID: r.ID,
 | 
							ReviewID: r.ID,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := sess.Where(opts.toConds()).Delete(new(Comment)); err != nil {
 | 
						if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -982,7 +982,7 @@ func DeleteReview(r *Review) error {
 | 
				
			|||||||
		ReviewID: r.ID,
 | 
							ReviewID: r.ID,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := sess.Where(opts.toConds()).Delete(new(Comment)); err != nil {
 | 
						if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1006,7 +1006,7 @@ func (r *Review) GetCodeCommentsCount() int {
 | 
				
			|||||||
		IssueID:  r.IssueID,
 | 
							IssueID:  r.IssueID,
 | 
				
			||||||
		ReviewID: r.ID,
 | 
							ReviewID: r.ID,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	conds := opts.toConds()
 | 
						conds := opts.ToConds()
 | 
				
			||||||
	if r.ID == 0 {
 | 
						if r.ID == 0 {
 | 
				
			||||||
		conds = conds.And(builder.Eq{"invalidated": false})
 | 
							conds = conds.And(builder.Eq{"invalidated": false})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -1026,7 +1026,7 @@ func (r *Review) HTMLURL() string {
 | 
				
			|||||||
		ReviewID: r.ID,
 | 
							ReviewID: r.ID,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	comment := new(Comment)
 | 
						comment := new(Comment)
 | 
				
			||||||
	has, err := db.GetEngine(db.DefaultContext).Where(opts.toConds()).Get(comment)
 | 
						has, err := db.GetEngine(db.DefaultContext).Where(opts.ToConds()).Get(comment)
 | 
				
			||||||
	if err != nil || !has {
 | 
						if err != nil || !has {
 | 
				
			||||||
		return ""
 | 
							return ""
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -240,7 +240,7 @@ func checkForInvalidation(ctx context.Context, requests issues_model.PullRequest
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	go func() {
 | 
						go func() {
 | 
				
			||||||
		// FIXME: graceful: We need to tell the manager we're doing something...
 | 
							// FIXME: graceful: We need to tell the manager we're doing something...
 | 
				
			||||||
		err := requests.InvalidateCodeComments(ctx, doer, gitRepo, branch)
 | 
							err := InvalidateCodeComments(ctx, requests, doer, gitRepo, branch)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Error("PullRequestList.InvalidateCodeComments: %v", err)
 | 
								log.Error("PullRequestList.InvalidateCodeComments: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,53 @@ import (
 | 
				
			|||||||
	issue_service "code.gitea.io/gitea/services/issue"
 | 
						issue_service "code.gitea.io/gitea/services/issue"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var notEnoughLines = regexp.MustCompile(`fatal: file .* has only \d+ lines?`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// checkInvalidation checks if the line of code comment got changed by another commit.
 | 
				
			||||||
 | 
					// If the line got changed the comment is going to be invalidated.
 | 
				
			||||||
 | 
					func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error {
 | 
				
			||||||
 | 
						// FIXME differentiate between previous and proposed line
 | 
				
			||||||
 | 
						commit, err := repo.LineBlame(branch, repo.Path, c.TreePath, uint(c.UnsignedLine()))
 | 
				
			||||||
 | 
						if err != nil && (strings.Contains(err.Error(), "fatal: no such path") || notEnoughLines.MatchString(err.Error())) {
 | 
				
			||||||
 | 
							c.Invalidated = true
 | 
				
			||||||
 | 
							return issues_model.UpdateCommentInvalidate(ctx, c)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.CommitSHA != "" && c.CommitSHA != commit.ID.String() {
 | 
				
			||||||
 | 
							c.Invalidated = true
 | 
				
			||||||
 | 
							return issues_model.UpdateCommentInvalidate(ctx, c)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InvalidateCodeComments will lookup the prs for code comments which got invalidated by change
 | 
				
			||||||
 | 
					func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestList, doer *user_model.User, repo *git.Repository, branch string) error {
 | 
				
			||||||
 | 
						if len(prs) == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issueIDs := prs.GetIssueIDs()
 | 
				
			||||||
 | 
						var codeComments []*issues_model.Comment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := db.Find(ctx, &issues_model.FindCommentsOptions{
 | 
				
			||||||
 | 
							ListOptions: db.ListOptions{
 | 
				
			||||||
 | 
								ListAll: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Type:        issues_model.CommentTypeCode,
 | 
				
			||||||
 | 
							Invalidated: util.OptionalBoolFalse,
 | 
				
			||||||
 | 
							IssueIDs:    issueIDs,
 | 
				
			||||||
 | 
						}, &codeComments); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("find code comments: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, comment := range codeComments {
 | 
				
			||||||
 | 
							if err := checkInvalidation(ctx, comment, doer, repo, branch); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateCodeComment creates a comment on the code line
 | 
					// CreateCodeComment creates a comment on the code line
 | 
				
			||||||
func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue, line int64, content, treePath string, isReview bool, replyReviewID int64, latestCommitID string) (*issues_model.Comment, error) {
 | 
					func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue, line int64, content, treePath string, isReview bool, replyReviewID int64, latestCommitID string) (*issues_model.Comment, error) {
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
@@ -114,8 +161,6 @@ func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git.
 | 
				
			|||||||
	return comment, nil
 | 
						return comment, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var notEnoughLines = regexp.MustCompile(`exit status 128 - fatal: file .* has only \d+ lines?`)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// createCodeComment creates a plain code comment at the specified line / path
 | 
					// createCodeComment creates a plain code comment at the specified line / path
 | 
				
			||||||
func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content, treePath string, line, reviewID int64) (*issues_model.Comment, error) {
 | 
					func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content, treePath string, line, reviewID int64) (*issues_model.Comment, error) {
 | 
				
			||||||
	var commitID, patch string
 | 
						var commitID, patch string
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user