mirror of https://github.com/gogits/gogs.git
Unknwon
9 years ago
10 changed files with 338 additions and 278 deletions
@ -0,0 +1,311 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"github.com/go-xorm/xorm" |
||||
|
||||
"github.com/gogits/gogs/modules/log" |
||||
) |
||||
|
||||
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
|
||||
type CommentType int |
||||
|
||||
const ( |
||||
// Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
|
||||
COMMENT_TYPE_COMMENT CommentType = iota |
||||
COMMENT_TYPE_REOPEN |
||||
COMMENT_TYPE_CLOSE |
||||
|
||||
// References.
|
||||
COMMENT_TYPE_ISSUE_REF |
||||
// Reference from a commit (not part of a pull request)
|
||||
COMMENT_TYPE_COMMIT_REF |
||||
// Reference from a comment
|
||||
COMMENT_TYPE_COMMENT_REF |
||||
// Reference from a pull request
|
||||
COMMENT_TYPE_PULL_REF |
||||
) |
||||
|
||||
type CommentTag int |
||||
|
||||
const ( |
||||
COMMENT_TAG_NONE CommentTag = iota |
||||
COMMENT_TAG_POSTER |
||||
COMMENT_TAG_ADMIN |
||||
COMMENT_TAG_OWNER |
||||
) |
||||
|
||||
// Comment represents a comment in commit and issue page.
|
||||
type Comment struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
Type CommentType |
||||
PosterID int64 |
||||
Poster *User `xorm:"-"` |
||||
IssueID int64 `xorm:"INDEX"` |
||||
CommitID int64 |
||||
Line int64 |
||||
Content string `xorm:"TEXT"` |
||||
RenderedContent string `xorm:"-"` |
||||
Created time.Time `xorm:"CREATED"` |
||||
|
||||
// Reference issue in commit message
|
||||
CommitSHA string `xorm:"VARCHAR(40)"` |
||||
|
||||
Attachments []*Attachment `xorm:"-"` |
||||
|
||||
// For view issue page.
|
||||
ShowTag CommentTag `xorm:"-"` |
||||
} |
||||
|
||||
func (c *Comment) AfterSet(colName string, _ xorm.Cell) { |
||||
var err error |
||||
switch colName { |
||||
case "id": |
||||
c.Attachments, err = GetAttachmentsByCommentID(c.ID) |
||||
if err != nil { |
||||
log.Error(3, "GetAttachmentsByCommentID[%d]: %v", c.ID, err) |
||||
} |
||||
|
||||
case "poster_id": |
||||
c.Poster, err = GetUserByID(c.PosterID) |
||||
if err != nil { |
||||
if IsErrUserNotExist(err) { |
||||
c.PosterID = -1 |
||||
c.Poster = NewFakeUser() |
||||
} else { |
||||
log.Error(3, "GetUserByID[%d]: %v", c.ID, err) |
||||
} |
||||
} |
||||
case "created": |
||||
c.Created = regulateTimeZone(c.Created) |
||||
} |
||||
} |
||||
|
||||
func (c *Comment) AfterDelete() { |
||||
_, 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) |
||||
} |
||||
} |
||||
|
||||
// HashTag returns unique hash tag for comment.
|
||||
func (c *Comment) HashTag() string { |
||||
return "issuecomment-" + com.ToStr(c.ID) |
||||
} |
||||
|
||||
// EventTag returns unique event hash tag for comment.
|
||||
func (c *Comment) EventTag() string { |
||||
return "event-" + com.ToStr(c.ID) |
||||
} |
||||
|
||||
func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { |
||||
comment := &Comment{ |
||||
Type: opts.Type, |
||||
PosterID: opts.Doer.Id, |
||||
IssueID: opts.Issue.ID, |
||||
CommitID: opts.CommitID, |
||||
CommitSHA: opts.CommitSHA, |
||||
Line: opts.LineNum, |
||||
Content: opts.Content, |
||||
} |
||||
if _, err = e.Insert(comment); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// Compose comment action, could be plain comment, close or reopen issue.
|
||||
// This object will be used to notify watchers in the end of function.
|
||||
act := &Action{ |
||||
ActUserID: opts.Doer.Id, |
||||
ActUserName: opts.Doer.Name, |
||||
ActEmail: opts.Doer.Email, |
||||
Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]), |
||||
RepoID: opts.Repo.ID, |
||||
RepoUserName: opts.Repo.Owner.Name, |
||||
RepoName: opts.Repo.Name, |
||||
IsPrivate: opts.Repo.IsPrivate, |
||||
} |
||||
|
||||
// Check comment type.
|
||||
switch opts.Type { |
||||
case COMMENT_TYPE_COMMENT: |
||||
act.OpType = ACTION_COMMENT_ISSUE |
||||
|
||||
if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// Check attachments
|
||||
attachments := make([]*Attachment, 0, len(opts.Attachments)) |
||||
for _, uuid := range opts.Attachments { |
||||
attach, err := getAttachmentByUUID(e, uuid) |
||||
if err != nil { |
||||
if IsErrAttachmentNotExist(err) { |
||||
continue |
||||
} |
||||
return nil, fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err) |
||||
} |
||||
attachments = append(attachments, attach) |
||||
} |
||||
|
||||
for i := range attachments { |
||||
attachments[i].IssueID = opts.Issue.ID |
||||
attachments[i].CommentID = comment.ID |
||||
// No assign value could be 0, so ignore AllCols().
|
||||
if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil { |
||||
return nil, fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err) |
||||
} |
||||
} |
||||
|
||||
case COMMENT_TYPE_REOPEN: |
||||
act.OpType = ACTION_REOPEN_ISSUE |
||||
if opts.Issue.IsPull { |
||||
act.OpType = ACTION_REOPEN_PULL_REQUEST |
||||
} |
||||
|
||||
if opts.Issue.IsPull { |
||||
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID) |
||||
} else { |
||||
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID) |
||||
} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
case COMMENT_TYPE_CLOSE: |
||||
act.OpType = ACTION_CLOSE_ISSUE |
||||
if opts.Issue.IsPull { |
||||
act.OpType = ACTION_CLOSE_PULL_REQUEST |
||||
} |
||||
|
||||
if opts.Issue.IsPull { |
||||
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID) |
||||
} else { |
||||
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID) |
||||
} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
// Notify watchers for whatever action comes in
|
||||
if err = notifyWatchers(e, act); err != nil { |
||||
return nil, fmt.Errorf("notifyWatchers: %v", err) |
||||
} |
||||
|
||||
return comment, nil |
||||
} |
||||
|
||||
func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) { |
||||
cmtType := COMMENT_TYPE_CLOSE |
||||
if !issue.IsClosed { |
||||
cmtType = COMMENT_TYPE_REOPEN |
||||
} |
||||
return createComment(e, &CreateCommentOptions{ |
||||
Type: cmtType, |
||||
Doer: doer, |
||||
Repo: repo, |
||||
Issue: issue, |
||||
}) |
||||
} |
||||
|
||||
type CreateCommentOptions struct { |
||||
Type CommentType |
||||
Doer *User |
||||
Repo *Repository |
||||
Issue *Issue |
||||
|
||||
CommitID int64 |
||||
CommitSHA string |
||||
LineNum int64 |
||||
Content string |
||||
Attachments []string // UUIDs of attachments
|
||||
} |
||||
|
||||
// CreateComment creates comment of issue or commit.
|
||||
func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) { |
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
comment, err = createComment(sess, opts) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return comment, sess.Commit() |
||||
} |
||||
|
||||
// CreateIssueComment creates a plain issue comment.
|
||||
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { |
||||
return CreateComment(&CreateCommentOptions{ |
||||
Type: COMMENT_TYPE_COMMENT, |
||||
Doer: doer, |
||||
Repo: repo, |
||||
Issue: issue, |
||||
Content: content, |
||||
Attachments: attachments, |
||||
}) |
||||
} |
||||
|
||||
// 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{ |
||||
Type: COMMENT_TYPE_COMMIT_REF, |
||||
IssueID: issue.ID, |
||||
CommitSHA: commitSHA, |
||||
}) |
||||
if err != nil { |
||||
return fmt.Errorf("check reference comment: %v", err) |
||||
} else if has { |
||||
return nil |
||||
} |
||||
|
||||
_, err = CreateComment(&CreateCommentOptions{ |
||||
Type: COMMENT_TYPE_COMMIT_REF, |
||||
Doer: doer, |
||||
Repo: repo, |
||||
Issue: issue, |
||||
CommitSHA: commitSHA, |
||||
Content: content, |
||||
}) |
||||
return err |
||||
} |
||||
|
||||
// GetCommentByID returns the comment by given ID.
|
||||
func GetCommentByID(id int64) (*Comment, error) { |
||||
c := new(Comment) |
||||
has, err := x.Id(id).Get(c) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if !has { |
||||
return nil, ErrCommentNotExist{id} |
||||
} |
||||
return c, nil |
||||
} |
||||
|
||||
// GetCommentsByIssueID returns all comments of issue by given ID.
|
||||
func GetCommentsByIssueID(issueID int64) ([]*Comment, error) { |
||||
comments := make([]*Comment, 0, 10) |
||||
return comments, x.Where("issue_id=?", issueID).Asc("created").Find(&comments) |
||||
} |
||||
|
||||
// UpdateComment updates information of comment.
|
||||
func UpdateComment(c *Comment) error { |
||||
_, err := x.Id(c.ID).AllCols().Update(c) |
||||
return err |
||||
} |
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue