2018-08-06 07:43:22 +03:00
// Copyright 2018 The Gitea Authors.
// Copyright 2016 The Gogs Authors.
// All rights reserved.
2016-03-05 20:58:51 +03:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
2018-08-06 07:43:22 +03:00
"bytes"
2016-03-05 20:58:51 +03:00
"fmt"
"strings"
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/modules/git"
2018-08-06 07:43:22 +03:00
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
2016-03-05 20:58:51 +03:00
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
2019-06-23 18:22:43 +03:00
"xorm.io/builder"
2016-08-26 23:40:53 +03:00
2019-05-11 13:21:34 +03:00
api "code.gitea.io/gitea/modules/structs"
2016-03-05 20:58:51 +03:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
2017-09-16 20:17:57 +03:00
"code.gitea.io/gitea/modules/markup"
2017-12-11 07:37:04 +03:00
"code.gitea.io/gitea/modules/util"
2016-03-05 20:58:51 +03:00
)
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
type CommentType int
2017-06-21 04:00:44 +03:00
// define unknown comment type
const (
CommentTypeUnknown CommentType = - 1
)
2016-11-28 16:33:09 +03:00
// Enumerate all the comment types
2016-03-05 20:58:51 +03:00
const (
// Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
2016-11-07 19:30:04 +03:00
CommentTypeComment CommentType = iota
CommentTypeReopen
2016-11-07 19:35:34 +03:00
CommentTypeClose
2016-03-05 20:58:51 +03:00
// References.
2016-11-07 19:35:34 +03:00
CommentTypeIssueRef
2016-03-05 20:58:51 +03:00
// Reference from a commit (not part of a pull request)
2016-11-07 19:35:34 +03:00
CommentTypeCommitRef
2016-03-05 20:58:51 +03:00
// Reference from a comment
2016-11-07 23:58:22 +03:00
CommentTypeCommentRef
2016-03-05 20:58:51 +03:00
// Reference from a pull request
2016-11-07 19:35:34 +03:00
CommentTypePullRef
2017-01-30 15:46:45 +03:00
// Labels changed
CommentTypeLabel
2017-02-01 05:36:08 +03:00
// Milestone changed
CommentTypeMilestone
2017-02-03 18:09:10 +03:00
// Assignees changed
CommentTypeAssignees
2017-02-05 17:36:00 +03:00
// Change Title
CommentTypeChangeTitle
2017-02-11 07:00:29 +03:00
// Delete Branch
CommentTypeDeleteBranch
2017-09-12 09:48:13 +03:00
// Start a stopwatch for time tracking
CommentTypeStartTracking
// Stop a stopwatch for time tracking
CommentTypeStopTracking
// Add time manual for time tracking
CommentTypeAddTimeManual
// Cancel a stopwatch for time tracking
CommentTypeCancelTracking
2018-05-01 22:05:28 +03:00
// Added a due date
CommentTypeAddedDeadline
// Modified the due date
CommentTypeModifiedDeadline
// Removed a due date
CommentTypeRemovedDeadline
2018-07-18 00:23:58 +03:00
// Dependency added
CommentTypeAddDependency
//Dependency removed
CommentTypeRemoveDependency
2018-08-06 07:43:22 +03:00
// Comment a line of code
CommentTypeCode
// Reviews a pull request by giving general feedback
CommentTypeReview
2019-02-18 23:55:04 +03:00
// Lock an issue, giving only collaborators access
CommentTypeLock
// Unlocks a previously locked issue
CommentTypeUnlock
2016-03-05 20:58:51 +03:00
)
2016-11-28 16:33:09 +03:00
// CommentTag defines comment tag type
2016-03-05 20:58:51 +03:00
type CommentTag int
2016-11-28 16:33:09 +03:00
// Enumerate all the comment tag types
2016-03-05 20:58:51 +03:00
const (
2016-11-07 19:30:04 +03:00
CommentTagNone CommentTag = iota
CommentTagPoster
CommentTagWriter
2016-11-07 19:35:34 +03:00
CommentTagOwner
2016-03-05 20:58:51 +03:00
)
// Comment represents a comment in commit and issue page.
type Comment struct {
2018-07-18 00:23:58 +03:00
ID int64 ` xorm:"pk autoincr" `
Type CommentType
2019-07-08 05:14:12 +03:00
PosterID int64 ` xorm:"INDEX" `
Poster * User ` xorm:"-" `
OriginalAuthor string
OriginalAuthorID int64
2018-07-18 00:23:58 +03:00
IssueID int64 ` xorm:"INDEX" `
Issue * Issue ` xorm:"-" `
LabelID int64
Label * Label ` xorm:"-" `
OldMilestoneID int64
MilestoneID int64
OldMilestone * Milestone ` xorm:"-" `
Milestone * Milestone ` xorm:"-" `
AssigneeID int64
RemovedAssignee bool
Assignee * User ` xorm:"-" `
OldTitle string
NewTitle string
DependentIssueID int64
DependentIssue * Issue ` xorm:"-" `
2017-02-03 18:09:10 +03:00
2017-02-01 05:36:08 +03:00
CommitID int64
2018-08-06 07:43:22 +03:00
Line int64 // - previous line / + proposed line
TreePath string
2016-03-10 03:53:30 +03:00
Content string ` xorm:"TEXT" `
RenderedContent string ` xorm:"-" `
2018-08-06 07:43:22 +03:00
// Path represents the 4 lines of code cemented by this comment
Patch string ` xorm:"TEXT" `
2017-12-11 07:37:04 +03:00
CreatedUnix util . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix util . TimeStamp ` xorm:"INDEX updated" `
2016-03-05 20:58:51 +03:00
// Reference issue in commit message
CommitSHA string ` xorm:"VARCHAR(40)" `
Attachments [ ] * Attachment ` xorm:"-" `
2017-12-04 02:14:26 +03:00
Reactions ReactionList ` xorm:"-" `
2016-03-05 20:58:51 +03:00
// For view issue page.
ShowTag CommentTag ` xorm:"-" `
2018-08-06 07:43:22 +03:00
Review * Review ` xorm:"-" `
ReviewID int64
Invalidated bool
2016-03-05 20:58:51 +03:00
}
2018-05-16 17:01:55 +03:00
// LoadIssue loads issue from database
func ( c * Comment ) LoadIssue ( ) ( err error ) {
if c . Issue != nil {
return nil
}
c . Issue , err = GetIssueByID ( c . IssueID )
return
}
2019-04-18 08:00:03 +03:00
func ( c * Comment ) loadPoster ( e Engine ) ( err error ) {
if c . Poster != nil {
return nil
}
c . Poster , err = getUserByID ( e , c . PosterID )
if err != nil {
if IsErrUserNotExist ( err ) {
c . PosterID = - 1
c . Poster = NewGhostUser ( )
} else {
log . Error ( "getUserByID[%d]: %v" , c . ID , err )
}
}
return err
}
2016-11-28 16:33:09 +03:00
// AfterDelete is invoked from XORM after the object is deleted.
2016-03-05 20:58:51 +03:00
func ( c * Comment ) AfterDelete ( ) {
2018-06-12 01:54:30 +03:00
if c . ID <= 0 {
return
}
2016-03-05 20:58:51 +03:00
_ , err := DeleteAttachmentsByComment ( c . ID , true )
if err != nil {
log . Info ( "Could not delete files for comment %d on issue #%d: %s" , c . ID , c . IssueID , err )
}
}
2016-12-22 11:29:26 +03:00
// HTMLURL formats a URL-string to the issue-comment
func ( c * Comment ) HTMLURL ( ) string {
2018-05-16 17:01:55 +03:00
err := c . LoadIssue ( )
2016-12-22 11:29:26 +03:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 10:48:31 +03:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2016-12-22 11:29:26 +03:00
return ""
}
2018-12-13 18:55:43 +03:00
err = c . Issue . loadRepo ( x )
if err != nil { // Silently dropping errors :unamused:
2019-04-02 10:48:31 +03:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 18:55:43 +03:00
return ""
}
2018-08-06 07:43:22 +03:00
if c . Type == CommentTypeCode {
if c . ReviewID == 0 {
return fmt . Sprintf ( "%s/files#%s" , c . Issue . HTMLURL ( ) , c . HashTag ( ) )
}
if c . Review == nil {
if err := c . LoadReview ( ) ; err != nil {
log . Warn ( "LoadReview(%d): %v" , c . ReviewID , err )
return fmt . Sprintf ( "%s/files#%s" , c . Issue . HTMLURL ( ) , c . HashTag ( ) )
}
}
if c . Review . Type <= ReviewTypePending {
return fmt . Sprintf ( "%s/files#%s" , c . Issue . HTMLURL ( ) , c . HashTag ( ) )
}
}
2018-05-16 17:01:55 +03:00
return fmt . Sprintf ( "%s#%s" , c . Issue . HTMLURL ( ) , c . HashTag ( ) )
2016-12-22 11:29:26 +03:00
}
// IssueURL formats a URL-string to the issue
func ( c * Comment ) IssueURL ( ) string {
2018-05-16 17:01:55 +03:00
err := c . LoadIssue ( )
2016-12-22 11:29:26 +03:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 10:48:31 +03:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2016-12-22 11:29:26 +03:00
return ""
}
2018-05-16 17:01:55 +03:00
if c . Issue . IsPull {
2016-12-22 11:29:26 +03:00
return ""
}
2018-12-13 18:55:43 +03:00
err = c . Issue . loadRepo ( x )
if err != nil { // Silently dropping errors :unamused:
2019-04-02 10:48:31 +03:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 18:55:43 +03:00
return ""
}
2018-05-16 17:01:55 +03:00
return c . Issue . HTMLURL ( )
2016-12-22 11:29:26 +03:00
}
// PRURL formats a URL-string to the pull-request
func ( c * Comment ) PRURL ( ) string {
2018-05-16 17:01:55 +03:00
err := c . LoadIssue ( )
2016-12-22 11:29:26 +03:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 10:48:31 +03:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2016-12-22 11:29:26 +03:00
return ""
}
2018-12-13 18:55:43 +03:00
err = c . Issue . loadRepo ( x )
if err != nil { // Silently dropping errors :unamused:
2019-04-02 10:48:31 +03:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 18:55:43 +03:00
return ""
}
2018-05-16 17:01:55 +03:00
if ! c . Issue . IsPull {
2016-12-22 11:29:26 +03:00
return ""
}
2018-05-16 17:01:55 +03:00
return c . Issue . HTMLURL ( )
2016-12-22 11:29:26 +03:00
}
2016-11-28 16:33:09 +03:00
// APIFormat converts a Comment to the api.Comment format
2016-08-26 21:23:21 +03:00
func ( c * Comment ) APIFormat ( ) * api . Comment {
2016-08-26 23:40:53 +03:00
return & api . Comment {
2016-12-22 11:29:26 +03:00
ID : c . ID ,
Poster : c . Poster . APIFormat ( ) ,
HTMLURL : c . HTMLURL ( ) ,
IssueURL : c . IssueURL ( ) ,
PRURL : c . PRURL ( ) ,
Body : c . Content ,
2017-12-11 07:37:04 +03:00
Created : c . CreatedUnix . AsTime ( ) ,
Updated : c . UpdatedUnix . AsTime ( ) ,
2016-08-26 21:23:21 +03:00
}
}
2018-05-16 17:01:55 +03:00
// CommentHashTag returns unique hash tag for comment id.
func CommentHashTag ( id int64 ) string {
return fmt . Sprintf ( "issuecomment-%d" , id )
}
2016-03-05 20:58:51 +03:00
// HashTag returns unique hash tag for comment.
func ( c * Comment ) HashTag ( ) string {
2018-05-16 17:01:55 +03:00
return CommentHashTag ( c . ID )
2016-03-05 20:58:51 +03:00
}
// EventTag returns unique event hash tag for comment.
func ( c * Comment ) EventTag ( ) string {
return "event-" + com . ToStr ( c . ID )
}
2017-01-30 15:46:45 +03:00
// LoadLabel if comment.Type is CommentTypeLabel, then load Label
func ( c * Comment ) LoadLabel ( ) error {
var label Label
has , err := x . ID ( c . LabelID ) . Get ( & label )
if err != nil {
return err
2017-02-11 15:56:57 +03:00
} else if has {
c . Label = & label
} else {
// Ignore Label is deleted, but not clear this table
log . Warn ( "Commit %d cannot load label %d" , c . ID , c . LabelID )
2017-01-30 15:46:45 +03:00
}
2017-02-11 15:56:57 +03:00
2017-01-30 15:46:45 +03:00
return nil
}
2017-02-01 05:36:08 +03:00
// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
func ( c * Comment ) LoadMilestone ( ) error {
if c . OldMilestoneID > 0 {
var oldMilestone Milestone
has , err := x . ID ( c . OldMilestoneID ) . Get ( & oldMilestone )
if err != nil {
return err
2017-06-17 07:51:28 +03:00
} else if has {
c . OldMilestone = & oldMilestone
2017-02-01 05:36:08 +03:00
}
}
if c . MilestoneID > 0 {
var milestone Milestone
has , err := x . ID ( c . MilestoneID ) . Get ( & milestone )
if err != nil {
return err
2017-06-17 07:51:28 +03:00
} else if has {
c . Milestone = & milestone
2017-02-01 05:36:08 +03:00
}
}
return nil
}
2018-12-13 18:55:43 +03:00
// LoadPoster loads comment poster
func ( c * Comment ) LoadPoster ( ) error {
if c . PosterID <= 0 || c . Poster != nil {
return nil
}
var err error
c . Poster , err = getUserByID ( x , c . PosterID )
if err != nil {
if IsErrUserNotExist ( err ) {
c . PosterID = - 1
c . Poster = NewGhostUser ( )
} else {
2019-04-02 10:48:31 +03:00
log . Error ( "getUserByID[%d]: %v" , c . ID , err )
2018-12-13 18:55:43 +03:00
}
}
return nil
}
// LoadAttachments loads attachments
func ( c * Comment ) LoadAttachments ( ) error {
if len ( c . Attachments ) > 0 {
return nil
}
var err error
c . Attachments , err = getAttachmentsByCommentID ( x , c . ID )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "getAttachmentsByCommentID[%d]: %v" , c . ID , err )
2018-12-13 18:55:43 +03:00
}
return nil
}
2018-05-09 19:29:04 +03:00
// LoadAssigneeUser if comment.Type is CommentTypeAssignees, then load assignees
func ( c * Comment ) LoadAssigneeUser ( ) error {
2017-02-03 18:09:10 +03:00
var err error
if c . AssigneeID > 0 {
c . Assignee , err = getUserByID ( x , c . AssigneeID )
if err != nil {
2018-01-07 12:13:10 +03:00
if ! IsErrUserNotExist ( err ) {
return err
}
c . Assignee = NewGhostUser ( )
2017-02-03 18:09:10 +03:00
}
}
return nil
}
2018-07-18 00:23:58 +03:00
// LoadDepIssueDetails loads Dependent Issue Details
func ( c * Comment ) LoadDepIssueDetails ( ) ( err error ) {
if c . DependentIssueID <= 0 || c . DependentIssue != nil {
return nil
}
c . DependentIssue , err = getIssueByID ( x , c . DependentIssueID )
return err
}
2016-07-15 19:36:39 +03:00
// MailParticipants sends new comment emails to repository watchers
// and mentioned people.
2019-01-13 17:42:55 +03:00
func ( c * Comment ) MailParticipants ( opType ActionType , issue * Issue ) ( err error ) {
return c . mailParticipants ( x , opType , issue )
}
func ( c * Comment ) mailParticipants ( e Engine , opType ActionType , issue * Issue ) ( err error ) {
2017-09-16 20:17:57 +03:00
mentions := markup . FindAllMentions ( c . Content )
2016-12-22 12:00:39 +03:00
if err = UpdateIssueMentions ( e , c . IssueID , mentions ) ; err != nil {
2016-11-28 16:33:09 +03:00
return fmt . Errorf ( "UpdateIssueMentions [%d]: %v" , c . IssueID , err )
2016-07-15 19:36:39 +03:00
}
2019-06-11 22:27:38 +03:00
if len ( c . Content ) > 0 {
if err = mailIssueCommentToParticipants ( e , issue , c . Poster , c . Content , c , mentions ) ; err != nil {
log . Error ( "mailIssueCommentToParticipants: %v" , err )
}
}
2017-11-03 12:23:17 +03:00
2016-07-15 19:36:39 +03:00
switch opType {
2016-11-07 18:37:32 +03:00
case ActionCloseIssue :
2019-06-11 22:27:38 +03:00
ct := fmt . Sprintf ( "Closed #%d." , issue . Index )
if err = mailIssueCommentToParticipants ( e , issue , c . Poster , ct , c , mentions ) ; err != nil {
log . Error ( "mailIssueCommentToParticipants: %v" , err )
}
2016-11-07 18:37:32 +03:00
case ActionReopenIssue :
2019-06-11 22:27:38 +03:00
ct := fmt . Sprintf ( "Reopened #%d." , issue . Index )
if err = mailIssueCommentToParticipants ( e , issue , c . Poster , ct , c , mentions ) ; err != nil {
log . Error ( "mailIssueCommentToParticipants: %v" , err )
}
2016-07-15 19:36:39 +03:00
}
return nil
}
2017-12-04 02:14:26 +03:00
func ( c * Comment ) loadReactions ( e Engine ) ( err error ) {
if c . Reactions != nil {
return nil
}
c . Reactions , err = findReactions ( e , FindReactionsOptions {
IssueID : c . IssueID ,
CommentID : c . ID ,
} )
if err != nil {
return err
}
// Load reaction user data
if _ , err := c . Reactions . LoadUsers ( ) ; err != nil {
return err
}
return nil
}
// LoadReactions loads comment reactions
func ( c * Comment ) LoadReactions ( ) error {
return c . loadReactions ( x )
}
2018-08-06 07:43:22 +03:00
func ( c * Comment ) loadReview ( e Engine ) ( err error ) {
2018-12-13 18:55:43 +03:00
if c . Review == nil {
if c . Review , err = getReviewByID ( e , c . ReviewID ) ; err != nil {
return err
}
2018-08-06 07:43:22 +03:00
}
2019-05-06 15:09:31 +03:00
c . Review . Issue = c . Issue
2018-08-06 07:43:22 +03:00
return nil
}
// LoadReview loads the associated review
func ( c * Comment ) LoadReview ( ) error {
return c . loadReview ( x )
}
2019-06-12 22:41:28 +03:00
func ( c * Comment ) checkInvalidation ( doer * User , repo * git . Repository , branch string ) error {
2018-08-06 07:43:22 +03:00
// FIXME differentiate between previous and proposed line
commit , err := repo . LineBlame ( branch , repo . Path , c . TreePath , uint ( c . UnsignedLine ( ) ) )
if err != nil {
return err
}
2018-09-17 17:59:49 +03:00
if c . CommitSHA != "" && c . CommitSHA != commit . ID . String ( ) {
2018-08-06 07:43:22 +03:00
c . Invalidated = true
return UpdateComment ( doer , c , "" )
}
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 , branch string ) error {
2019-06-12 22:41:28 +03:00
return c . checkInvalidation ( doer , repo , branch )
2018-08-06 07:43:22 +03:00
}
// 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 {
if c . Line < 0 {
return "previous"
}
return "proposed"
}
// UnsignedLine returns the LOC of the code comment without + or -
func ( c * Comment ) UnsignedLine ( ) uint64 {
if c . Line < 0 {
return uint64 ( c . Line * - 1 )
}
return uint64 ( c . Line )
}
// AsDiff returns c.Patch as *Diff
func ( c * Comment ) AsDiff ( ) ( * Diff , error ) {
diff , err := ParsePatch ( setting . Git . MaxGitDiffLines ,
setting . Git . MaxGitDiffLineCharacters , setting . Git . MaxGitDiffFiles , strings . NewReader ( c . Patch ) )
if err != nil {
return nil , err
}
if len ( diff . Files ) == 0 {
return nil , fmt . Errorf ( "no file found for comment ID: %d" , c . ID )
}
secs := diff . Files [ 0 ] . Sections
if len ( secs ) == 0 {
return nil , fmt . Errorf ( "no sections found for comment ID: %d" , c . ID )
}
return diff , nil
}
// MustAsDiff executes AsDiff and logs the error instead of returning
func ( c * Comment ) MustAsDiff ( ) * Diff {
diff , err := c . AsDiff ( )
if err != nil {
log . Warn ( "MustAsDiff: %v" , err )
}
return diff
}
// CodeCommentURL returns the url to a comment in code
func ( c * Comment ) CodeCommentURL ( ) string {
err := c . LoadIssue ( )
if err != nil { // Silently dropping errors :unamused:
2019-04-02 10:48:31 +03:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2018-08-06 07:43:22 +03:00
return ""
}
2018-12-13 18:55:43 +03:00
err = c . Issue . loadRepo ( x )
if err != nil { // Silently dropping errors :unamused:
2019-04-02 10:48:31 +03:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 18:55:43 +03:00
return ""
}
2018-08-06 07:43:22 +03:00
return fmt . Sprintf ( "%s/files#%s" , c . Issue . HTMLURL ( ) , c . HashTag ( ) )
}
2016-03-05 20:58:51 +03:00
func createComment ( e * xorm . Session , opts * CreateCommentOptions ) ( _ * Comment , err error ) {
2017-01-30 15:46:45 +03:00
var LabelID int64
if opts . Label != nil {
LabelID = opts . Label . ID
}
2018-07-18 00:23:58 +03:00
2016-03-05 20:58:51 +03:00
comment := & Comment {
2018-07-18 00:23:58 +03:00
Type : opts . Type ,
PosterID : opts . Doer . ID ,
Poster : opts . Doer ,
IssueID : opts . Issue . ID ,
LabelID : LabelID ,
OldMilestoneID : opts . OldMilestoneID ,
MilestoneID : opts . MilestoneID ,
RemovedAssignee : opts . RemovedAssignee ,
AssigneeID : opts . AssigneeID ,
CommitID : opts . CommitID ,
CommitSHA : opts . CommitSHA ,
Line : opts . LineNum ,
Content : opts . Content ,
OldTitle : opts . OldTitle ,
NewTitle : opts . NewTitle ,
DependentIssueID : opts . DependentIssueID ,
2018-08-06 07:43:22 +03:00
TreePath : opts . TreePath ,
ReviewID : opts . ReviewID ,
Patch : opts . Patch ,
2016-03-05 20:58:51 +03:00
}
if _ , err = e . Insert ( comment ) ; err != nil {
return nil , err
}
2017-01-30 15:46:45 +03:00
if err = opts . Repo . getOwner ( e ) ; err != nil {
return nil , err
}
2018-08-06 07:43:22 +03:00
if err = sendCreateCommentAction ( e , opts , comment ) ; err != nil {
return nil , err
}
return comment , nil
}
func sendCreateCommentAction ( e * xorm . Session , opts * CreateCommentOptions , comment * Comment ) ( err error ) {
2016-03-06 02:08:42 +03:00
// Compose comment action, could be plain comment, close or reopen issue/pull request.
2016-03-05 20:58:51 +03:00
// This object will be used to notify watchers in the end of function.
act := & Action {
2017-05-26 04:38:18 +03:00
ActUserID : opts . Doer . ID ,
ActUser : opts . Doer ,
Content : fmt . Sprintf ( "%d|%s" , opts . Issue . Index , strings . Split ( opts . Content , "\n" ) [ 0 ] ) ,
RepoID : opts . Repo . ID ,
Repo : opts . Repo ,
2017-06-25 21:20:29 +03:00
Comment : comment ,
CommentID : comment . ID ,
2017-05-26 04:38:18 +03:00
IsPrivate : opts . Repo . IsPrivate ,
2016-03-05 20:58:51 +03:00
}
// Check comment type.
switch opts . Type {
2018-08-06 07:43:22 +03:00
case CommentTypeCode :
if comment . ReviewID != 0 {
if comment . Review == nil {
2019-01-14 05:29:58 +03:00
if err := comment . loadReview ( e ) ; err != nil {
2018-08-06 07:43:22 +03:00
return err
}
}
if comment . Review . Type <= ReviewTypePending {
return nil
}
}
fallthrough
2016-11-07 19:30:04 +03:00
case CommentTypeComment :
2016-11-07 18:37:32 +03:00
act . OpType = ActionCommentIssue
2016-03-05 20:58:51 +03:00
if _ , err = e . Exec ( "UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?" , opts . Issue . ID ) ; err != nil {
2018-08-06 07:43:22 +03:00
return err
2016-03-05 20:58:51 +03:00
}
// Check attachments
attachments := make ( [ ] * Attachment , 0 , len ( opts . Attachments ) )
for _ , uuid := range opts . Attachments {
attach , err := getAttachmentByUUID ( e , uuid )
if err != nil {
if IsErrAttachmentNotExist ( err ) {
continue
}
2018-08-06 07:43:22 +03:00
return fmt . Errorf ( "getAttachmentByUUID [%s]: %v" , uuid , err )
2016-03-05 20:58:51 +03:00
}
attachments = append ( attachments , attach )
}
for i := range attachments {
attachments [ i ] . IssueID = opts . Issue . ID
attachments [ i ] . CommentID = comment . ID
// No assign value could be 0, so ignore AllCols().
2017-10-05 07:43:04 +03:00
if _ , err = e . ID ( attachments [ i ] . ID ) . Update ( attachments [ i ] ) ; err != nil {
2018-08-06 07:43:22 +03:00
return fmt . Errorf ( "update attachment [%d]: %v" , attachments [ i ] . ID , err )
2016-03-05 20:58:51 +03:00
}
}
2016-11-07 19:30:04 +03:00
case CommentTypeReopen :
2016-11-07 18:37:32 +03:00
act . OpType = ActionReopenIssue
2016-03-05 20:58:51 +03:00
if opts . Issue . IsPull {
2016-11-07 18:37:32 +03:00
act . OpType = ActionReopenPullRequest
2016-03-05 20:58:51 +03:00
}
2019-07-19 00:51:33 +03:00
if err = opts . Issue . updateClosedNum ( e ) ; err != nil {
2018-08-06 07:43:22 +03:00
return err
2016-03-05 20:58:51 +03:00
}
2016-03-06 02:08:42 +03:00
2016-11-07 19:35:34 +03:00
case CommentTypeClose :
2016-11-07 18:37:32 +03:00
act . OpType = ActionCloseIssue
2016-03-05 20:58:51 +03:00
if opts . Issue . IsPull {
2016-11-07 18:37:32 +03:00
act . OpType = ActionClosePullRequest
2016-03-05 20:58:51 +03:00
}
2019-07-19 00:51:33 +03:00
if err = opts . Issue . updateClosedNum ( e ) ; err != nil {
2018-08-06 07:43:22 +03:00
return err
2016-03-05 20:58:51 +03:00
}
2017-07-27 04:20:38 +03:00
}
// update the issue's updated_unix column
2018-04-29 07:48:14 +03:00
if err = updateIssueCols ( e , opts . Issue , "updated_unix" ) ; err != nil {
2018-08-06 07:43:22 +03:00
return err
2016-03-05 20:58:51 +03:00
}
2016-07-15 19:36:39 +03:00
// Notify watchers for whatever action comes in, ignore if no action type.
2016-03-11 13:11:58 +03:00
if act . OpType > 0 {
if err = notifyWatchers ( e , act ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "notifyWatchers: %v" , err )
2016-03-11 13:11:58 +03:00
}
2016-03-05 20:58:51 +03:00
}
2018-08-06 07:43:22 +03:00
return nil
2016-03-05 20:58:51 +03:00
}
2018-12-13 18:55:43 +03:00
func createStatusComment ( e * xorm . Session , doer * User , issue * Issue ) ( * Comment , error ) {
2016-11-07 19:35:34 +03:00
cmtType := CommentTypeClose
2016-03-05 20:58:51 +03:00
if ! issue . IsClosed {
2016-11-07 19:30:04 +03:00
cmtType = CommentTypeReopen
2016-03-05 20:58:51 +03:00
}
return createComment ( e , & CreateCommentOptions {
Type : cmtType ,
Doer : doer ,
2018-12-13 18:55:43 +03:00
Repo : issue . Repo ,
2016-03-05 20:58:51 +03:00
Issue : issue ,
} )
}
2017-01-30 15:46:45 +03:00
func createLabelComment ( e * xorm . Session , doer * User , repo * Repository , issue * Issue , label * Label , add bool ) ( * Comment , error ) {
var content string
if add {
content = "1"
}
return createComment ( e , & CreateCommentOptions {
Type : CommentTypeLabel ,
Doer : doer ,
Repo : repo ,
Issue : issue ,
Label : label ,
Content : content ,
} )
}
2017-02-01 05:36:08 +03:00
func createMilestoneComment ( e * xorm . Session , doer * User , repo * Repository , issue * Issue , oldMilestoneID , milestoneID int64 ) ( * Comment , error ) {
return createComment ( e , & CreateCommentOptions {
Type : CommentTypeMilestone ,
Doer : doer ,
Repo : repo ,
Issue : issue ,
OldMilestoneID : oldMilestoneID ,
MilestoneID : milestoneID ,
} )
}
2018-05-09 19:29:04 +03:00
func createAssigneeComment ( e * xorm . Session , doer * User , repo * Repository , issue * Issue , assigneeID int64 , removedAssignee bool ) ( * Comment , error ) {
2017-02-03 18:09:10 +03:00
return createComment ( e , & CreateCommentOptions {
2018-05-09 19:29:04 +03:00
Type : CommentTypeAssignees ,
Doer : doer ,
Repo : repo ,
Issue : issue ,
RemovedAssignee : removedAssignee ,
AssigneeID : assigneeID ,
2017-02-03 18:09:10 +03:00
} )
}
2018-05-01 22:05:28 +03:00
func createDeadlineComment ( e * xorm . Session , doer * User , issue * Issue , newDeadlineUnix util . TimeStamp ) ( * Comment , error ) {
var content string
var commentType CommentType
// newDeadline = 0 means deleting
if newDeadlineUnix == 0 {
commentType = CommentTypeRemovedDeadline
content = issue . DeadlineUnix . Format ( "2006-01-02" )
} else if issue . DeadlineUnix == 0 {
// Check if the new date was added or modified
// If the actual deadline is 0 => deadline added
commentType = CommentTypeAddedDeadline
content = newDeadlineUnix . Format ( "2006-01-02" )
} else { // Otherwise modified
commentType = CommentTypeModifiedDeadline
content = newDeadlineUnix . Format ( "2006-01-02" ) + "|" + issue . DeadlineUnix . Format ( "2006-01-02" )
}
2019-01-14 05:29:58 +03:00
if err := issue . loadRepo ( e ) ; err != nil {
2018-12-27 18:02:43 +03:00
return nil , err
}
2018-05-01 22:05:28 +03:00
return createComment ( e , & CreateCommentOptions {
Type : commentType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
Content : content ,
} )
}
2017-02-05 17:36:00 +03:00
func createChangeTitleComment ( e * xorm . Session , doer * User , repo * Repository , issue * Issue , oldTitle , newTitle string ) ( * Comment , error ) {
return createComment ( e , & CreateCommentOptions {
Type : CommentTypeChangeTitle ,
Doer : doer ,
Repo : repo ,
Issue : issue ,
OldTitle : oldTitle ,
NewTitle : newTitle ,
} )
}
2017-02-11 07:00:29 +03:00
func createDeleteBranchComment ( e * xorm . Session , doer * User , repo * Repository , issue * Issue , branchName string ) ( * Comment , error ) {
return createComment ( e , & CreateCommentOptions {
Type : CommentTypeDeleteBranch ,
Doer : doer ,
Repo : repo ,
Issue : issue ,
CommitSHA : branchName ,
} )
}
2018-07-18 00:23:58 +03:00
// Creates issue dependency comment
func createIssueDependencyComment ( e * xorm . Session , doer * User , issue * Issue , dependentIssue * Issue , add bool ) ( err error ) {
cType := CommentTypeAddDependency
if ! add {
cType = CommentTypeRemoveDependency
}
2019-01-27 14:31:40 +03:00
if err = issue . loadRepo ( e ) ; err != nil {
return
}
2018-07-18 00:23:58 +03:00
// Make two comments, one in each issue
_ , err = createComment ( e , & CreateCommentOptions {
Type : cType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
DependentIssueID : dependentIssue . ID ,
} )
if err != nil {
return
}
_ , err = createComment ( e , & CreateCommentOptions {
Type : cType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : dependentIssue ,
DependentIssueID : issue . ID ,
} )
if err != nil {
return
}
return
}
2016-11-28 16:33:09 +03:00
// CreateCommentOptions defines options for creating comment
2016-03-05 20:58:51 +03:00
type CreateCommentOptions struct {
Type CommentType
Doer * User
Repo * Repository
Issue * Issue
2017-01-30 15:46:45 +03:00
Label * Label
2016-03-05 20:58:51 +03:00
2018-07-18 00:23:58 +03:00
DependentIssueID int64
OldMilestoneID int64
MilestoneID int64
AssigneeID int64
RemovedAssignee bool
OldTitle string
NewTitle string
CommitID int64
CommitSHA string
2018-08-06 07:43:22 +03:00
Patch string
2018-07-18 00:23:58 +03:00
LineNum int64
2018-08-06 07:43:22 +03:00
TreePath string
ReviewID int64
2018-07-18 00:23:58 +03:00
Content string
Attachments [ ] string // UUIDs of attachments
2016-03-05 20:58:51 +03:00
}
// CreateComment creates comment of issue or commit.
func CreateComment ( opts * CreateCommentOptions ) ( comment * Comment , err error ) {
sess := x . NewSession ( )
2017-06-21 03:57:05 +03:00
defer sess . Close ( )
2016-03-05 20:58:51 +03:00
if err = sess . Begin ( ) ; err != nil {
return nil , err
}
comment , err = createComment ( sess , opts )
if err != nil {
return nil , err
}
2017-09-16 23:16:21 +03:00
if err = sess . Commit ( ) ; err != nil {
return nil , err
}
return comment , nil
2016-03-05 20:58:51 +03:00
}
// CreateIssueComment creates a plain issue comment.
func CreateIssueComment ( doer * User , repo * Repository , issue * Issue , content string , attachments [ ] string ) ( * Comment , error ) {
2018-05-16 17:01:55 +03:00
comment , err := CreateComment ( & CreateCommentOptions {
2016-11-07 19:30:04 +03:00
Type : CommentTypeComment ,
2016-03-05 20:58:51 +03:00
Doer : doer ,
Repo : repo ,
Issue : issue ,
Content : content ,
Attachments : attachments ,
} )
2018-05-16 17:01:55 +03:00
if err != nil {
return nil , fmt . Errorf ( "CreateComment: %v" , err )
}
2018-11-28 14:26:14 +03:00
mode , _ := AccessLevel ( doer , repo )
2018-05-16 17:01:55 +03:00
if err = PrepareWebhooks ( repo , HookEventIssueComment , & api . IssueCommentPayload {
Action : api . HookIssueCommentCreated ,
Issue : issue . APIFormat ( ) ,
Comment : comment . APIFormat ( ) ,
Repository : repo . APIFormat ( mode ) ,
Sender : doer . APIFormat ( ) ,
} ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "PrepareWebhooks [comment_id: %d]: %v" , comment . ID , err )
2018-05-21 05:28:29 +03:00
} else {
go HookQueue . Add ( repo . ID )
2018-05-16 17:01:55 +03:00
}
return comment , nil
2016-03-05 20:58:51 +03:00
}
2018-08-06 07:43:22 +03:00
// CreateCodeComment creates a plain code comment at the specified line / path
func CreateCodeComment ( doer * User , repo * Repository , issue * Issue , content , treePath string , line , reviewID int64 ) ( * Comment , error ) {
var commitID , patch string
pr , err := GetPullRequestByIssueID ( issue . ID )
if err != nil {
return nil , fmt . Errorf ( "GetPullRequestByIssueID: %v" , err )
}
if err := pr . GetBaseRepo ( ) ; err != nil {
return nil , fmt . Errorf ( "GetHeadRepo: %v" , err )
}
gitRepo , err := git . OpenRepository ( pr . BaseRepo . RepoPath ( ) )
if err != nil {
return nil , fmt . Errorf ( "OpenRepository: %v" , err )
}
2018-09-17 17:59:49 +03:00
2018-08-06 07:43:22 +03:00
// FIXME validate treePath
// Get latest commit referencing the commented line
2018-09-17 17:59:49 +03:00
// No need for get commit for base branch changes
if line > 0 {
commit , err := gitRepo . LineBlame ( pr . GetGitRefName ( ) , gitRepo . Path , treePath , uint ( line ) )
2019-02-01 04:37:20 +03:00
if err == nil {
commitID = commit . ID . String ( )
2019-06-12 22:41:28 +03:00
} else if ! strings . Contains ( err . Error ( ) , "exit status 128 - fatal: no such path" ) {
2018-09-17 17:59:49 +03:00
return nil , fmt . Errorf ( "LineBlame[%s, %s, %s, %d]: %v" , pr . GetGitRefName ( ) , gitRepo . Path , treePath , line , err )
}
2018-08-06 07:43:22 +03:00
}
2018-09-17 17:59:49 +03:00
2018-08-06 07:43:22 +03:00
// Only fetch diff if comment is review comment
if reviewID != 0 {
headCommitID , err := gitRepo . GetRefCommitID ( pr . GetGitRefName ( ) )
if err != nil {
return nil , fmt . Errorf ( "GetRefCommitID[%s]: %v" , pr . GetGitRefName ( ) , err )
}
patchBuf := new ( bytes . Buffer )
if err := GetRawDiffForFile ( gitRepo . Path , pr . MergeBase , headCommitID , RawDiffNormal , treePath , patchBuf ) ; err != nil {
return nil , fmt . Errorf ( "GetRawDiffForLine[%s, %s, %s, %s]: %v" , err , gitRepo . Path , pr . MergeBase , headCommitID , treePath )
}
patch = CutDiffAroundLine ( strings . NewReader ( patchBuf . String ( ) ) , int64 ( ( & Comment { Line : line } ) . UnsignedLine ( ) ) , line < 0 , setting . UI . CodeCommentLines )
}
return CreateComment ( & CreateCommentOptions {
Type : CommentTypeCode ,
Doer : doer ,
Repo : repo ,
Issue : issue ,
Content : content ,
LineNum : line ,
TreePath : treePath ,
CommitSHA : commitID ,
ReviewID : reviewID ,
Patch : patch ,
} )
}
2016-03-05 20:58:51 +03:00
// CreateRefComment creates a commit reference comment to issue.
func CreateRefComment ( doer * User , repo * Repository , issue * Issue , content , commitSHA string ) error {
if len ( commitSHA ) == 0 {
return fmt . Errorf ( "cannot create reference with empty commit SHA" )
}
// Check if same reference from same commit has already existed.
has , err := x . Get ( & Comment {
2016-11-07 19:35:34 +03:00
Type : CommentTypeCommitRef ,
2016-03-05 20:58:51 +03:00
IssueID : issue . ID ,
CommitSHA : commitSHA ,
} )
if err != nil {
return fmt . Errorf ( "check reference comment: %v" , err )
} else if has {
return nil
}
_ , err = CreateComment ( & CreateCommentOptions {
2016-11-07 19:35:34 +03:00
Type : CommentTypeCommitRef ,
2016-03-05 20:58:51 +03:00
Doer : doer ,
Repo : repo ,
Issue : issue ,
CommitSHA : commitSHA ,
Content : content ,
} )
return err
}
// GetCommentByID returns the comment by given ID.
func GetCommentByID ( id int64 ) ( * Comment , error ) {
c := new ( Comment )
2017-10-05 07:43:04 +03:00
has , err := x . ID ( id ) . Get ( c )
2016-03-05 20:58:51 +03:00
if err != nil {
return nil , err
} else if ! has {
2016-08-26 23:40:53 +03:00
return nil , ErrCommentNotExist { id , 0 }
2016-03-05 20:58:51 +03:00
}
return c , nil
}
2017-06-21 04:00:44 +03:00
// FindCommentsOptions describes the conditions to Find comments
type FindCommentsOptions struct {
2018-08-06 07:43:22 +03:00
RepoID int64
IssueID int64
ReviewID int64
Since int64
Type CommentType
2017-06-21 04:00:44 +03:00
}
func ( opts * FindCommentsOptions ) toConds ( ) builder . Cond {
var cond = builder . NewCond ( )
if opts . RepoID > 0 {
cond = cond . And ( builder . Eq { "issue.repo_id" : opts . RepoID } )
2016-08-26 23:40:53 +03:00
}
2017-06-21 04:00:44 +03:00
if opts . IssueID > 0 {
cond = cond . And ( builder . Eq { "comment.issue_id" : opts . IssueID } )
}
2018-08-06 07:43:22 +03:00
if opts . ReviewID > 0 {
cond = cond . And ( builder . Eq { "comment.review_id" : opts . ReviewID } )
}
2017-06-21 04:00:44 +03:00
if opts . Since > 0 {
cond = cond . And ( builder . Gte { "comment.updated_unix" : opts . Since } )
}
if opts . Type != CommentTypeUnknown {
cond = cond . And ( builder . Eq { "comment.type" : opts . Type } )
}
return cond
2016-08-26 23:40:53 +03:00
}
2017-06-21 04:00:44 +03:00
func findComments ( e Engine , opts FindCommentsOptions ) ( [ ] * Comment , error ) {
2016-12-22 11:29:26 +03:00
comments := make ( [ ] * Comment , 0 , 10 )
2017-06-21 04:00:44 +03:00
sess := e . Where ( opts . toConds ( ) )
if opts . RepoID > 0 {
sess . Join ( "INNER" , "issue" , "issue.id = comment.issue_id" )
2016-12-22 11:29:26 +03:00
}
2017-06-21 04:00:44 +03:00
return comments , sess .
Asc ( "comment.created_unix" ) .
2017-11-03 06:11:42 +03:00
Asc ( "comment.id" ) .
2017-06-21 04:00:44 +03:00
Find ( & comments )
2016-12-22 11:29:26 +03:00
}
2017-06-21 04:00:44 +03:00
// FindComments returns all comments according options
func FindComments ( opts FindCommentsOptions ) ( [ ] * Comment , error ) {
return findComments ( x , opts )
2016-08-26 23:40:53 +03:00
}
2016-03-05 20:58:51 +03:00
// UpdateComment updates information of comment.
2018-05-16 17:01:55 +03:00
func UpdateComment ( doer * User , c * Comment , oldContent string ) error {
2017-10-05 07:43:04 +03:00
if _ , err := x . ID ( c . ID ) . AllCols ( ) . Update ( c ) ; err != nil {
2017-09-16 23:16:21 +03:00
return err
}
2018-05-16 17:01:55 +03:00
2018-12-13 18:55:43 +03:00
if err := c . LoadPoster ( ) ; err != nil {
return err
}
2018-05-16 17:01:55 +03:00
if err := c . LoadIssue ( ) ; err != nil {
return err
}
2019-02-19 17:39:39 +03:00
2018-05-16 17:01:55 +03:00
if err := c . Issue . LoadAttributes ( ) ; err != nil {
return err
}
2019-04-18 08:00:03 +03:00
if err := c . loadPoster ( x ) ; err != nil {
return err
}
2018-05-16 17:01:55 +03:00
2018-11-28 14:26:14 +03:00
mode , _ := AccessLevel ( doer , c . Issue . Repo )
2018-05-16 17:01:55 +03:00
if err := PrepareWebhooks ( c . Issue . Repo , HookEventIssueComment , & api . IssueCommentPayload {
Action : api . HookIssueCommentEdited ,
Issue : c . Issue . APIFormat ( ) ,
Comment : c . APIFormat ( ) ,
Changes : & api . ChangesPayload {
Body : & api . ChangesFromPayload {
From : oldContent ,
} ,
} ,
Repository : c . Issue . Repo . APIFormat ( mode ) ,
Sender : doer . APIFormat ( ) ,
} ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "PrepareWebhooks [comment_id: %d]: %v" , c . ID , err )
2018-05-21 05:28:29 +03:00
} else {
go HookQueue . Add ( c . Issue . Repo . ID )
2018-05-16 17:01:55 +03:00
}
2017-09-16 23:16:21 +03:00
return nil
2016-03-05 20:58:51 +03:00
}
2016-07-25 21:48:17 +03:00
2017-01-25 05:43:02 +03:00
// DeleteComment deletes the comment
2018-05-16 17:01:55 +03:00
func DeleteComment ( doer * User , comment * Comment ) error {
2016-07-25 21:48:17 +03:00
sess := x . NewSession ( )
2017-06-21 03:57:05 +03:00
defer sess . Close ( )
2017-01-25 05:43:02 +03:00
if err := sess . Begin ( ) ; err != nil {
2016-07-25 21:48:17 +03:00
return err
}
2017-01-25 17:54:52 +03:00
if _ , err := sess . Delete ( & Comment {
ID : comment . ID ,
} ) ; err != nil {
2016-07-25 21:48:17 +03:00
return err
}
2016-11-07 19:30:04 +03:00
if comment . Type == CommentTypeComment {
2017-01-25 05:43:02 +03:00
if _ , err := sess . Exec ( "UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?" , comment . IssueID ) ; err != nil {
2016-07-25 21:48:17 +03:00
return err
}
}
2017-07-04 04:30:41 +03:00
if _ , err := sess . Where ( "comment_id = ?" , comment . ID ) . Cols ( "is_deleted" ) . Update ( & Action { IsDeleted : true } ) ; err != nil {
return err
}
2016-07-25 21:48:17 +03:00
2017-09-16 23:16:21 +03:00
if err := sess . Commit ( ) ; err != nil {
return err
}
2019-04-18 08:00:03 +03:00
sess . Close ( )
2018-05-16 17:01:55 +03:00
2018-12-13 18:55:43 +03:00
if err := comment . LoadPoster ( ) ; err != nil {
return err
}
2018-05-16 17:01:55 +03:00
if err := comment . LoadIssue ( ) ; err != nil {
return err
}
2019-02-19 17:39:39 +03:00
2018-05-16 17:01:55 +03:00
if err := comment . Issue . LoadAttributes ( ) ; err != nil {
return err
}
2019-04-18 08:00:03 +03:00
if err := comment . loadPoster ( x ) ; err != nil {
return err
}
2018-05-16 17:01:55 +03:00
2018-11-28 14:26:14 +03:00
mode , _ := AccessLevel ( doer , comment . Issue . Repo )
2018-05-16 17:01:55 +03:00
if err := PrepareWebhooks ( comment . Issue . Repo , HookEventIssueComment , & api . IssueCommentPayload {
Action : api . HookIssueCommentDeleted ,
Issue : comment . Issue . APIFormat ( ) ,
Comment : comment . APIFormat ( ) ,
Repository : comment . Issue . Repo . APIFormat ( mode ) ,
Sender : doer . APIFormat ( ) ,
} ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "PrepareWebhooks [comment_id: %d]: %v" , comment . ID , err )
2018-05-21 05:28:29 +03:00
} else {
go HookQueue . Add ( comment . Issue . Repo . ID )
2018-05-16 17:01:55 +03:00
}
2017-09-16 23:16:21 +03:00
return nil
2016-07-25 21:48:17 +03:00
}
2018-08-06 07:43:22 +03:00
// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
type CodeComments map [ string ] map [ int64 ] [ ] * Comment
func fetchCodeComments ( e Engine , issue * Issue , currentUser * User ) ( CodeComments , error ) {
return fetchCodeCommentsByReview ( e , issue , currentUser , nil )
}
func fetchCodeCommentsByReview ( e Engine , issue * Issue , currentUser * User , review * Review ) ( CodeComments , error ) {
pathToLineToComment := make ( CodeComments )
if review == nil {
review = & Review { ID : 0 }
}
//Find comments
opts := FindCommentsOptions {
Type : CommentTypeCode ,
IssueID : issue . ID ,
ReviewID : review . ID ,
}
conds := opts . toConds ( )
if review . ID == 0 {
2018-10-05 18:49:30 +03:00
conds = conds . And ( builder . Eq { "invalidated" : false } )
2018-08-06 07:43:22 +03:00
}
var comments [ ] * Comment
if err := e . Where ( conds ) .
Asc ( "comment.created_unix" ) .
Asc ( "comment.id" ) .
Find ( & comments ) ; err != nil {
return nil , err
}
2018-12-13 18:55:43 +03:00
if err := CommentList ( comments ) . loadPosters ( e ) ; err != nil {
return nil , err
}
2018-08-06 07:43:22 +03:00
if err := issue . loadRepo ( e ) ; err != nil {
return nil , err
}
2019-04-18 08:00:03 +03:00
if err := CommentList ( comments ) . loadPosters ( e ) ; err != nil {
return nil , err
}
2018-08-06 07:43:22 +03:00
// Find all reviews by ReviewID
reviews := make ( map [ int64 ] * Review )
var 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
}
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 the review is set)
if review . ID == 0 {
if re . Type == ReviewTypePending &&
( currentUser == nil || currentUser . ID != re . ReviewerID ) {
continue
}
}
comment . Review = re
}
comment . RenderedContent = string ( markdown . Render ( [ ] byte ( comment . Content ) , issue . Repo . Link ( ) ,
issue . Repo . ComposeMetas ( ) ) )
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
}
// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
func FetchCodeComments ( issue * Issue , currentUser * User ) ( CodeComments , error ) {
return fetchCodeComments ( x , issue , currentUser )
}