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 (
2020-05-20 15:47:24 +03:00
"container/list"
"encoding/json"
2016-03-05 20:58:51 +03:00
"fmt"
2020-06-14 16:55:20 +03:00
"regexp"
2020-06-18 17:07:09 +03:00
"strconv"
2016-03-05 20:58:51 +03:00
"strings"
2020-06-18 17:07:09 +03:00
"unicode/utf8"
2016-03-05 20:58:51 +03:00
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/modules/git"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/log"
2018-08-06 07:43:22 +03:00
"code.gitea.io/gitea/modules/markup/markdown"
2019-10-14 01:29:10 +03:00
"code.gitea.io/gitea/modules/references"
2019-10-14 09:10:42 +03:00
"code.gitea.io/gitea/modules/structs"
2019-08-15 17:46:21 +03:00
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
2019-08-23 19:40:30 +03:00
"github.com/unknwon/com"
2019-06-23 18:22:43 +03:00
"xorm.io/builder"
2019-10-17 12:26:49 +03:00
"xorm.io/xorm"
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
2019-12-16 09:20:25 +03:00
// Change pull request's target branch
CommentTypeChangeTargetBranch
2019-12-27 23:30:58 +03:00
// Delete time manual for time tracking
CommentTypeDeleteTimeManual
2020-04-06 19:33:34 +03:00
// add or remove Request from one
CommentTypeReviewRequest
2020-04-14 04:06:23 +03:00
// merge pull request
CommentTypeMergePull
2020-05-20 15:47:24 +03:00
// push to PR head branch
CommentTypePullPush
2020-08-17 06:07:38 +03:00
// Project changed
CommentTypeProject
// Project board changed
CommentTypeProjectBoard
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 {
2019-08-05 17:29:40 +03:00
ID int64 ` xorm:"pk autoincr" `
2019-12-27 23:30:58 +03:00
Type CommentType ` xorm:"INDEX" `
2019-08-05 17:29:40 +03:00
PosterID int64 ` xorm:"INDEX" `
Poster * User ` xorm:"-" `
2019-07-08 05:14:12 +03:00
OriginalAuthor string
OriginalAuthorID int64
2018-07-18 00:23:58 +03:00
IssueID int64 ` xorm:"INDEX" `
Issue * Issue ` xorm:"-" `
LabelID int64
Label * Label ` xorm:"-" `
2020-08-17 06:07:38 +03:00
OldProjectID int64
ProjectID int64
OldProject * Project ` xorm:"-" `
Project * Project ` xorm:"-" `
2018-07-18 00:23:58 +03:00
OldMilestoneID int64
MilestoneID int64
OldMilestone * Milestone ` xorm:"-" `
Milestone * Milestone ` xorm:"-" `
AssigneeID int64
RemovedAssignee bool
Assignee * User ` xorm:"-" `
2020-04-18 16:50:25 +03:00
ResolveDoerID int64
ResolveDoer * User ` xorm:"-" `
2018-07-18 00:23:58 +03:00
OldTitle string
NewTitle string
2019-12-16 09:20:25 +03:00
OldRef string
NewRef string
2018-07-18 00:23:58 +03:00
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
2020-06-18 17:07:09 +03:00
Patch string ` xorm:"-" `
PatchQuoted string ` xorm:"TEXT patch" `
2018-08-06 07:43:22 +03:00
2019-08-15 17:46:21 +03:00
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . 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:"-" `
2019-08-05 17:29:40 +03:00
ReviewID int64 ` xorm:"index" `
2018-08-06 07:43:22 +03:00
Invalidated bool
2019-09-20 08:45:38 +03:00
// Reference an issue or pull from another comment, issue or PR
// All information is about the origin of the reference
2019-10-14 01:29:10 +03:00
RefRepoID int64 ` xorm:"index" ` // Repo where the referencing
RefIssueID int64 ` xorm:"index" `
RefCommentID int64 ` xorm:"index" ` // 0 if origin is Issue title or content (or PR's)
RefAction references . XRefAction ` xorm:"SMALLINT" ` // What hapens if RefIssueID resolves
2019-09-20 08:45:38 +03:00
RefIsPull bool
RefRepo * Repository ` xorm:"-" `
RefIssue * Issue ` xorm:"-" `
RefComment * Comment ` xorm:"-" `
2020-05-20 15:47:24 +03:00
Commits * list . List ` xorm:"-" `
OldCommit string ` xorm:"-" `
NewCommit string ` xorm:"-" `
CommitsNum int64 ` xorm:"-" `
IsForcePush bool ` xorm:"-" `
}
// PushActionContent is content of push pull comment
type PushActionContent struct {
IsForcePush bool ` json:"is_force_push" `
CommitIDs [ ] string ` json:"commit_ids" `
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 ) {
2019-09-20 08:45:38 +03:00
return c . loadIssue ( x )
}
func ( c * Comment ) loadIssue ( e Engine ) ( err error ) {
2018-05-16 17:01:55 +03:00
if c . Issue != nil {
return nil
}
2019-09-20 08:45:38 +03:00
c . Issue , err = getIssueByID ( e , c . IssueID )
2018-05-16 17:01:55 +03:00
return
}
2020-06-18 17:07:09 +03:00
// BeforeInsert will be invoked by XORM before inserting a record
func ( c * Comment ) BeforeInsert ( ) {
c . PatchQuoted = c . Patch
if ! utf8 . ValidString ( c . Patch ) {
c . PatchQuoted = strconv . Quote ( c . Patch )
}
}
// BeforeUpdate will be invoked by XORM before updating a record
func ( c * Comment ) BeforeUpdate ( ) {
c . PatchQuoted = c . Patch
if ! utf8 . ValidString ( c . Patch ) {
c . PatchQuoted = strconv . Quote ( c . Patch )
}
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func ( c * Comment ) AfterLoad ( session * xorm . Session ) {
c . Patch = c . PatchQuoted
if len ( c . PatchQuoted ) > 0 && c . PatchQuoted [ 0 ] == '"' {
unquoted , err := strconv . Unquote ( c . PatchQuoted )
if err == nil {
c . Patch = unquoted
}
}
}
2019-04-18 08:00:03 +03:00
func ( c * Comment ) loadPoster ( e Engine ) ( err error ) {
2019-09-24 20:39:50 +03:00
if c . PosterID <= 0 || c . Poster != nil {
2019-04-18 08:00:03 +03:00
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
}
2020-01-09 14:56:32 +03:00
// APIURL formats a API-string to the issue-comment
func ( c * Comment ) APIURL ( ) string {
err := c . LoadIssue ( )
if err != nil { // Silently dropping errors :unamused:
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
return ""
}
err = c . Issue . loadRepo ( x )
if err != nil { // Silently dropping errors :unamused:
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
return ""
}
2020-01-14 18:37:19 +03:00
return fmt . Sprintf ( "%s/issues/comments/%d" , c . Issue . Repo . APIURL ( ) , c . ID )
2020-01-09 14:56:32 +03:00
}
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
}
2020-08-17 06:07:38 +03:00
// LoadProject if comment.Type is CommentTypeProject, then load project.
func ( c * Comment ) LoadProject ( ) error {
if c . OldProjectID > 0 {
var oldProject Project
has , err := x . ID ( c . OldProjectID ) . Get ( & oldProject )
if err != nil {
return err
} else if has {
c . OldProject = & oldProject
}
}
if c . ProjectID > 0 {
var project Project
has , err := x . ID ( c . ProjectID ) . Get ( & project )
if err != nil {
return err
} else if has {
c . Project = & project
}
}
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 {
2019-09-24 20:39:50 +03:00
return c . loadPoster ( x )
2018-12-13 18:55:43 +03:00
}
// 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
}
2019-10-15 15:19:32 +03:00
// UpdateAttachments update attachments by UUIDs for the comment
func ( c * Comment ) UpdateAttachments ( uuids [ ] string ) error {
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
attachments , err := getAttachmentsByUUIDs ( sess , uuids )
if err != nil {
return fmt . Errorf ( "getAttachmentsByUUIDs [uuids: %v]: %v" , uuids , err )
}
for i := 0 ; i < len ( attachments ) ; i ++ {
attachments [ i ] . IssueID = c . IssueID
attachments [ i ] . CommentID = c . ID
if err := updateAttachment ( sess , attachments [ i ] ) ; err != nil {
return fmt . Errorf ( "update attachment [id: %d]: %v" , attachments [ i ] . ID , err )
}
}
return sess . Commit ( )
}
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
}
2020-04-18 16:50:25 +03:00
// LoadResolveDoer if comment.Type is CommentTypeCode and ResolveDoerID not zero, then load resolveDoer
func ( c * Comment ) LoadResolveDoer ( ) ( err error ) {
if c . ResolveDoerID == 0 || c . Type != CommentTypeCode {
return nil
}
c . ResolveDoer , err = getUserByID ( x , c . ResolveDoerID )
if err != nil {
if IsErrUserNotExist ( err ) {
c . ResolveDoer = NewGhostUser ( )
err = nil
}
}
return
}
// IsResolved check if an code comment is resolved
func ( c * Comment ) IsResolved ( ) bool {
return c . ResolveDoerID != 0 && c . Type == CommentTypeCode
}
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
}
2020-01-15 14:14:07 +03:00
func ( c * Comment ) loadReactions ( e Engine , repo * Repository ) ( err error ) {
2017-12-04 02:14:26 +03:00
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
2020-01-15 14:14:07 +03:00
if _ , err := c . Reactions . loadUsers ( e , repo ) ; err != nil {
2017-12-04 02:14:26 +03:00
return err
}
return nil
}
// LoadReactions loads comment reactions
2020-01-15 14:14:07 +03:00
func ( c * Comment ) LoadReactions ( repo * Repository ) error {
return c . loadReactions ( x , repo )
2017-12-04 02:14:26 +03:00
}
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 )
}
2020-06-14 16:55:20 +03:00
var notEnoughLines = regexp . MustCompile ( ` fatal: file .* has only \d+ lines? ` )
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 ( ) ) )
2020-06-14 16:55:20 +03:00
if err != nil && ( strings . Contains ( err . Error ( ) , "fatal: no such path" ) || notEnoughLines . MatchString ( err . Error ( ) ) ) {
2019-10-31 20:59:36 +03:00
c . Invalidated = true
return UpdateComment ( c , doer )
}
2018-08-06 07:43:22 +03:00
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
2019-09-24 20:39:50 +03:00
return UpdateComment ( c , doer )
2018-08-06 07:43:22 +03:00
}
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 )
}
// 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 ( ) )
}
2020-05-20 15:47:24 +03:00
// LoadPushCommits Load push commits
func ( c * Comment ) LoadPushCommits ( ) ( err error ) {
if c . Content == "" || c . Commits != nil || c . Type != CommentTypePullPush {
return nil
}
var data PushActionContent
err = json . Unmarshal ( [ ] byte ( c . Content ) , & data )
if err != nil {
return
}
c . IsForcePush = data . IsForcePush
if c . IsForcePush {
if len ( data . CommitIDs ) != 2 {
return nil
}
c . OldCommit = data . CommitIDs [ 0 ]
c . NewCommit = data . CommitIDs [ 1 ]
} else {
repoPath := c . Issue . Repo . RepoPath ( )
gitRepo , err := git . OpenRepository ( repoPath )
if err != nil {
return err
}
defer gitRepo . Close ( )
c . Commits = gitRepo . GetCommitsFromIDs ( data . CommitIDs )
c . CommitsNum = int64 ( c . Commits . Len ( ) )
if c . CommitsNum > 0 {
c . Commits = ValidateCommitsWithEmails ( c . Commits )
c . Commits = ParseCommitsWithSignature ( c . Commits , c . Issue . Repo )
c . Commits = ParseCommitsWithStatus ( c . Commits , c . Issue . Repo )
}
}
return err
}
2019-12-16 06:54:24 +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 ,
2020-08-17 06:07:38 +03:00
OldProjectID : opts . OldProjectID ,
ProjectID : opts . ProjectID ,
2018-07-18 00:23:58 +03:00
RemovedAssignee : opts . RemovedAssignee ,
AssigneeID : opts . AssigneeID ,
CommitID : opts . CommitID ,
CommitSHA : opts . CommitSHA ,
Line : opts . LineNum ,
Content : opts . Content ,
OldTitle : opts . OldTitle ,
NewTitle : opts . NewTitle ,
2019-12-16 09:20:25 +03:00
OldRef : opts . OldRef ,
NewRef : opts . NewRef ,
2018-07-18 00:23:58 +03:00
DependentIssueID : opts . DependentIssueID ,
2018-08-06 07:43:22 +03:00
TreePath : opts . TreePath ,
ReviewID : opts . ReviewID ,
Patch : opts . Patch ,
2019-09-20 08:45:38 +03:00
RefRepoID : opts . RefRepoID ,
RefIssueID : opts . RefIssueID ,
RefCommentID : opts . RefCommentID ,
RefAction : opts . RefAction ,
RefIsPull : opts . RefIsPull ,
2020-05-20 15:47:24 +03:00
IsForcePush : opts . IsForcePush ,
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
}
2019-11-06 16:39:29 +03:00
if err = updateCommentInfos ( e , opts , comment ) ; err != nil {
return nil , err
}
2019-11-19 02:43:03 +03:00
if err = comment . addCrossReferences ( e , opts . Doer , false ) ; err != nil {
2019-09-20 08:45:38 +03:00
return nil , err
}
2018-08-06 07:43:22 +03:00
return comment , nil
}
2019-11-06 16:39:29 +03:00
func updateCommentInfos ( e * xorm . Session , opts * CreateCommentOptions , comment * Comment ) ( err error ) {
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-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
2019-12-11 03:01:52 +03:00
attachments , err := getAttachmentsByUUIDs ( e , opts . Attachments )
if err != nil {
return fmt . Errorf ( "getAttachmentsByUUIDs [uuids: %v]: %v" , opts . Attachments , err )
2016-03-05 20:58:51 +03:00
}
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
}
}
2019-11-06 16:39:29 +03:00
case CommentTypeReopen , CommentTypeClose :
if err = opts . Issue . updateClosedNum ( e ) ; err != nil {
return err
}
}
// update the issue's updated_unix column
return updateIssueCols ( e , opts . Issue , "updated_unix" )
}
2016-03-05 20:58:51 +03:00
2019-08-15 17:46:21 +03:00
func createDeadlineComment ( e * xorm . Session , doer * User , issue * Issue , newDeadlineUnix timeutil . TimeStamp ) ( * Comment , error ) {
2018-05-01 22:05:28 +03:00
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
}
2019-12-01 05:44:39 +03:00
var opts = & CreateCommentOptions {
2018-05-01 22:05:28 +03:00
Type : commentType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
Content : content ,
2019-12-01 05:44:39 +03:00
}
2019-12-16 06:54:24 +03:00
comment , err := createComment ( e , opts )
2019-12-01 05:44:39 +03:00
if err != nil {
return nil , err
}
2019-12-02 17:43:39 +03:00
return comment , nil
2018-05-01 22:05:28 +03:00
}
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
2019-12-01 05:44:39 +03:00
var opts = & CreateCommentOptions {
2018-07-18 00:23:58 +03:00
Type : cType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
DependentIssueID : dependentIssue . ID ,
2019-12-01 05:44:39 +03:00
}
2019-12-16 06:54:24 +03:00
if _ , err = createComment ( e , opts ) ; err != nil {
2018-07-18 00:23:58 +03:00
return
}
2019-12-01 05:44:39 +03:00
opts = & CreateCommentOptions {
2018-07-18 00:23:58 +03:00
Type : cType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : dependentIssue ,
DependentIssueID : issue . ID ,
2019-12-01 05:44:39 +03:00
}
2019-12-16 06:54:24 +03:00
_ , err = createComment ( e , opts )
2018-07-18 00:23:58 +03:00
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
2020-08-17 06:07:38 +03:00
OldProjectID int64
ProjectID int64
2018-07-18 00:23:58 +03:00
AssigneeID int64
RemovedAssignee bool
OldTitle string
NewTitle string
2019-12-16 09:20:25 +03:00
OldRef string
NewRef string
2018-07-18 00:23:58 +03:00
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
2019-09-20 08:45:38 +03:00
RefRepoID int64
RefIssueID int64
RefCommentID int64
2019-10-14 01:29:10 +03:00
RefAction references . XRefAction
2019-09-20 08:45:38 +03:00
RefIsPull bool
2020-05-20 15:47:24 +03:00
IsForcePush bool
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
}
2019-12-16 06:54:24 +03:00
comment , err = createComment ( sess , opts )
2016-03-05 20:58:51 +03:00
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
}
// 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 ) {
2020-02-28 02:10:27 +03:00
return getCommentByID ( x , id )
}
func getCommentByID ( e Engine , id int64 ) ( * Comment , error ) {
2016-03-05 20:58:51 +03:00
c := new ( Comment )
2020-02-28 02:10:27 +03:00
has , err := e . 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 {
2020-01-24 22:00:29 +03:00
ListOptions
2018-08-06 07:43:22 +03:00
RepoID int64
IssueID int64
ReviewID int64
Since int64
2020-01-13 19:02:24 +03:00
Before int64
2018-08-06 07:43:22 +03:00
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 } )
}
2020-01-13 19:02:24 +03:00
if opts . Before > 0 {
cond = cond . And ( builder . Lte { "comment.updated_unix" : opts . Before } )
}
2017-06-21 04:00:44 +03:00
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
}
2020-01-24 22:00:29 +03:00
if opts . Page != 0 {
sess = opts . setSessionPagination ( sess )
}
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.
2019-09-24 20:39:50 +03:00
func UpdateComment ( c * Comment , doer * User ) error {
2019-09-20 08:45:38 +03:00
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
2017-09-16 23:16:21 +03:00
return err
}
2018-05-16 17:01:55 +03:00
2019-09-20 08:45:38 +03:00
if _ , err := sess . ID ( c . ID ) . AllCols ( ) . Update ( c ) ; err != nil {
2018-12-13 18:55:43 +03:00
return err
}
2019-09-20 08:45:38 +03:00
if err := c . loadIssue ( sess ) ; err != nil {
2018-05-16 17:01:55 +03:00
return err
}
2019-11-19 02:43:03 +03:00
if err := c . addCrossReferences ( sess , doer , true ) ; err != nil {
2019-09-20 08:45:38 +03:00
return err
}
if err := sess . Commit ( ) ; err != nil {
return fmt . Errorf ( "Commit: %v" , err )
}
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
2019-09-24 20:39:50 +03:00
func DeleteComment ( comment * Comment , doer * User ) 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
2019-09-20 08:45:38 +03:00
if err := comment . neuterCrossReferences ( sess ) ; err != nil {
return err
}
2019-09-24 20:39:50 +03:00
return sess . Commit ( )
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
}
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
}
2020-04-18 16:50:25 +03:00
2018-08-06 07:43:22 +03:00
for _ , comment := range comments {
2020-04-18 16:50:25 +03:00
if err := comment . LoadResolveDoer ( ) ; err != nil {
return nil , err
}
2018-08-06 07:43:22 +03:00
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 )
}
2019-10-14 09:10:42 +03:00
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
2019-10-17 05:06:28 +03:00
func UpdateCommentsMigrationsByType ( tp structs . GitServiceType , originalAuthorID string , posterID int64 ) error {
2019-10-14 09:10:42 +03:00
_ , err := x . Table ( "comment" ) .
Where ( builder . In ( "issue_id" ,
builder . Select ( "issue.id" ) .
From ( "issue" ) .
InnerJoin ( "repository" , "issue.repo_id = repository.id" ) .
Where ( builder . Eq {
"repository.original_service_type" : tp ,
} ) ,
) ) .
And ( "comment.original_author_id = ?" , originalAuthorID ) .
Update ( map [ string ] interface { } {
"poster_id" : posterID ,
"original_author" : "" ,
"original_author_id" : 0 ,
} )
return err
}
2020-05-20 15:47:24 +03:00
// CreatePushPullComment create push code to pull base commend
func CreatePushPullComment ( pusher * User , pr * PullRequest , oldCommitID , newCommitID string ) ( comment * Comment , err error ) {
if pr . HasMerged || oldCommitID == "" || newCommitID == "" {
return nil , nil
}
ops := & CreateCommentOptions {
Type : CommentTypePullPush ,
Doer : pusher ,
Repo : pr . BaseRepo ,
}
var data PushActionContent
data . CommitIDs , data . IsForcePush , err = getCommitIDsFromRepo ( pr . BaseRepo , oldCommitID , newCommitID , pr . BaseBranch )
if err != nil {
return nil , err
}
ops . Issue = pr . Issue
dataJSON , err := json . Marshal ( data )
if err != nil {
return nil , err
}
ops . Content = string ( dataJSON )
comment , err = CreateComment ( ops )
return
}
// getCommitsFromRepo get commit IDs from repo in betwern oldCommitID and newCommitID
// isForcePush will be true if oldCommit isn't on the branch
// Commit on baseBranch will skip
func getCommitIDsFromRepo ( repo * Repository , oldCommitID , newCommitID , baseBranch string ) ( commitIDs [ ] string , isForcePush bool , err error ) {
repoPath := repo . RepoPath ( )
gitRepo , err := git . OpenRepository ( repoPath )
if err != nil {
return nil , false , err
}
defer gitRepo . Close ( )
oldCommit , err := gitRepo . GetCommit ( oldCommitID )
if err != nil {
return nil , false , err
}
2020-06-24 22:40:52 +03:00
if err = oldCommit . LoadBranchName ( ) ; err != nil {
2020-05-20 15:47:24 +03:00
return nil , false , err
}
2020-06-24 22:40:52 +03:00
if len ( oldCommit . Branch ) == 0 {
2020-05-20 15:47:24 +03:00
commitIDs = make ( [ ] string , 2 )
commitIDs [ 0 ] = oldCommitID
commitIDs [ 1 ] = newCommitID
return commitIDs , true , err
}
newCommit , err := gitRepo . GetCommit ( newCommitID )
if err != nil {
return nil , false , err
}
2020-06-24 22:40:52 +03:00
var (
commits * list . List
commitChecks map [ string ] commitBranchCheckItem
)
2020-05-20 15:47:24 +03:00
commits , err = newCommit . CommitsBeforeUntil ( oldCommitID )
if err != nil {
return nil , false , err
}
commitIDs = make ( [ ] string , 0 , commits . Len ( ) )
2020-06-24 22:40:52 +03:00
commitChecks = make ( map [ string ] commitBranchCheckItem )
2020-05-20 15:47:24 +03:00
2020-06-24 22:40:52 +03:00
for e := commits . Front ( ) ; e != nil ; e = e . Next ( ) {
commitChecks [ e . Value . ( * git . Commit ) . ID . String ( ) ] = commitBranchCheckItem {
Commit : e . Value . ( * git . Commit ) ,
Checked : false ,
2020-05-20 15:47:24 +03:00
}
2020-06-24 22:40:52 +03:00
}
2020-05-20 15:47:24 +03:00
2020-06-24 22:40:52 +03:00
if err = commitBranchCheck ( gitRepo , newCommit , oldCommitID , baseBranch , commitChecks ) ; err != nil {
return
}
for e := commits . Back ( ) ; e != nil ; e = e . Prev ( ) {
commitID := e . Value . ( * git . Commit ) . ID . String ( )
if item , ok := commitChecks [ commitID ] ; ok && item . Checked {
commitIDs = append ( commitIDs , commitID )
2020-05-20 15:47:24 +03:00
}
}
return
}
2020-06-24 22:40:52 +03:00
type commitBranchCheckItem struct {
Commit * git . Commit
Checked bool
}
func commitBranchCheck ( gitRepo * git . Repository , startCommit * git . Commit , endCommitID , baseBranch string , commitList map [ string ] commitBranchCheckItem ) ( err error ) {
var (
item commitBranchCheckItem
ok bool
listItem * list . Element
tmp string
)
if startCommit . ID . String ( ) == endCommitID {
return
}
checkStack := list . New ( )
checkStack . PushBack ( startCommit . ID . String ( ) )
listItem = checkStack . Back ( )
for listItem != nil {
tmp = listItem . Value . ( string )
checkStack . Remove ( listItem )
if item , ok = commitList [ tmp ] ; ! ok {
listItem = checkStack . Back ( )
continue
}
if item . Commit . ID . String ( ) == endCommitID {
listItem = checkStack . Back ( )
continue
}
if err = item . Commit . LoadBranchName ( ) ; err != nil {
return
}
if item . Commit . Branch == baseBranch {
listItem = checkStack . Back ( )
continue
}
if item . Checked {
listItem = checkStack . Back ( )
continue
}
item . Checked = true
commitList [ tmp ] = item
parentNum := item . Commit . ParentCount ( )
for i := 0 ; i < parentNum ; i ++ {
var parentCommit * git . Commit
parentCommit , err = item . Commit . Parent ( i )
if err != nil {
return
}
checkStack . PushBack ( parentCommit . ID . String ( ) )
}
listItem = checkStack . Back ( )
}
return nil
}