mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28:37 +00:00
Merge pull request #307 from nuss-justin/feature/attachments
Feature: Ability to attach files to issues (attachments)
This commit is contained in:
@@ -127,7 +127,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
|
||||
url := fmt.Sprintf("/%s/%s/commit/%s", repoUserName, repoName, c.Sha1)
|
||||
message := fmt.Sprintf(`<a href="%s">%s</a>`, url, c.Message)
|
||||
|
||||
if err = CreateComment(userId, issue.RepoId, issue.Id, 0, 0, COMMIT, message); err != nil {
|
||||
if _, err = CreateComment(userId, issue.RepoId, issue.Id, 0, 0, COMMIT, message, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
|
||||
}
|
||||
|
||||
// If commit happened in the referenced repository, it means the issue can be closed.
|
||||
if err = CreateComment(userId, repoId, issue.Id, 0, 0, CLOSE, ""); err != nil {
|
||||
if _, err = CreateComment(userId, repoId, issue.Id, 0, 0, CLOSE, "", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
195
models/issue.go
195
models/issue.go
@@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"html/template"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -15,14 +16,17 @@ import (
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIssueNotExist = errors.New("Issue does not exist")
|
||||
ErrLabelNotExist = errors.New("Label does not exist")
|
||||
ErrMilestoneNotExist = errors.New("Milestone does not exist")
|
||||
ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone")
|
||||
ErrMissingIssueNumber = errors.New("No issue number specified")
|
||||
ErrIssueNotExist = errors.New("Issue does not exist")
|
||||
ErrLabelNotExist = errors.New("Label does not exist")
|
||||
ErrMilestoneNotExist = errors.New("Milestone does not exist")
|
||||
ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone")
|
||||
ErrAttachmentNotExist = errors.New("Attachment does not exist")
|
||||
ErrAttachmentNotLinked = errors.New("Attachment does not belong to this issue")
|
||||
ErrMissingIssueNumber = errors.New("No issue number specified")
|
||||
)
|
||||
|
||||
// Issue represents an issue or pull request of repository.
|
||||
@@ -94,6 +98,19 @@ func (i *Issue) GetAssignee() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *Issue) Attachments() []*Attachment {
|
||||
a, _ := GetAttachmentsForIssue(i.Id)
|
||||
return a
|
||||
}
|
||||
|
||||
func (i *Issue) AfterDelete() {
|
||||
_, err := DeleteAttachmentsByIssue(i.Id, true)
|
||||
|
||||
if err != nil {
|
||||
log.Info("Could not delete files for issue #%d: %s", i.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateIssue creates new issue for repository.
|
||||
func NewIssue(issue *Issue) (err error) {
|
||||
sess := x.NewSession()
|
||||
@@ -871,17 +888,19 @@ type Comment struct {
|
||||
}
|
||||
|
||||
// CreateComment creates comment of issue or commit.
|
||||
func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType CommentType, content string) error {
|
||||
func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType CommentType, content string, attachments []int64) (*Comment, error) {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := sess.Insert(&Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
|
||||
CommitId: commitId, Line: line, Content: content}); err != nil {
|
||||
comment := &Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
|
||||
CommitId: commitId, Line: line, Content: content}
|
||||
|
||||
if _, err := sess.Insert(comment); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check comment type.
|
||||
@@ -890,22 +909,46 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
|
||||
rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
|
||||
if _, err := sess.Exec(rawSql, issueId); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(attachments) > 0 {
|
||||
rawSql = "UPDATE `attachment` SET comment_id = ? WHERE id IN (?)"
|
||||
|
||||
astrs := make([]string, 0, len(attachments))
|
||||
|
||||
for _, a := range attachments {
|
||||
astrs = append(astrs, strconv.FormatInt(a, 10))
|
||||
}
|
||||
|
||||
if _, err := sess.Exec(rawSql, comment.Id, strings.Join(astrs, ",")); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
case REOPEN:
|
||||
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
|
||||
if _, err := sess.Exec(rawSql, repoId); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
case CLOSE:
|
||||
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
|
||||
if _, err := sess.Exec(rawSql, repoId); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return sess.Commit()
|
||||
|
||||
return comment, sess.Commit()
|
||||
}
|
||||
|
||||
// GetCommentById returns the comment with the given id
|
||||
func GetCommentById(commentId int64) (*Comment, error) {
|
||||
c := &Comment{Id: commentId}
|
||||
_, err := x.Get(c)
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (c *Comment) ContentHtml() template.HTML {
|
||||
@@ -918,3 +961,127 @@ func GetIssueComments(issueId int64) ([]Comment, error) {
|
||||
err := x.Asc("created").Find(&comments, &Comment{IssueId: issueId})
|
||||
return comments, err
|
||||
}
|
||||
|
||||
// Attachments returns the attachments for this comment.
|
||||
func (c *Comment) Attachments() []*Attachment {
|
||||
a, _ := GetAttachmentsByComment(c.Id)
|
||||
return a
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
Id int64
|
||||
IssueId int64
|
||||
CommentId int64
|
||||
Name string
|
||||
Path string `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
}
|
||||
|
||||
// CreateAttachment creates a new attachment inside the database and
|
||||
func CreateAttachment(issueId, commentId int64, name, path string) (*Attachment, error) {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
if err := sess.Begin(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a := &Attachment{IssueId: issueId, CommentId: commentId, Name: name, Path: path}
|
||||
|
||||
if _, err := sess.Insert(a); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a, sess.Commit()
|
||||
}
|
||||
|
||||
// Attachment returns the attachment by given ID.
|
||||
func GetAttachmentById(id int64) (*Attachment, error) {
|
||||
m := &Attachment{Id: id}
|
||||
|
||||
has, err := x.Get(m)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !has {
|
||||
return nil, ErrAttachmentNotExist
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func GetAttachmentsForIssue(issueId int64) ([]*Attachment, error) {
|
||||
attachments := make([]*Attachment, 0, 10)
|
||||
err := x.Where("issue_id = ?", issueId).And("comment_id = 0").Find(&attachments)
|
||||
return attachments, err
|
||||
}
|
||||
|
||||
// GetAttachmentsByIssue returns a list of attachments for the given issue
|
||||
func GetAttachmentsByIssue(issueId int64) ([]*Attachment, error) {
|
||||
attachments := make([]*Attachment, 0, 10)
|
||||
err := x.Where("issue_id = ?", issueId).And("comment_id > 0").Find(&attachments)
|
||||
return attachments, err
|
||||
}
|
||||
|
||||
// GetAttachmentsByComment returns a list of attachments for the given comment
|
||||
func GetAttachmentsByComment(commentId int64) ([]*Attachment, error) {
|
||||
attachments := make([]*Attachment, 0, 10)
|
||||
err := x.Where("comment_id = ?", commentId).Find(&attachments)
|
||||
return attachments, err
|
||||
}
|
||||
|
||||
// DeleteAttachment deletes the given attachment and optionally the associated file.
|
||||
func DeleteAttachment(a *Attachment, remove bool) error {
|
||||
_, err := DeleteAttachments([]*Attachment{a}, remove)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAttachments deletes the given attachments and optionally the associated files.
|
||||
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
|
||||
for i, a := range attachments {
|
||||
if remove {
|
||||
if err := os.Remove(a.Path); err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := x.Delete(a.Id); err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
|
||||
return len(attachments), nil
|
||||
}
|
||||
|
||||
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
|
||||
func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) {
|
||||
attachments, err := GetAttachmentsByIssue(issueId)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return DeleteAttachments(attachments, remove)
|
||||
}
|
||||
|
||||
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
|
||||
func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) {
|
||||
attachments, err := GetAttachmentsByComment(commentId)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return DeleteAttachments(attachments, remove)
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ func init() {
|
||||
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow),
|
||||
new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser),
|
||||
new(Milestone), new(Label), new(HookTask), new(Team), new(OrgUser), new(TeamUser),
|
||||
new(UpdateTask))
|
||||
new(UpdateTask), new(Attachment))
|
||||
}
|
||||
|
||||
func LoadModelsConfig() {
|
||||
|
Reference in New Issue
Block a user