2018-08-06 04:43:22 +00:00
// Copyright 2018 The Gitea Authors.
// Copyright 2016 The Gogs Authors.
// All rights reserved.
2016-03-05 17:58:51 +00:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2022-06-13 09:37:59 +00:00
package issues
2016-03-05 17:58:51 +00:00
import (
2021-11-19 13:39:57 +00:00
"context"
2016-03-05 17:58:51 +00:00
"fmt"
2020-06-18 14:07:09 +00:00
"strconv"
"unicode/utf8"
2016-03-05 17:58:51 +00:00
2021-09-19 11:49:59 +00:00
"code.gitea.io/gitea/models/db"
2022-06-12 15:51:54 +00:00
git_model "code.gitea.io/gitea/models/git"
2022-03-29 06:29:02 +00:00
"code.gitea.io/gitea/models/organization"
2022-03-29 14:16:31 +00:00
project_model "code.gitea.io/gitea/models/project"
2021-11-19 13:39:57 +00:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 09:49:20 +00:00
user_model "code.gitea.io/gitea/models/user"
2019-03-27 09:33:00 +00:00
"code.gitea.io/gitea/modules/git"
2021-07-24 16:03:58 +00:00
"code.gitea.io/gitea/modules/json"
2019-08-15 14:46:21 +00:00
"code.gitea.io/gitea/modules/log"
2019-10-13 22:29:10 +00:00
"code.gitea.io/gitea/modules/references"
2019-10-14 06:10:42 +00:00
"code.gitea.io/gitea/modules/structs"
2019-08-15 14:46:21 +00:00
"code.gitea.io/gitea/modules/timeutil"
2022-10-18 05:50:37 +00:00
"code.gitea.io/gitea/modules/util"
2019-08-15 14:46:21 +00:00
2019-06-23 15:22:43 +00:00
"xorm.io/builder"
2019-10-17 09:26:49 +00:00
"xorm.io/xorm"
2016-03-05 17:58:51 +00:00
)
2022-06-13 09:37:59 +00:00
// ErrCommentNotExist represents a "CommentNotExist" kind of error.
type ErrCommentNotExist struct {
ID int64
IssueID int64
}
// IsErrCommentNotExist checks if an error is a ErrCommentNotExist.
func IsErrCommentNotExist ( err error ) bool {
_ , ok := err . ( ErrCommentNotExist )
return ok
}
func ( err ErrCommentNotExist ) Error ( ) string {
return fmt . Sprintf ( "comment does not exist [id: %d, issue_id: %d]" , err . ID , err . IssueID )
}
2022-10-18 05:50:37 +00:00
func ( err ErrCommentNotExist ) Unwrap ( ) error {
return util . ErrNotExist
}
2016-03-05 17:58:51 +00:00
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
type CommentType int
2017-06-21 01:00:44 +00:00
// define unknown comment type
const (
CommentTypeUnknown CommentType = - 1
)
2016-11-28 13:33:09 +00:00
// Enumerate all the comment types
2016-03-05 17:58:51 +00:00
const (
2021-02-10 03:57:30 +00:00
// 0 Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
2016-11-07 16:30:04 +00:00
CommentTypeComment CommentType = iota
2021-02-10 03:57:30 +00:00
CommentTypeReopen // 1
CommentTypeClose // 2
2016-03-05 17:58:51 +00:00
2021-02-10 03:57:30 +00:00
// 3 References.
2016-11-07 16:35:34 +00:00
CommentTypeIssueRef
2021-02-10 03:57:30 +00:00
// 4 Reference from a commit (not part of a pull request)
2016-11-07 16:35:34 +00:00
CommentTypeCommitRef
2021-02-10 03:57:30 +00:00
// 5 Reference from a comment
2016-11-07 20:58:22 +00:00
CommentTypeCommentRef
2021-02-10 03:57:30 +00:00
// 6 Reference from a pull request
2016-11-07 16:35:34 +00:00
CommentTypePullRef
2021-02-10 03:57:30 +00:00
// 7 Labels changed
2017-01-30 12:46:45 +00:00
CommentTypeLabel
2021-02-10 03:57:30 +00:00
// 8 Milestone changed
2017-02-01 02:36:08 +00:00
CommentTypeMilestone
2021-02-10 03:57:30 +00:00
// 9 Assignees changed
2017-02-03 15:09:10 +00:00
CommentTypeAssignees
2021-02-10 03:57:30 +00:00
// 10 Change Title
2017-02-05 14:36:00 +00:00
CommentTypeChangeTitle
2021-02-10 03:57:30 +00:00
// 11 Delete Branch
2017-02-11 04:00:29 +00:00
CommentTypeDeleteBranch
2021-02-10 03:57:30 +00:00
// 12 Start a stopwatch for time tracking
2017-09-12 06:48:13 +00:00
CommentTypeStartTracking
2021-02-10 03:57:30 +00:00
// 13 Stop a stopwatch for time tracking
2017-09-12 06:48:13 +00:00
CommentTypeStopTracking
2021-02-10 03:57:30 +00:00
// 14 Add time manual for time tracking
2017-09-12 06:48:13 +00:00
CommentTypeAddTimeManual
2021-02-10 03:57:30 +00:00
// 15 Cancel a stopwatch for time tracking
2017-09-12 06:48:13 +00:00
CommentTypeCancelTracking
2021-02-10 03:57:30 +00:00
// 16 Added a due date
2018-05-01 19:05:28 +00:00
CommentTypeAddedDeadline
2021-02-10 03:57:30 +00:00
// 17 Modified the due date
2018-05-01 19:05:28 +00:00
CommentTypeModifiedDeadline
2021-02-10 03:57:30 +00:00
// 18 Removed a due date
2018-05-01 19:05:28 +00:00
CommentTypeRemovedDeadline
2021-02-10 03:57:30 +00:00
// 19 Dependency added
2018-07-17 21:23:58 +00:00
CommentTypeAddDependency
2021-02-10 03:57:30 +00:00
// 20 Dependency removed
2018-07-17 21:23:58 +00:00
CommentTypeRemoveDependency
2021-02-10 03:57:30 +00:00
// 21 Comment a line of code
2018-08-06 04:43:22 +00:00
CommentTypeCode
2021-02-10 03:57:30 +00:00
// 22 Reviews a pull request by giving general feedback
2018-08-06 04:43:22 +00:00
CommentTypeReview
2021-02-10 03:57:30 +00:00
// 23 Lock an issue, giving only collaborators access
2019-02-18 20:55:04 +00:00
CommentTypeLock
2021-02-10 03:57:30 +00:00
// 24 Unlocks a previously locked issue
2019-02-18 20:55:04 +00:00
CommentTypeUnlock
2021-02-10 03:57:30 +00:00
// 25 Change pull request's target branch
2019-12-16 06:20:25 +00:00
CommentTypeChangeTargetBranch
2021-02-10 03:57:30 +00:00
// 26 Delete time manual for time tracking
2019-12-27 20:30:58 +00:00
CommentTypeDeleteTimeManual
2021-02-10 03:57:30 +00:00
// 27 add or remove Request from one
2020-04-06 16:33:34 +00:00
CommentTypeReviewRequest
2021-02-10 03:57:30 +00:00
// 28 merge pull request
2020-04-14 01:06:23 +00:00
CommentTypeMergePull
2021-02-10 03:57:30 +00:00
// 29 push to PR head branch
2022-01-21 17:59:26 +00:00
CommentTypePullRequestPush
2021-02-10 03:57:30 +00:00
// 30 Project changed
2020-08-17 03:07:38 +00:00
CommentTypeProject
2021-02-10 03:57:30 +00:00
// 31 Project board changed
2020-08-17 03:07:38 +00:00
CommentTypeProjectBoard
2021-11-19 09:54:31 +00:00
// 32 Dismiss Review
2021-02-11 17:32:25 +00:00
CommentTypeDismissReview
2021-11-19 09:54:31 +00:00
// 33 Change issue ref
CommentTypeChangeIssueRef
2022-05-07 17:05:52 +00:00
// 34 pr was scheduled to auto merge when checks succeed
CommentTypePRScheduledToAutoMerge
// 35 pr was un scheduled to auto merge when checks succeed
CommentTypePRUnScheduledToAutoMerge
2016-03-05 17:58:51 +00:00
)
2022-01-01 14:12:25 +00:00
var commentStrings = [ ] string {
"comment" ,
"reopen" ,
"close" ,
"issue_ref" ,
"commit_ref" ,
"comment_ref" ,
"pull_ref" ,
"label" ,
"milestone" ,
"assignees" ,
"change_title" ,
"delete_branch" ,
"start_tracking" ,
"stop_tracking" ,
"add_time_manual" ,
"cancel_tracking" ,
"added_deadline" ,
"modified_deadline" ,
"removed_deadline" ,
"add_dependency" ,
"remove_dependency" ,
"code" ,
"review" ,
"lock" ,
"unlock" ,
"change_target_branch" ,
"delete_time_manual" ,
"review_request" ,
"merge_pull" ,
"pull_push" ,
"project" ,
"project_board" ,
"dismiss_review" ,
"change_issue_ref" ,
2022-05-07 17:05:52 +00:00
"pull_scheduled_merge" ,
"pull_cancel_scheduled_merge" ,
2022-01-01 14:12:25 +00:00
}
func ( t CommentType ) String ( ) string {
return commentStrings [ t ]
}
2021-11-11 06:29:30 +00:00
// RoleDescriptor defines comment tag type
type RoleDescriptor int
2016-03-05 17:58:51 +00:00
2021-11-11 06:29:30 +00:00
// Enumerate all the role tags.
2016-03-05 17:58:51 +00:00
const (
2021-11-11 06:29:30 +00:00
RoleDescriptorNone RoleDescriptor = iota
RoleDescriptorPoster
RoleDescriptorWriter
RoleDescriptorOwner
2016-03-05 17:58:51 +00:00
)
2021-11-11 06:29:30 +00:00
// WithRole enable a specific tag on the RoleDescriptor.
func ( rd RoleDescriptor ) WithRole ( role RoleDescriptor ) RoleDescriptor {
2021-12-26 14:56:14 +00:00
return rd | ( 1 << role )
2021-11-11 06:29:30 +00:00
}
func stringToRoleDescriptor ( role string ) RoleDescriptor {
switch role {
case "Poster" :
return RoleDescriptorPoster
case "Writer" :
return RoleDescriptorWriter
case "Owner" :
return RoleDescriptorOwner
default :
return RoleDescriptorNone
}
}
// HasRole returns if a certain role is enabled on the RoleDescriptor.
func ( rd RoleDescriptor ) HasRole ( role string ) bool {
roleDescriptor := stringToRoleDescriptor ( role )
bitValue := rd & ( 1 << roleDescriptor )
return ( bitValue > 0 )
}
2016-03-05 17:58:51 +00:00
// Comment represents a comment in commit and issue page.
type Comment struct {
2021-11-24 09:49:20 +00:00
ID int64 ` xorm:"pk autoincr" `
Type CommentType ` xorm:"INDEX" `
PosterID int64 ` xorm:"INDEX" `
Poster * user_model . User ` xorm:"-" `
2019-07-08 02:14:12 +00:00
OriginalAuthor string
OriginalAuthorID int64
2018-07-17 21:23:58 +00:00
IssueID int64 ` xorm:"INDEX" `
Issue * Issue ` xorm:"-" `
LabelID int64
2020-10-25 21:49:48 +00:00
Label * Label ` xorm:"-" `
AddedLabels [ ] * Label ` xorm:"-" `
RemovedLabels [ ] * Label ` xorm:"-" `
2020-08-17 03:07:38 +00:00
OldProjectID int64
ProjectID int64
2022-03-29 14:16:31 +00:00
OldProject * project_model . Project ` xorm:"-" `
Project * project_model . Project ` xorm:"-" `
2018-07-17 21:23:58 +00:00
OldMilestoneID int64
MilestoneID int64
2022-06-13 09:37:59 +00:00
OldMilestone * Milestone ` xorm:"-" `
Milestone * Milestone ` xorm:"-" `
2021-02-19 10:52:11 +00:00
TimeID int64
Time * TrackedTime ` xorm:"-" `
2018-07-17 21:23:58 +00:00
AssigneeID int64
RemovedAssignee bool
2022-03-29 06:29:02 +00:00
Assignee * user_model . User ` xorm:"-" `
AssigneeTeamID int64 ` xorm:"NOT NULL DEFAULT 0" `
AssigneeTeam * organization . Team ` xorm:"-" `
2020-04-18 13:50:25 +00:00
ResolveDoerID int64
2021-11-24 09:49:20 +00:00
ResolveDoer * user_model . User ` xorm:"-" `
2018-07-17 21:23:58 +00:00
OldTitle string
NewTitle string
2019-12-16 06:20:25 +00:00
OldRef string
NewRef string
2018-07-17 21:23:58 +00:00
DependentIssueID int64
DependentIssue * Issue ` xorm:"-" `
2017-02-03 15:09:10 +00:00
2017-02-01 02:36:08 +00:00
CommitID int64
2018-08-06 04:43:22 +00:00
Line int64 // - previous line / + proposed line
TreePath string
2021-08-22 15:33:05 +00:00
Content string ` xorm:"LONGTEXT" `
2016-03-10 00:53:30 +00:00
RenderedContent string ` xorm:"-" `
2018-08-06 04:43:22 +00:00
// Path represents the 4 lines of code cemented by this comment
2020-06-18 14:07:09 +00:00
Patch string ` xorm:"-" `
2021-08-22 15:33:05 +00:00
PatchQuoted string ` xorm:"LONGTEXT patch" `
2018-08-06 04:43:22 +00:00
2019-08-15 14:46:21 +00:00
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
2016-03-05 17:58:51 +00:00
// Reference issue in commit message
CommitSHA string ` xorm:"VARCHAR(40)" `
2022-06-13 09:37:59 +00:00
Attachments [ ] * repo_model . Attachment ` xorm:"-" `
Reactions ReactionList ` xorm:"-" `
2016-03-05 17:58:51 +00:00
// For view issue page.
2021-11-11 06:29:30 +00:00
ShowRole RoleDescriptor ` xorm:"-" `
2018-08-06 04:43:22 +00:00
Review * Review ` xorm:"-" `
2019-08-05 14:29:40 +00:00
ReviewID int64 ` xorm:"index" `
2018-08-06 04:43:22 +00:00
Invalidated bool
2019-09-20 05:45:38 +00:00
// Reference an issue or pull from another comment, issue or PR
// All information is about the origin of the reference
2019-10-13 22:29:10 +00: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)
2021-07-08 11:38:13 +00:00
RefAction references . XRefAction ` xorm:"SMALLINT" ` // What happens if RefIssueID resolves
2019-09-20 05:45:38 +00:00
RefIsPull bool
2021-12-10 01:27:50 +00:00
RefRepo * repo_model . Repository ` xorm:"-" `
RefIssue * Issue ` xorm:"-" `
RefComment * Comment ` xorm:"-" `
2020-05-20 12:47:24 +00:00
2022-06-12 15:51:54 +00:00
Commits [ ] * git_model . SignCommitWithStatuses ` xorm:"-" `
OldCommit string ` xorm:"-" `
NewCommit string ` xorm:"-" `
CommitsNum int64 ` xorm:"-" `
IsForcePush bool ` xorm:"-" `
2020-05-20 12:47:24 +00:00
}
2021-09-19 11:49:59 +00:00
func init ( ) {
db . RegisterModel ( new ( Comment ) )
}
2020-05-20 12:47:24 +00:00
// PushActionContent is content of push pull comment
type PushActionContent struct {
IsForcePush bool ` json:"is_force_push" `
CommitIDs [ ] string ` json:"commit_ids" `
2016-03-05 17:58:51 +00:00
}
2018-05-16 14:01:55 +00:00
// LoadIssue loads issue from database
func ( c * Comment ) LoadIssue ( ) ( err error ) {
2022-05-03 19:46:28 +00:00
return c . LoadIssueCtx ( db . DefaultContext )
2019-09-20 05:45:38 +00:00
}
2022-05-03 19:46:28 +00:00
// LoadIssueCtx loads issue from database
func ( c * Comment ) LoadIssueCtx ( ctx context . Context ) ( err error ) {
2018-05-16 14:01:55 +00:00
if c . Issue != nil {
return nil
}
2022-06-13 09:37:59 +00:00
c . Issue , err = GetIssueByID ( ctx , c . IssueID )
2022-06-20 10:02:49 +00:00
return err
2018-05-16 14:01:55 +00:00
}
2020-06-18 14:07:09 +00: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
}
}
}
2022-05-20 14:08:52 +00:00
func ( c * Comment ) loadPoster ( ctx context . Context ) ( err error ) {
2019-09-24 17:39:50 +00:00
if c . PosterID <= 0 || c . Poster != nil {
2019-04-18 05:00:03 +00:00
return nil
}
2022-05-20 14:08:52 +00:00
c . Poster , err = user_model . GetUserByIDCtx ( ctx , c . PosterID )
2019-04-18 05:00:03 +00:00
if err != nil {
2021-11-24 09:49:20 +00:00
if user_model . IsErrUserNotExist ( err ) {
2019-04-18 05:00:03 +00:00
c . PosterID = - 1
2021-11-24 09:49:20 +00:00
c . Poster = user_model . NewGhostUser ( )
2019-04-18 05:00:03 +00:00
} else {
log . Error ( "getUserByID[%d]: %v" , c . ID , err )
}
}
return err
}
2016-11-28 13:33:09 +00:00
// AfterDelete is invoked from XORM after the object is deleted.
2016-03-05 17:58:51 +00:00
func ( c * Comment ) AfterDelete ( ) {
2018-06-11 22:54:30 +00:00
if c . ID <= 0 {
return
}
2021-11-19 13:39:57 +00:00
_ , err := repo_model . DeleteAttachmentsByComment ( c . ID , true )
2016-03-05 17:58:51 +00:00
if err != nil {
log . Info ( "Could not delete files for comment %d on issue #%d: %s" , c . ID , c . IssueID , err )
}
}
2016-12-22 08:29:26 +00:00
// HTMLURL formats a URL-string to the issue-comment
func ( c * Comment ) HTMLURL ( ) string {
2018-05-16 14:01:55 +00:00
err := c . LoadIssue ( )
2016-12-22 08:29:26 +00:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 07:48:31 +00:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2016-12-22 08:29:26 +00:00
return ""
}
2022-04-08 09:11:15 +00:00
err = c . Issue . LoadRepo ( db . DefaultContext )
2018-12-13 15:55:43 +00:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 07:48:31 +00:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 15:55:43 +00:00
return ""
}
2018-08-06 04:43:22 +00: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 14:01:55 +00:00
return fmt . Sprintf ( "%s#%s" , c . Issue . HTMLURL ( ) , c . HashTag ( ) )
2016-12-22 08:29:26 +00:00
}
2020-01-09 11:56:32 +00: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 ""
}
2022-04-08 09:11:15 +00:00
err = c . Issue . LoadRepo ( db . DefaultContext )
2020-01-09 11:56:32 +00:00
if err != nil { // Silently dropping errors :unamused:
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
return ""
}
2020-01-14 15:37:19 +00:00
return fmt . Sprintf ( "%s/issues/comments/%d" , c . Issue . Repo . APIURL ( ) , c . ID )
2020-01-09 11:56:32 +00:00
}
2016-12-22 08:29:26 +00:00
// IssueURL formats a URL-string to the issue
func ( c * Comment ) IssueURL ( ) string {
2018-05-16 14:01:55 +00:00
err := c . LoadIssue ( )
2016-12-22 08:29:26 +00:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 07:48:31 +00:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2016-12-22 08:29:26 +00:00
return ""
}
2018-05-16 14:01:55 +00:00
if c . Issue . IsPull {
2016-12-22 08:29:26 +00:00
return ""
}
2018-12-13 15:55:43 +00:00
2022-04-08 09:11:15 +00:00
err = c . Issue . LoadRepo ( db . DefaultContext )
2018-12-13 15:55:43 +00:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 07:48:31 +00:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 15:55:43 +00:00
return ""
}
2018-05-16 14:01:55 +00:00
return c . Issue . HTMLURL ( )
2016-12-22 08:29:26 +00:00
}
// PRURL formats a URL-string to the pull-request
func ( c * Comment ) PRURL ( ) string {
2018-05-16 14:01:55 +00:00
err := c . LoadIssue ( )
2016-12-22 08:29:26 +00:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 07:48:31 +00:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2016-12-22 08:29:26 +00:00
return ""
}
2022-04-08 09:11:15 +00:00
err = c . Issue . LoadRepo ( db . DefaultContext )
2018-12-13 15:55:43 +00:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 07:48:31 +00:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 15:55:43 +00:00
return ""
}
2018-05-16 14:01:55 +00:00
if ! c . Issue . IsPull {
2016-12-22 08:29:26 +00:00
return ""
}
2018-05-16 14:01:55 +00:00
return c . Issue . HTMLURL ( )
2016-12-22 08:29:26 +00:00
}
2018-05-16 14:01:55 +00:00
// CommentHashTag returns unique hash tag for comment id.
func CommentHashTag ( id int64 ) string {
return fmt . Sprintf ( "issuecomment-%d" , id )
}
2016-03-05 17:58:51 +00:00
// HashTag returns unique hash tag for comment.
func ( c * Comment ) HashTag ( ) string {
2018-05-16 14:01:55 +00:00
return CommentHashTag ( c . ID )
2016-03-05 17:58:51 +00:00
}
// EventTag returns unique event hash tag for comment.
func ( c * Comment ) EventTag ( ) string {
2020-12-25 09:59:32 +00:00
return fmt . Sprintf ( "event-%d" , c . ID )
2016-03-05 17:58:51 +00:00
}
2017-01-30 12:46:45 +00:00
// LoadLabel if comment.Type is CommentTypeLabel, then load Label
func ( c * Comment ) LoadLabel ( ) error {
var label Label
2021-09-23 15:45:36 +00:00
has , err := db . GetEngine ( db . DefaultContext ) . ID ( c . LabelID ) . Get ( & label )
2017-01-30 12:46:45 +00:00
if err != nil {
return err
2017-02-11 12:56:57 +00: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 12:46:45 +00:00
}
2017-02-11 12:56:57 +00:00
2017-01-30 12:46:45 +00:00
return nil
}
2020-08-17 03:07:38 +00:00
// LoadProject if comment.Type is CommentTypeProject, then load project.
func ( c * Comment ) LoadProject ( ) error {
if c . OldProjectID > 0 {
2022-03-29 14:16:31 +00:00
var oldProject project_model . Project
2021-09-23 15:45:36 +00:00
has , err := db . GetEngine ( db . DefaultContext ) . ID ( c . OldProjectID ) . Get ( & oldProject )
2020-08-17 03:07:38 +00:00
if err != nil {
return err
} else if has {
c . OldProject = & oldProject
}
}
if c . ProjectID > 0 {
2022-03-29 14:16:31 +00:00
var project project_model . Project
2021-09-23 15:45:36 +00:00
has , err := db . GetEngine ( db . DefaultContext ) . ID ( c . ProjectID ) . Get ( & project )
2020-08-17 03:07:38 +00:00
if err != nil {
return err
} else if has {
c . Project = & project
}
}
return nil
}
2017-02-01 02:36:08 +00:00
// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
func ( c * Comment ) LoadMilestone ( ) error {
if c . OldMilestoneID > 0 {
2022-06-13 09:37:59 +00:00
var oldMilestone Milestone
2021-09-23 15:45:36 +00:00
has , err := db . GetEngine ( db . DefaultContext ) . ID ( c . OldMilestoneID ) . Get ( & oldMilestone )
2017-02-01 02:36:08 +00:00
if err != nil {
return err
2017-06-17 04:51:28 +00:00
} else if has {
c . OldMilestone = & oldMilestone
2017-02-01 02:36:08 +00:00
}
}
if c . MilestoneID > 0 {
2022-06-13 09:37:59 +00:00
var milestone Milestone
2021-09-23 15:45:36 +00:00
has , err := db . GetEngine ( db . DefaultContext ) . ID ( c . MilestoneID ) . Get ( & milestone )
2017-02-01 02:36:08 +00:00
if err != nil {
return err
2017-06-17 04:51:28 +00:00
} else if has {
c . Milestone = & milestone
2017-02-01 02:36:08 +00:00
}
}
return nil
}
2018-12-13 15:55:43 +00:00
// LoadPoster loads comment poster
func ( c * Comment ) LoadPoster ( ) error {
2022-05-20 14:08:52 +00:00
return c . loadPoster ( db . DefaultContext )
2018-12-13 15:55:43 +00:00
}
2022-01-18 17:28:38 +00:00
// LoadAttachments loads attachments (it never returns error, the error during `GetAttachmentsByCommentIDCtx` is ignored)
2018-12-13 15:55:43 +00:00
func ( c * Comment ) LoadAttachments ( ) error {
if len ( c . Attachments ) > 0 {
return nil
}
var err error
2022-05-20 14:08:52 +00:00
c . Attachments , err = repo_model . GetAttachmentsByCommentID ( db . DefaultContext , c . ID )
2018-12-13 15:55:43 +00:00
if err != nil {
2019-04-02 07:48:31 +00:00
log . Error ( "getAttachmentsByCommentID[%d]: %v" , c . ID , err )
2018-12-13 15:55:43 +00:00
}
return nil
}
2019-10-15 12:19:32 +00:00
// UpdateAttachments update attachments by UUIDs for the comment
func ( c * Comment ) UpdateAttachments ( uuids [ ] string ) error {
2021-11-19 13:39:57 +00:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2019-10-15 12:19:32 +00:00
return err
}
2021-11-19 13:39:57 +00:00
defer committer . Close ( )
attachments , err := repo_model . GetAttachmentsByUUIDs ( ctx , uuids )
2019-10-15 12:19:32 +00:00
if err != nil {
2022-10-24 19:29:17 +00:00
return fmt . Errorf ( "getAttachmentsByUUIDs [uuids: %v]: %w" , uuids , err )
2019-10-15 12:19:32 +00:00
}
for i := 0 ; i < len ( attachments ) ; i ++ {
attachments [ i ] . IssueID = c . IssueID
attachments [ i ] . CommentID = c . ID
2022-05-20 14:08:52 +00:00
if err := repo_model . UpdateAttachment ( ctx , attachments [ i ] ) ; err != nil {
2022-10-24 19:29:17 +00:00
return fmt . Errorf ( "update attachment [id: %d]: %w" , attachments [ i ] . ID , err )
2019-10-15 12:19:32 +00:00
}
}
2021-11-19 13:39:57 +00:00
return committer . Commit ( )
2019-10-15 12:19:32 +00:00
}
2020-10-12 19:55:13 +00:00
// LoadAssigneeUserAndTeam if comment.Type is CommentTypeAssignees, then load assignees
func ( c * Comment ) LoadAssigneeUserAndTeam ( ) error {
2017-02-03 15:09:10 +00:00
var err error
2020-10-12 19:55:13 +00:00
if c . AssigneeID > 0 && c . Assignee == nil {
2021-11-24 09:49:20 +00:00
c . Assignee , err = user_model . GetUserByIDCtx ( db . DefaultContext , c . AssigneeID )
2017-02-03 15:09:10 +00:00
if err != nil {
2021-11-24 09:49:20 +00:00
if ! user_model . IsErrUserNotExist ( err ) {
2018-01-07 09:13:10 +00:00
return err
}
2021-11-24 09:49:20 +00:00
c . Assignee = user_model . NewGhostUser ( )
2017-02-03 15:09:10 +00:00
}
2020-10-12 19:55:13 +00:00
} else if c . AssigneeTeamID > 0 && c . AssigneeTeam == nil {
if err = c . LoadIssue ( ) ; err != nil {
return err
}
2022-04-08 09:11:15 +00:00
if err = c . Issue . LoadRepo ( db . DefaultContext ) ; err != nil {
2020-10-12 19:55:13 +00:00
return err
}
2021-12-10 01:27:50 +00:00
if err = c . Issue . Repo . GetOwner ( db . DefaultContext ) ; err != nil {
2020-10-12 19:55:13 +00:00
return err
}
if c . Issue . Repo . Owner . IsOrganization ( ) {
2022-05-20 14:08:52 +00:00
c . AssigneeTeam , err = organization . GetTeamByID ( db . DefaultContext , c . AssigneeTeamID )
2022-03-29 06:29:02 +00:00
if err != nil && ! organization . IsErrTeamNotExist ( err ) {
2020-10-12 19:55:13 +00:00
return err
}
}
2017-02-03 15:09:10 +00:00
}
return nil
}
2020-04-18 13:50:25 +00: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
}
2021-11-24 09:49:20 +00:00
c . ResolveDoer , err = user_model . GetUserByIDCtx ( db . DefaultContext , c . ResolveDoerID )
2020-04-18 13:50:25 +00:00
if err != nil {
2021-11-24 09:49:20 +00:00
if user_model . IsErrUserNotExist ( err ) {
c . ResolveDoer = user_model . NewGhostUser ( )
2020-04-18 13:50:25 +00:00
err = nil
}
}
2022-06-20 10:02:49 +00:00
return err
2020-04-18 13:50:25 +00:00
}
// IsResolved check if an code comment is resolved
func ( c * Comment ) IsResolved ( ) bool {
return c . ResolveDoerID != 0 && c . Type == CommentTypeCode
}
2018-07-17 21:23:58 +00:00
// LoadDepIssueDetails loads Dependent Issue Details
func ( c * Comment ) LoadDepIssueDetails ( ) ( err error ) {
if c . DependentIssueID <= 0 || c . DependentIssue != nil {
return nil
}
2022-06-13 09:37:59 +00:00
c . DependentIssue , err = GetIssueByID ( db . DefaultContext , c . DependentIssueID )
2018-07-17 21:23:58 +00:00
return err
}
2021-02-19 10:52:11 +00:00
// LoadTime loads the associated time for a CommentTypeAddTimeManual
func ( c * Comment ) LoadTime ( ) error {
if c . Time != nil || c . TimeID == 0 {
return nil
}
var err error
c . Time , err = GetTrackedTimeByID ( c . TimeID )
return err
}
2022-03-31 09:20:39 +00:00
func ( c * Comment ) loadReactions ( ctx context . Context , repo * repo_model . Repository ) ( err error ) {
2017-12-03 23:14:26 +00:00
if c . Reactions != nil {
return nil
}
2022-06-13 09:37:59 +00:00
c . Reactions , _ , err = FindReactions ( ctx , FindReactionsOptions {
2017-12-03 23:14:26 +00:00
IssueID : c . IssueID ,
CommentID : c . ID ,
} )
if err != nil {
return err
}
// Load reaction user data
2022-03-31 09:20:39 +00:00
if _ , err := c . Reactions . LoadUsers ( ctx , repo ) ; err != nil {
2017-12-03 23:14:26 +00:00
return err
}
return nil
}
// LoadReactions loads comment reactions
2021-12-10 01:27:50 +00:00
func ( c * Comment ) LoadReactions ( repo * repo_model . Repository ) error {
2022-03-31 09:20:39 +00:00
return c . loadReactions ( db . DefaultContext , repo )
2017-12-03 23:14:26 +00:00
}
2022-05-20 14:08:52 +00:00
func ( c * Comment ) loadReview ( ctx context . Context ) ( err error ) {
2018-12-13 15:55:43 +00:00
if c . Review == nil {
2022-05-20 14:08:52 +00:00
if c . Review , err = GetReviewByID ( ctx , c . ReviewID ) ; err != nil {
2018-12-13 15:55:43 +00:00
return err
}
2018-08-06 04:43:22 +00:00
}
2019-05-06 12:09:31 +00:00
c . Review . Issue = c . Issue
2018-08-06 04:43:22 +00:00
return nil
}
// LoadReview loads the associated review
func ( c * Comment ) LoadReview ( ) error {
2022-05-20 14:08:52 +00:00
return c . loadReview ( db . DefaultContext )
2018-08-06 04:43:22 +00: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 07:48:31 +00:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2018-08-06 04:43:22 +00:00
return ""
}
2022-04-08 09:11:15 +00:00
err = c . Issue . LoadRepo ( db . DefaultContext )
2018-12-13 15:55:43 +00:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 07:48:31 +00:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 15:55:43 +00:00
return ""
}
2018-08-06 04:43:22 +00:00
return fmt . Sprintf ( "%s/files#%s" , c . Issue . HTMLURL ( ) , c . HashTag ( ) )
}
2020-05-20 12:47:24 +00:00
// LoadPushCommits Load push commits
2022-01-19 23:26:57 +00:00
func ( c * Comment ) LoadPushCommits ( ctx context . Context ) ( err error ) {
2022-01-21 17:59:26 +00:00
if c . Content == "" || c . Commits != nil || c . Type != CommentTypePullRequestPush {
2020-05-20 12:47:24 +00:00
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 ( )
2022-01-19 23:26:57 +00:00
gitRepo , closer , err := git . RepositoryFromContextOrOpen ( ctx , repoPath )
2020-05-20 12:47:24 +00:00
if err != nil {
return err
}
2022-01-19 23:26:57 +00:00
defer closer . Close ( )
2020-05-20 12:47:24 +00:00
2022-06-12 15:51:54 +00:00
c . Commits = git_model . ConvertFromGitCommit ( gitRepo . GetCommitsFromIDs ( data . CommitIDs ) , c . Issue . Repo )
2021-08-09 18:08:51 +00:00
c . CommitsNum = int64 ( len ( c . Commits ) )
2020-05-20 12:47:24 +00:00
}
return err
}
2022-04-08 09:11:15 +00:00
// CreateCommentCtx creates comment with context
func CreateCommentCtx ( ctx context . Context , opts * CreateCommentOptions ) ( _ * Comment , err error ) {
2021-11-19 13:39:57 +00:00
e := db . GetEngine ( ctx )
2017-01-30 12:46:45 +00:00
var LabelID int64
if opts . Label != nil {
LabelID = opts . Label . ID
}
2018-07-17 21:23:58 +00:00
2016-03-05 17:58:51 +00:00
comment := & Comment {
2018-07-17 21:23:58 +00: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 03:07:38 +00:00
OldProjectID : opts . OldProjectID ,
ProjectID : opts . ProjectID ,
2021-02-19 10:52:11 +00:00
TimeID : opts . TimeID ,
2018-07-17 21:23:58 +00:00
RemovedAssignee : opts . RemovedAssignee ,
AssigneeID : opts . AssigneeID ,
2020-10-12 19:55:13 +00:00
AssigneeTeamID : opts . AssigneeTeamID ,
2018-07-17 21:23:58 +00:00
CommitID : opts . CommitID ,
CommitSHA : opts . CommitSHA ,
Line : opts . LineNum ,
Content : opts . Content ,
OldTitle : opts . OldTitle ,
NewTitle : opts . NewTitle ,
2019-12-16 06:20:25 +00:00
OldRef : opts . OldRef ,
NewRef : opts . NewRef ,
2018-07-17 21:23:58 +00:00
DependentIssueID : opts . DependentIssueID ,
2018-08-06 04:43:22 +00:00
TreePath : opts . TreePath ,
ReviewID : opts . ReviewID ,
Patch : opts . Patch ,
2019-09-20 05:45:38 +00:00
RefRepoID : opts . RefRepoID ,
RefIssueID : opts . RefIssueID ,
RefCommentID : opts . RefCommentID ,
RefAction : opts . RefAction ,
RefIsPull : opts . RefIsPull ,
2020-05-20 12:47:24 +00:00
IsForcePush : opts . IsForcePush ,
2020-11-09 06:15:09 +00:00
Invalidated : opts . Invalidated ,
2016-03-05 17:58:51 +00:00
}
if _ , err = e . Insert ( comment ) ; err != nil {
return nil , err
}
2021-12-10 01:27:50 +00:00
if err = opts . Repo . GetOwner ( ctx ) ; err != nil {
2017-01-30 12:46:45 +00:00
return nil , err
}
2021-11-19 13:39:57 +00:00
if err = updateCommentInfos ( ctx , opts , comment ) ; err != nil {
2019-11-06 13:39:29 +00:00
return nil , err
}
2022-06-13 09:37:59 +00:00
if err = comment . AddCrossReferences ( ctx , opts . Doer , false ) ; err != nil {
2019-09-20 05:45:38 +00:00
return nil , err
}
2018-08-06 04:43:22 +00:00
return comment , nil
}
2021-11-19 13:39:57 +00:00
func updateCommentInfos ( ctx context . Context , opts * CreateCommentOptions , comment * Comment ) ( err error ) {
2016-03-05 17:58:51 +00:00
// Check comment type.
switch opts . Type {
2018-08-06 04:43:22 +00:00
case CommentTypeCode :
if comment . ReviewID != 0 {
if comment . Review == nil {
2022-05-20 14:08:52 +00:00
if err := comment . loadReview ( ctx ) ; err != nil {
2018-08-06 04:43:22 +00:00
return err
}
}
if comment . Review . Type <= ReviewTypePending {
return nil
}
}
fallthrough
2016-11-07 16:30:04 +00:00
case CommentTypeComment :
2022-05-20 14:08:52 +00:00
if _ , err = db . Exec ( ctx , "UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?" , opts . Issue . ID ) ; err != nil {
2018-08-06 04:43:22 +00:00
return err
2016-03-05 17:58:51 +00:00
}
2022-01-13 16:50:43 +00:00
fallthrough
case CommentTypeReview :
2016-03-05 17:58:51 +00:00
// Check attachments
2021-11-19 13:39:57 +00:00
attachments , err := repo_model . GetAttachmentsByUUIDs ( ctx , opts . Attachments )
2019-12-11 00:01:52 +00:00
if err != nil {
2022-10-24 19:29:17 +00:00
return fmt . Errorf ( "getAttachmentsByUUIDs [uuids: %v]: %w" , opts . Attachments , err )
2016-03-05 17:58:51 +00: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().
2022-05-20 14:08:52 +00:00
if _ , err = db . GetEngine ( ctx ) . ID ( attachments [ i ] . ID ) . Update ( attachments [ i ] ) ; err != nil {
2022-10-24 19:29:17 +00:00
return fmt . Errorf ( "update attachment [%d]: %w" , attachments [ i ] . ID , err )
2016-03-05 17:58:51 +00:00
}
}
2019-11-06 13:39:29 +00:00
case CommentTypeReopen , CommentTypeClose :
2022-10-25 12:47:46 +00:00
if err = repo_model . UpdateRepoIssueNumbers ( ctx , opts . Issue . RepoID , opts . Issue . IsPull , true ) ; err != nil {
2019-11-06 13:39:29 +00:00
return err
}
}
// update the issue's updated_unix column
2022-04-08 09:11:15 +00:00
return UpdateIssueCols ( ctx , opts . Issue , "updated_unix" )
2019-11-06 13:39:29 +00:00
}
2016-03-05 17:58:51 +00:00
2021-11-24 09:49:20 +00:00
func createDeadlineComment ( ctx context . Context , doer * user_model . User , issue * Issue , newDeadlineUnix timeutil . TimeStamp ) ( * Comment , error ) {
2018-05-01 19:05:28 +00: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" )
}
2022-04-08 09:11:15 +00:00
if err := issue . LoadRepo ( ctx ) ; err != nil {
2018-12-27 15:02:43 +00:00
return nil , err
}
2021-03-14 18:52:12 +00:00
opts := & CreateCommentOptions {
2018-05-01 19:05:28 +00:00
Type : commentType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
Content : content ,
2019-12-01 02:44:39 +00:00
}
2022-04-08 09:11:15 +00:00
comment , err := CreateCommentCtx ( ctx , opts )
2019-12-01 02:44:39 +00:00
if err != nil {
return nil , err
}
2019-12-02 14:43:39 +00:00
return comment , nil
2018-05-01 19:05:28 +00:00
}
2018-07-17 21:23:58 +00:00
// Creates issue dependency comment
2021-11-24 09:49:20 +00:00
func createIssueDependencyComment ( ctx context . Context , doer * user_model . User , issue , dependentIssue * Issue , add bool ) ( err error ) {
2018-07-17 21:23:58 +00:00
cType := CommentTypeAddDependency
if ! add {
cType = CommentTypeRemoveDependency
}
2022-04-08 09:11:15 +00:00
if err = issue . LoadRepo ( ctx ) ; err != nil {
2019-01-27 11:31:40 +00:00
return
}
2018-07-17 21:23:58 +00:00
// Make two comments, one in each issue
2021-03-14 18:52:12 +00:00
opts := & CreateCommentOptions {
2018-07-17 21:23:58 +00:00
Type : cType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
DependentIssueID : dependentIssue . ID ,
2019-12-01 02:44:39 +00:00
}
2022-04-08 09:11:15 +00:00
if _ , err = CreateCommentCtx ( ctx , opts ) ; err != nil {
2018-07-17 21:23:58 +00:00
return
}
2019-12-01 02:44:39 +00:00
opts = & CreateCommentOptions {
2018-07-17 21:23:58 +00:00
Type : cType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : dependentIssue ,
DependentIssueID : issue . ID ,
2019-12-01 02:44:39 +00:00
}
2022-04-08 09:11:15 +00:00
_ , err = CreateCommentCtx ( ctx , opts )
2022-06-20 10:02:49 +00:00
return err
2018-07-17 21:23:58 +00:00
}
2016-11-28 13:33:09 +00:00
// CreateCommentOptions defines options for creating comment
2016-03-05 17:58:51 +00:00
type CreateCommentOptions struct {
Type CommentType
2021-11-24 09:49:20 +00:00
Doer * user_model . User
2021-12-10 01:27:50 +00:00
Repo * repo_model . Repository
2016-03-05 17:58:51 +00:00
Issue * Issue
2017-01-30 12:46:45 +00:00
Label * Label
2016-03-05 17:58:51 +00:00
2018-07-17 21:23:58 +00:00
DependentIssueID int64
OldMilestoneID int64
MilestoneID int64
2020-08-17 03:07:38 +00:00
OldProjectID int64
ProjectID int64
2021-02-19 10:52:11 +00:00
TimeID int64
2018-07-17 21:23:58 +00:00
AssigneeID int64
2020-10-12 19:55:13 +00:00
AssigneeTeamID int64
2018-07-17 21:23:58 +00:00
RemovedAssignee bool
OldTitle string
NewTitle string
2019-12-16 06:20:25 +00:00
OldRef string
NewRef string
2018-07-17 21:23:58 +00:00
CommitID int64
CommitSHA string
2018-08-06 04:43:22 +00:00
Patch string
2018-07-17 21:23:58 +00:00
LineNum int64
2018-08-06 04:43:22 +00:00
TreePath string
ReviewID int64
2018-07-17 21:23:58 +00:00
Content string
Attachments [ ] string // UUIDs of attachments
2019-09-20 05:45:38 +00:00
RefRepoID int64
RefIssueID int64
RefCommentID int64
2019-10-13 22:29:10 +00:00
RefAction references . XRefAction
2019-09-20 05:45:38 +00:00
RefIsPull bool
2020-05-20 12:47:24 +00:00
IsForcePush bool
2020-11-09 06:15:09 +00:00
Invalidated bool
2016-03-05 17:58:51 +00:00
}
// CreateComment creates comment of issue or commit.
func CreateComment ( opts * CreateCommentOptions ) ( comment * Comment , err error ) {
2021-11-19 13:39:57 +00:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2016-03-05 17:58:51 +00:00
return nil , err
}
2021-11-19 13:39:57 +00:00
defer committer . Close ( )
2016-03-05 17:58:51 +00:00
2022-04-08 09:11:15 +00:00
comment , err = CreateCommentCtx ( ctx , opts )
2016-03-05 17:58:51 +00:00
if err != nil {
return nil , err
}
2021-11-19 13:39:57 +00:00
if err = committer . Commit ( ) ; err != nil {
2017-09-16 20:16:21 +00:00
return nil , err
}
return comment , nil
2016-03-05 17:58:51 +00:00
}
// CreateRefComment creates a commit reference comment to issue.
2021-12-10 01:27:50 +00:00
func CreateRefComment ( doer * user_model . User , repo * repo_model . Repository , issue * Issue , content , commitSHA string ) error {
2016-03-05 17:58:51 +00:00
if len ( commitSHA ) == 0 {
return fmt . Errorf ( "cannot create reference with empty commit SHA" )
}
// Check if same reference from same commit has already existed.
2021-09-23 15:45:36 +00:00
has , err := db . GetEngine ( db . DefaultContext ) . Get ( & Comment {
2016-11-07 16:35:34 +00:00
Type : CommentTypeCommitRef ,
2016-03-05 17:58:51 +00:00
IssueID : issue . ID ,
CommitSHA : commitSHA ,
} )
if err != nil {
2022-10-24 19:29:17 +00:00
return fmt . Errorf ( "check reference comment: %w" , err )
2016-03-05 17:58:51 +00:00
} else if has {
return nil
}
_ , err = CreateComment ( & CreateCommentOptions {
2016-11-07 16:35:34 +00:00
Type : CommentTypeCommitRef ,
2016-03-05 17:58:51 +00:00
Doer : doer ,
Repo : repo ,
Issue : issue ,
CommitSHA : commitSHA ,
Content : content ,
} )
return err
}
// GetCommentByID returns the comment by given ID.
2022-05-20 14:08:52 +00:00
func GetCommentByID ( ctx context . Context , id int64 ) ( * Comment , error ) {
2016-03-05 17:58:51 +00:00
c := new ( Comment )
2022-05-20 14:08:52 +00:00
has , err := db . GetEngine ( ctx ) . ID ( id ) . Get ( c )
2016-03-05 17:58:51 +00:00
if err != nil {
return nil , err
} else if ! has {
2016-08-26 20:40:53 +00:00
return nil , ErrCommentNotExist { id , 0 }
2016-03-05 17:58:51 +00:00
}
return c , nil
}
2017-06-21 01:00:44 +00:00
// FindCommentsOptions describes the conditions to Find comments
type FindCommentsOptions struct {
2021-09-24 11:32:56 +00:00
db . ListOptions
2023-02-16 13:21:25 +00:00
RepoID int64
IssueID int64
ReviewID int64
Since int64
Before int64
Line int64
TreePath string
Type CommentType
IssueIDs [ ] int64
Invalidated util . OptionalBool
}
// ToConds implements FindOptions interface
func ( opts * FindCommentsOptions ) ToConds ( ) builder . Cond {
2021-03-14 18:52:12 +00:00
cond := builder . NewCond ( )
2017-06-21 01:00:44 +00:00
if opts . RepoID > 0 {
cond = cond . And ( builder . Eq { "issue.repo_id" : opts . RepoID } )
2016-08-26 20:40:53 +00:00
}
2017-06-21 01:00:44 +00:00
if opts . IssueID > 0 {
cond = cond . And ( builder . Eq { "comment.issue_id" : opts . IssueID } )
2023-02-16 13:21:25 +00:00
} else if len ( opts . IssueIDs ) > 0 {
cond = cond . And ( builder . In ( "comment.issue_id" , opts . IssueIDs ) )
2017-06-21 01:00:44 +00:00
}
2018-08-06 04:43:22 +00:00
if opts . ReviewID > 0 {
cond = cond . And ( builder . Eq { "comment.review_id" : opts . ReviewID } )
}
2017-06-21 01:00:44 +00:00
if opts . Since > 0 {
cond = cond . And ( builder . Gte { "comment.updated_unix" : opts . Since } )
}
2020-01-13 16:02:24 +00:00
if opts . Before > 0 {
cond = cond . And ( builder . Lte { "comment.updated_unix" : opts . Before } )
}
2017-06-21 01:00:44 +00:00
if opts . Type != CommentTypeUnknown {
cond = cond . And ( builder . Eq { "comment.type" : opts . Type } )
}
2021-01-08 21:49:55 +00:00
if opts . Line != 0 {
2020-11-09 06:15:09 +00:00
cond = cond . And ( builder . Eq { "comment.line" : opts . Line } )
}
if len ( opts . TreePath ) > 0 {
cond = cond . And ( builder . Eq { "comment.tree_path" : opts . TreePath } )
}
2023-02-16 13:21:25 +00:00
if ! opts . Invalidated . IsNone ( ) {
cond = cond . And ( builder . Eq { "comment.invalidated" : opts . Invalidated . IsTrue ( ) } )
}
2017-06-21 01:00:44 +00:00
return cond
2016-08-26 20:40:53 +00:00
}
2022-05-20 14:08:52 +00:00
// FindComments returns all comments according options
func FindComments ( ctx context . Context , opts * FindCommentsOptions ) ( [ ] * Comment , error ) {
2016-12-22 08:29:26 +00:00
comments := make ( [ ] * Comment , 0 , 10 )
2023-02-16 13:21:25 +00:00
sess := db . GetEngine ( ctx ) . Where ( opts . ToConds ( ) )
2017-06-21 01:00:44 +00:00
if opts . RepoID > 0 {
sess . Join ( "INNER" , "issue" , "issue.id = comment.issue_id" )
2016-12-22 08:29:26 +00:00
}
2020-01-24 19:00:29 +00:00
if opts . Page != 0 {
2021-09-24 11:32:56 +00:00
sess = db . SetSessionPagination ( sess , opts )
2020-01-24 19:00:29 +00:00
}
2020-11-09 06:15:09 +00:00
// WARNING: If you change this order you will need to fix createCodeComment
2017-06-21 01:00:44 +00:00
return comments , sess .
Asc ( "comment.created_unix" ) .
2017-11-03 03:11:42 +00:00
Asc ( "comment.id" ) .
2017-06-21 01:00:44 +00:00
Find ( & comments )
2016-12-22 08:29:26 +00:00
}
2021-08-12 12:43:08 +00:00
// CountComments count all comments according options by ignoring pagination
func CountComments ( opts * FindCommentsOptions ) ( int64 , error ) {
2023-02-16 13:21:25 +00:00
sess := db . GetEngine ( db . DefaultContext ) . Where ( opts . ToConds ( ) )
2021-08-12 12:43:08 +00:00
if opts . RepoID > 0 {
sess . Join ( "INNER" , "issue" , "issue.id = comment.issue_id" )
}
return sess . Count ( & Comment { } )
}
2023-02-16 13:21:25 +00:00
// 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
}
2016-03-05 17:58:51 +00:00
// UpdateComment updates information of comment.
2021-11-24 09:49:20 +00:00
func UpdateComment ( c * Comment , doer * user_model . User ) error {
2021-11-19 13:39:57 +00:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2017-09-16 20:16:21 +00:00
return err
}
2021-11-19 13:39:57 +00:00
defer committer . Close ( )
sess := db . GetEngine ( ctx )
2018-05-16 14:01:55 +00:00
2019-09-20 05:45:38 +00:00
if _ , err := sess . ID ( c . ID ) . AllCols ( ) . Update ( c ) ; err != nil {
2018-12-13 15:55:43 +00:00
return err
}
2022-05-03 19:46:28 +00:00
if err := c . LoadIssueCtx ( ctx ) ; err != nil {
2018-05-16 14:01:55 +00:00
return err
}
2022-06-13 09:37:59 +00:00
if err := c . AddCrossReferences ( ctx , doer , true ) ; err != nil {
2019-09-20 05:45:38 +00:00
return err
}
2021-11-19 13:39:57 +00:00
if err := committer . Commit ( ) ; err != nil {
2022-10-24 19:29:17 +00:00
return fmt . Errorf ( "Commit: %w" , err )
2019-09-20 05:45:38 +00:00
}
2018-05-16 14:01:55 +00:00
2017-09-16 20:16:21 +00:00
return nil
2016-03-05 17:58:51 +00:00
}
2016-07-25 18:48:17 +00:00
2017-01-25 02:43:02 +00:00
// DeleteComment deletes the comment
2022-06-13 09:37:59 +00:00
func DeleteComment ( ctx context . Context , comment * Comment ) error {
2022-03-31 09:20:39 +00:00
e := db . GetEngine ( ctx )
2022-03-01 00:20:15 +00:00
if _ , err := e . ID ( comment . ID ) . NoAutoCondition ( ) . Delete ( comment ) ; err != nil {
2016-07-25 18:48:17 +00:00
return err
}
2022-06-13 09:37:59 +00:00
if _ , err := db . DeleteByBean ( ctx , & ContentHistory {
2021-10-10 22:40:03 +00:00
CommentID : comment . ID ,
} ) ; err != nil {
return err
}
2016-11-07 16:30:04 +00:00
if comment . Type == CommentTypeComment {
2022-03-17 22:04:09 +00:00
if _ , err := e . ID ( comment . IssueID ) . Decr ( "num_comments" ) . Update ( new ( Issue ) ) ; err != nil {
2016-07-25 18:48:17 +00:00
return err
}
}
2022-06-13 09:37:59 +00:00
if _ , err := e . Table ( "action" ) .
Where ( "comment_id = ?" , comment . ID ) .
Update ( map [ string ] interface { } {
"is_deleted" : true ,
} ) ; err != nil {
2017-07-04 01:30:41 +00:00
return err
}
2016-07-25 18:48:17 +00:00
2022-05-20 14:08:52 +00:00
if err := comment . neuterCrossReferences ( ctx ) ; err != nil {
2019-09-20 05:45:38 +00:00
return err
}
2022-06-13 09:37:59 +00:00
return DeleteReaction ( ctx , & ReactionOptions { CommentID : comment . ID } )
2016-07-25 18:48:17 +00:00
}
2018-08-06 04:43:22 +00:00
2019-10-14 06:10:42 +00:00
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
2019-10-17 02:06:28 +00:00
func UpdateCommentsMigrationsByType ( tp structs . GitServiceType , originalAuthorID string , posterID int64 ) error {
2021-09-23 15:45:36 +00:00
_ , err := db . GetEngine ( db . DefaultContext ) . Table ( "comment" ) .
2019-10-14 06:10:42 +00:00
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 12:47:24 +00:00
2021-07-08 11:38:13 +00:00
// CreatePushPullComment create push code to pull base comment
2022-01-19 23:26:57 +00:00
func CreatePushPullComment ( ctx context . Context , pusher * user_model . User , pr * PullRequest , oldCommitID , newCommitID string ) ( comment * Comment , err error ) {
2020-05-20 12:47:24 +00:00
if pr . HasMerged || oldCommitID == "" || newCommitID == "" {
return nil , nil
}
ops := & CreateCommentOptions {
2022-01-21 17:59:26 +00:00
Type : CommentTypePullRequestPush ,
2020-05-20 12:47:24 +00:00
Doer : pusher ,
Repo : pr . BaseRepo ,
}
var data PushActionContent
2022-01-19 23:26:57 +00:00
data . CommitIDs , data . IsForcePush , err = getCommitIDsFromRepo ( ctx , pr . BaseRepo , oldCommitID , newCommitID , pr . BaseBranch )
2020-05-20 12:47:24 +00:00
if err != nil {
return nil , err
}
ops . Issue = pr . Issue
2021-03-01 21:08:10 +00:00
2020-05-20 12:47:24 +00:00
dataJSON , err := json . Marshal ( data )
if err != nil {
return nil , err
}
ops . Content = string ( dataJSON )
comment , err = CreateComment ( ops )
2022-06-20 10:02:49 +00:00
return comment , err
2020-05-20 12:47:24 +00:00
}
2022-05-08 13:46:34 +00:00
// CreateAutoMergeComment is a internal function, only use it for CommentTypePRScheduledToAutoMerge and CommentTypePRUnScheduledToAutoMerge CommentTypes
func CreateAutoMergeComment ( ctx context . Context , typ CommentType , pr * PullRequest , doer * user_model . User ) ( comment * Comment , err error ) {
if typ != CommentTypePRScheduledToAutoMerge && typ != CommentTypePRUnScheduledToAutoMerge {
return nil , fmt . Errorf ( "comment type %d cannot be used to create an auto merge comment" , typ )
}
if err = pr . LoadIssueCtx ( ctx ) ; err != nil {
return
}
if err = pr . LoadBaseRepoCtx ( ctx ) ; err != nil {
return
}
comment , err = CreateCommentCtx ( ctx , & CreateCommentOptions {
Type : typ ,
Doer : doer ,
Repo : pr . BaseRepo ,
Issue : pr . Issue ,
} )
2022-06-20 10:02:49 +00:00
return comment , err
2022-05-08 13:46:34 +00:00
}
2021-07-08 11:38:13 +00:00
// getCommitsFromRepo get commit IDs from repo in between oldCommitID and newCommitID
2020-05-20 12:47:24 +00:00
// isForcePush will be true if oldCommit isn't on the branch
// Commit on baseBranch will skip
2022-01-19 23:26:57 +00:00
func getCommitIDsFromRepo ( ctx context . Context , repo * repo_model . Repository , oldCommitID , newCommitID , baseBranch string ) ( commitIDs [ ] string , isForcePush bool , err error ) {
2020-05-20 12:47:24 +00:00
repoPath := repo . RepoPath ( )
2022-01-19 23:26:57 +00:00
gitRepo , closer , err := git . RepositoryFromContextOrOpen ( ctx , repoPath )
2020-05-20 12:47:24 +00:00
if err != nil {
return nil , false , err
}
2022-01-19 23:26:57 +00:00
defer closer . Close ( )
2020-05-20 12:47:24 +00:00
oldCommit , err := gitRepo . GetCommit ( oldCommitID )
if err != nil {
return nil , false , err
}
2020-06-24 19:40:52 +00:00
if err = oldCommit . LoadBranchName ( ) ; err != nil {
2020-05-20 12:47:24 +00:00
return nil , false , err
}
2020-06-24 19:40:52 +00:00
if len ( oldCommit . Branch ) == 0 {
2020-05-20 12:47:24 +00: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
}
2021-08-09 18:08:51 +00:00
commits , err := newCommit . CommitsBeforeUntil ( oldCommitID )
2020-05-20 12:47:24 +00:00
if err != nil {
return nil , false , err
}
2021-08-09 18:08:51 +00:00
commitIDs = make ( [ ] string , 0 , len ( commits ) )
commitChecks := make ( map [ string ] * commitBranchCheckItem )
2020-05-20 12:47:24 +00:00
2021-08-09 18:08:51 +00:00
for _ , commit := range commits {
commitChecks [ commit . ID . String ( ) ] = & commitBranchCheckItem {
Commit : commit ,
2020-06-24 19:40:52 +00:00
Checked : false ,
2020-05-20 12:47:24 +00:00
}
2020-06-24 19:40:52 +00:00
}
2020-05-20 12:47:24 +00:00
2020-06-24 19:40:52 +00:00
if err = commitBranchCheck ( gitRepo , newCommit , oldCommitID , baseBranch , commitChecks ) ; err != nil {
return
}
2021-08-09 18:08:51 +00:00
for i := len ( commits ) - 1 ; i >= 0 ; i -- {
commitID := commits [ i ] . ID . String ( )
2020-06-24 19:40:52 +00:00
if item , ok := commitChecks [ commitID ] ; ok && item . Checked {
commitIDs = append ( commitIDs , commitID )
2020-05-20 12:47:24 +00:00
}
}
2022-06-20 10:02:49 +00:00
return commitIDs , isForcePush , err
2020-05-20 12:47:24 +00:00
}
2020-06-24 19:40:52 +00:00
type commitBranchCheckItem struct {
Commit * git . Commit
Checked bool
}
2021-08-09 18:08:51 +00:00
func commitBranchCheck ( gitRepo * git . Repository , startCommit * git . Commit , endCommitID , baseBranch string , commitList map [ string ] * commitBranchCheckItem ) error {
2020-06-24 19:40:52 +00:00
if startCommit . ID . String ( ) == endCommitID {
2021-08-09 18:08:51 +00:00
return nil
2020-06-24 19:40:52 +00:00
}
2021-08-09 18:08:51 +00:00
checkStack := make ( [ ] string , 0 , 10 )
checkStack = append ( checkStack , startCommit . ID . String ( ) )
2020-06-24 19:40:52 +00:00
2021-08-09 18:08:51 +00:00
for len ( checkStack ) > 0 {
commitID := checkStack [ 0 ]
checkStack = checkStack [ 1 : ]
2020-06-24 19:40:52 +00:00
2021-08-09 18:08:51 +00:00
item , ok := commitList [ commitID ]
if ! ok {
2020-06-24 19:40:52 +00:00
continue
}
if item . Commit . ID . String ( ) == endCommitID {
continue
}
2021-08-09 18:08:51 +00:00
if err := item . Commit . LoadBranchName ( ) ; err != nil {
return err
2020-06-24 19:40:52 +00:00
}
if item . Commit . Branch == baseBranch {
continue
}
if item . Checked {
continue
}
item . Checked = true
parentNum := item . Commit . ParentCount ( )
for i := 0 ; i < parentNum ; i ++ {
2021-08-09 18:08:51 +00:00
parentCommit , err := item . Commit . Parent ( i )
2020-06-24 19:40:52 +00:00
if err != nil {
2021-08-09 18:08:51 +00:00
return err
2020-06-24 19:40:52 +00:00
}
2021-08-09 18:08:51 +00:00
checkStack = append ( checkStack , parentCommit . ID . String ( ) )
2020-06-24 19:40:52 +00:00
}
}
return nil
}
2022-02-01 18:20:28 +00:00
// RemapExternalUser ExternalUserRemappable interface
func ( c * Comment ) RemapExternalUser ( externalName string , externalID , userID int64 ) error {
c . OriginalAuthor = externalName
c . OriginalAuthorID = externalID
c . PosterID = userID
return nil
}
// GetUserID ExternalUserRemappable interface
func ( c * Comment ) GetUserID ( ) int64 { return c . PosterID }
// GetExternalName ExternalUserRemappable interface
func ( c * Comment ) GetExternalName ( ) string { return c . OriginalAuthor }
// GetExternalID ExternalUserRemappable interface
func ( c * Comment ) GetExternalID ( ) int64 { return c . OriginalAuthorID }
2022-06-13 09:37:59 +00:00
// CountCommentTypeLabelWithEmptyLabel count label comments with empty label
func CountCommentTypeLabelWithEmptyLabel ( ) ( int64 , error ) {
return db . GetEngine ( db . DefaultContext ) . Where ( builder . Eq { "type" : CommentTypeLabel , "label_id" : 0 } ) . Count ( new ( Comment ) )
}
// FixCommentTypeLabelWithEmptyLabel count label comments with empty label
func FixCommentTypeLabelWithEmptyLabel ( ) ( int64 , error ) {
return db . GetEngine ( db . DefaultContext ) . Where ( builder . Eq { "type" : CommentTypeLabel , "label_id" : 0 } ) . Delete ( new ( Comment ) )
}
// CountCommentTypeLabelWithOutsideLabels count label comments with outside label
func CountCommentTypeLabelWithOutsideLabels ( ) ( int64 , error ) {
return db . GetEngine ( db . DefaultContext ) . Where ( "comment.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))" , CommentTypeLabel ) .
Table ( "comment" ) .
Join ( "inner" , "label" , "label.id = comment.label_id" ) .
Join ( "inner" , "issue" , "issue.id = comment.issue_id " ) .
Join ( "inner" , "repository" , "issue.repo_id = repository.id" ) .
Count ( )
}
// FixCommentTypeLabelWithOutsideLabels count label comments with outside label
func FixCommentTypeLabelWithOutsideLabels ( ) ( int64 , error ) {
res , err := db . GetEngine ( db . DefaultContext ) . Exec ( ` DELETE FROM comment WHERE comment . id IN (
SELECT il_too . id FROM (
SELECT com . id
FROM comment AS com
INNER JOIN label ON com . label_id = label . id
INNER JOIN issue on issue . id = com . issue_id
INNER JOIN repository ON issue . repo_id = repository . id
WHERE
com . type = ? AND ( ( label . org_id = 0 AND issue . repo_id != label . repo_id ) OR ( label . repo_id = 0 AND label . org_id != repository . owner_id ) )
) AS il_too ) ` , CommentTypeLabel )
if err != nil {
return 0 , err
}
return res . RowsAffected ( )
}
2023-02-16 03:19:46 +00:00
// HasOriginalAuthor returns if a comment was migrated and has an original author.
func ( c * Comment ) HasOriginalAuthor ( ) bool {
return c . OriginalAuthor != "" && c . OriginalAuthorID != 0
}