2014-03-21 00:04:56 +04:00
// Copyright 2014 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
2014-03-22 21:50:50 +04:00
import (
2014-05-08 00:51:14 +04:00
"bytes"
2014-03-23 00:00:46 +04:00
"errors"
2015-08-10 09:42:50 +03:00
"fmt"
2015-08-11 18:24:40 +03:00
"io"
"mime/multipart"
2014-07-23 23:15:47 +04:00
"os"
2015-08-11 18:24:40 +03:00
"path"
2014-07-23 23:15:47 +04:00
"strconv"
2014-03-22 21:50:50 +04:00
"strings"
"time"
2014-05-08 00:51:14 +04:00
2014-07-26 08:24:27 +04:00
"github.com/Unknwon/com"
2015-08-04 17:24:04 +03:00
"github.com/go-xorm/xorm"
2014-05-13 22:49:20 +04:00
2015-08-10 16:47:23 +03:00
"github.com/gogits/gogs/modules/base"
2014-07-23 23:15:47 +04:00
"github.com/gogits/gogs/modules/log"
2015-07-24 11:42:47 +03:00
"github.com/gogits/gogs/modules/setting"
2015-08-11 18:24:40 +03:00
gouuid "github.com/gogits/gogs/modules/uuid"
2014-03-22 21:50:50 +04:00
)
2014-03-23 00:00:46 +04:00
var (
2014-07-23 23:15:47 +04:00
ErrWrongIssueCounter = errors . New ( "Invalid number of issues for this milestone" )
ErrAttachmentNotLinked = errors . New ( "Attachment does not belong to this issue" )
2014-07-24 14:29:37 +04:00
ErrMissingIssueNumber = errors . New ( "No issue number specified" )
2014-03-23 00:00:46 +04:00
)
2014-03-22 21:50:50 +04:00
// Issue represents an issue or pull request of repository.
2014-03-21 00:04:56 +04:00
type Issue struct {
2015-07-24 16:02:49 +03:00
ID int64 ` xorm:"pk autoincr" `
2015-08-05 15:23:08 +03:00
RepoID int64 ` xorm:"INDEX" `
2014-03-29 18:24:42 +04:00
Index int64 // Index in one repository.
Name string
Repo * Repository ` xorm:"-" `
2015-08-05 15:23:08 +03:00
PosterID int64
2014-05-24 10:31:58 +04:00
Poster * User ` xorm:"-" `
Labels [ ] * Label ` xorm:"-" `
2015-08-05 15:23:08 +03:00
MilestoneID int64
Milestone * Milestone ` xorm:"-" `
AssigneeID int64
2014-05-08 20:24:11 +04:00
Assignee * User ` xorm:"-" `
IsRead bool ` xorm:"-" `
IsPull bool // Indicates whether is a pull request or not.
2014-03-29 18:24:42 +04:00
IsClosed bool
Content string ` xorm:"TEXT" `
RenderedContent string ` xorm:"-" `
2014-05-07 00:28:52 +04:00
Priority int
2014-03-29 18:24:42 +04:00
NumComments int
2014-05-07 00:28:52 +04:00
Deadline time . Time
2014-05-07 20:09:30 +04:00
Created time . Time ` xorm:"CREATED" `
Updated time . Time ` xorm:"UPDATED" `
2015-08-12 12:04:23 +03:00
Attachments [ ] * Attachment ` xorm:"-" `
2015-08-13 11:07:11 +03:00
Comments [ ] * Comment ` xorm:"-" `
2015-08-12 12:04:23 +03:00
}
2015-08-06 17:48:11 +03:00
func ( i * Issue ) AfterSet ( colName string , _ xorm . Cell ) {
2015-08-05 15:23:08 +03:00
var err error
switch colName {
2015-08-12 12:04:23 +03:00
case "id" :
i . Attachments , err = GetAttachmentsByIssueID ( i . ID )
if err != nil {
log . Error ( 3 , "GetAttachmentsByIssueID[%d]: %v" , i . ID , err )
}
2015-08-13 11:07:11 +03:00
i . Comments , err = GetCommentsByIssueID ( i . ID )
if err != nil {
log . Error ( 3 , "GetCommentsByIssueID[%d]: %v" , i . ID , err )
}
2015-08-05 15:23:08 +03:00
case "milestone_id" :
2015-08-10 16:47:23 +03:00
if i . MilestoneID == 0 {
return
}
2015-08-06 18:25:35 +03:00
i . Milestone , err = GetMilestoneByID ( i . MilestoneID )
2015-08-05 15:23:08 +03:00
if err != nil {
2015-08-12 12:04:23 +03:00
log . Error ( 3 , "GetMilestoneById[%d]: %v" , i . ID , err )
2015-08-05 15:23:08 +03:00
}
2015-08-10 16:47:23 +03:00
case "assignee_id" :
if i . AssigneeID == 0 {
return
}
i . Assignee , err = GetUserByID ( i . AssigneeID )
if err != nil {
2015-08-12 12:04:23 +03:00
log . Error ( 3 , "GetUserByID[%d]: %v" , i . ID , err )
2015-08-10 16:47:23 +03:00
}
2015-08-19 19:12:43 +03:00
case "created" :
2015-08-19 19:56:12 +03:00
i . Created = regulateTimeZone ( i . Created )
2015-08-05 15:23:08 +03:00
}
}
2015-08-19 19:12:43 +03:00
// HashTag returns unique hash tag for issue.
func ( i * Issue ) HashTag ( ) string {
return "issue-" + com . ToStr ( i . ID )
}
2015-08-13 11:07:11 +03:00
// IsPoster returns true if given user by ID is the poster.
func ( i * Issue ) IsPoster ( uid int64 ) bool {
return i . PosterID == uid
}
2014-05-07 20:09:30 +04:00
func ( i * Issue ) GetPoster ( ) ( err error ) {
2015-08-08 17:43:14 +03:00
i . Poster , err = GetUserByID ( i . PosterID )
2015-08-05 06:14:17 +03:00
if IsErrUserNotExist ( err ) {
2015-08-14 21:54:39 +03:00
i . PosterID = - 1
i . Poster = NewFakeUser ( )
2014-05-09 04:00:07 +04:00
return nil
}
2014-05-07 20:09:30 +04:00
return err
}
2015-08-10 09:42:50 +03:00
func ( i * Issue ) hasLabel ( e Engine , labelID int64 ) bool {
return hasIssueLabel ( e , i . ID , labelID )
}
// HasLabel returns true if issue has been labeled by given ID.
func ( i * Issue ) HasLabel ( labelID int64 ) bool {
return i . hasLabel ( x , labelID )
}
2015-08-14 19:42:43 +03:00
func ( i * Issue ) addLabel ( e * xorm . Session , label * Label ) error {
return newIssueLabel ( e , i , label )
2015-08-10 09:42:50 +03:00
}
// AddLabel adds new label to issue by given ID.
2015-08-14 19:42:43 +03:00
func ( i * Issue ) AddLabel ( label * Label ) ( err error ) {
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = i . addLabel ( sess , label ) ; err != nil {
return err
}
return sess . Commit ( )
2015-08-10 09:42:50 +03:00
}
func ( i * Issue ) getLabels ( e Engine ) ( err error ) {
if len ( i . Labels ) > 0 {
2014-05-24 10:31:58 +04:00
return nil
}
2015-08-10 09:42:50 +03:00
i . Labels , err = getLabelsByIssueID ( e , i . ID )
if err != nil {
return fmt . Errorf ( "getLabelsByIssueID: %v" , err )
2014-05-24 10:31:58 +04:00
}
return nil
}
2015-08-10 09:42:50 +03:00
// GetLabels retrieves all labels of issue and assign to corresponding field.
func ( i * Issue ) GetLabels ( ) error {
return i . getLabels ( x )
}
2015-08-14 19:42:43 +03:00
func ( i * Issue ) removeLabel ( e * xorm . Session , label * Label ) error {
return deleteIssueLabel ( e , i , label )
2015-08-10 09:42:50 +03:00
}
// RemoveLabel removes a label from issue by given ID.
2015-08-14 19:42:43 +03:00
func ( i * Issue ) RemoveLabel ( label * Label ) ( err error ) {
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = i . removeLabel ( sess , label ) ; err != nil {
return err
}
return sess . Commit ( )
}
func ( i * Issue ) ClearLabels ( ) ( err error ) {
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = i . getLabels ( sess ) ; err != nil {
return err
}
for idx := range i . Labels {
if err = i . removeLabel ( sess , i . Labels [ idx ] ) ; err != nil {
return err
}
}
return sess . Commit ( )
2015-08-10 09:42:50 +03:00
}
2014-05-08 20:24:11 +04:00
func ( i * Issue ) GetAssignee ( ) ( err error ) {
2015-08-10 16:47:23 +03:00
if i . AssigneeID == 0 || i . Assignee != nil {
2014-05-08 20:24:11 +04:00
return nil
}
2015-08-05 15:26:00 +03:00
2015-08-08 17:43:14 +03:00
i . Assignee , err = GetUserByID ( i . AssigneeID )
2015-08-05 06:14:17 +03:00
if IsErrUserNotExist ( err ) {
2014-05-24 10:31:58 +04:00
return nil
}
2014-05-08 20:24:11 +04:00
return err
}
2015-08-12 13:44:09 +03:00
// ReadBy sets issue to be read by given user.
func ( i * Issue ) ReadBy ( uid int64 ) error {
return UpdateIssueUserByRead ( uid , i . ID )
2014-07-23 23:15:47 +04:00
}
2015-08-13 11:07:11 +03:00
func ( i * Issue ) changeStatus ( e * xorm . Session , doer * User , isClosed bool ) ( err error ) {
if i . IsClosed == isClosed {
return nil
}
i . IsClosed = isClosed
if err = updateIssue ( e , i ) ; err != nil {
return err
} else if err = updateIssueUsersByStatus ( e , i . ID , isClosed ) ; err != nil {
return err
}
// Update labels.
if err = i . getLabels ( e ) ; err != nil {
return err
}
for idx := range i . Labels {
if i . IsClosed {
i . Labels [ idx ] . NumClosedIssues ++
} else {
i . Labels [ idx ] . NumClosedIssues --
}
if err = updateLabel ( e , i . Labels [ idx ] ) ; err != nil {
return err
}
}
// Update milestone.
if err = changeMilestoneIssueStats ( e , i ) ; err != nil {
return err
}
// New action comment.
if _ , err = createStatusComment ( e , doer , i . Repo , i ) ; err != nil {
return err
}
return nil
}
// ChangeStatus changes issue status to open/closed.
func ( i * Issue ) ChangeStatus ( doer * User , isClosed bool ) ( err error ) {
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = i . changeStatus ( sess , doer , isClosed ) ; err != nil {
return err
}
return sess . Commit ( )
}
2015-08-10 11:52:08 +03:00
// CreateIssue creates new issue with labels for repository.
2015-08-11 18:24:40 +03:00
func NewIssue ( repo * Repository , issue * Issue , labelIDs [ ] int64 , uuids [ ] string ) ( err error ) {
// Check attachments.
attachments := make ( [ ] * Attachment , 0 , len ( uuids ) )
for _ , uuid := range uuids {
attach , err := GetAttachmentByUUID ( uuid )
if err != nil {
if IsErrAttachmentNotExist ( err ) {
continue
}
return fmt . Errorf ( "GetAttachmentByUUID[%s]: %v" , uuid , err )
}
attachments = append ( attachments , attach )
}
2014-06-21 08:51:41 +04:00
sess := x . NewSession ( )
2015-07-24 11:42:47 +03:00
defer sessionRelease ( sess )
2014-05-07 20:09:30 +04:00
if err = sess . Begin ( ) ; err != nil {
return err
2014-03-23 00:00:46 +04:00
}
2014-05-07 20:09:30 +04:00
2014-03-27 20:48:29 +04:00
if _ , err = sess . Insert ( issue ) ; err != nil {
2014-05-07 20:09:30 +04:00
return err
2015-08-09 10:23:02 +03:00
} else if _ , err = sess . Exec ( "UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?" , issue . RepoID ) ; err != nil {
2014-05-07 20:09:30 +04:00
return err
2014-03-27 20:48:29 +04:00
}
2014-07-22 15:50:34 +04:00
2015-08-14 19:42:43 +03:00
var label * Label
2015-08-10 11:52:08 +03:00
for _ , id := range labelIDs {
2015-08-14 19:42:43 +03:00
if id == 0 {
continue
}
label , err = getLabelByID ( sess , id )
if err != nil {
return err
}
if err = issue . addLabel ( sess , label ) ; err != nil {
2015-08-10 11:52:08 +03:00
return fmt . Errorf ( "addLabel: %v" , err )
}
2015-08-14 19:42:43 +03:00
2015-08-10 11:52:08 +03:00
}
2015-08-05 15:23:08 +03:00
if issue . MilestoneID > 0 {
2015-08-10 13:57:57 +03:00
if err = changeMilestoneAssign ( sess , 0 , issue ) ; err != nil {
return err
}
2014-07-22 15:50:34 +04:00
}
2015-08-10 16:47:23 +03:00
if err = newIssueUsers ( sess , repo , issue ) ; err != nil {
return err
}
2015-08-11 18:24:40 +03:00
for i := range attachments {
attachments [ i ] . IssueID = issue . ID
// No assign value could be 0, so ignore AllCols().
if _ , err = sess . Id ( attachments [ i ] . ID ) . Update ( attachments [ i ] ) ; err != nil {
return fmt . Errorf ( "update attachment[%d]: %v" , attachments [ i ] . ID , err )
}
}
2015-08-10 18:31:59 +03:00
// Notify watchers.
act := & Action {
ActUserID : issue . Poster . Id ,
ActUserName : issue . Poster . Name ,
ActEmail : issue . Poster . Email ,
OpType : CREATE_ISSUE ,
Content : fmt . Sprintf ( "%d|%s" , issue . Index , issue . Name ) ,
RepoID : repo . ID ,
RepoUserName : repo . Owner . Name ,
RepoName : repo . Name ,
IsPrivate : repo . IsPrivate ,
}
if err = notifyWatchers ( sess , act ) ; err != nil {
return err
}
2015-08-10 13:57:57 +03:00
return sess . Commit ( )
2014-03-21 00:04:56 +04:00
}
2014-07-23 15:48:06 +04:00
// GetIssueByRef returns an Issue specified by a GFM reference.
// See https://help.github.com/articles/writing-on-github#references for more information on the syntax.
func GetIssueByRef ( ref string ) ( issue * Issue , err error ) {
var issueNumber int64
var repo * Repository
n := strings . IndexByte ( ref , byte ( '#' ) )
if n == - 1 {
return nil , ErrMissingIssueNumber
}
if issueNumber , err = strconv . ParseInt ( ref [ n + 1 : ] , 10 , 64 ) ; err != nil {
return
}
if repo , err = GetRepositoryByRef ( ref [ : n ] ) ; err != nil {
return
}
2015-08-08 17:43:14 +03:00
return GetIssueByIndex ( repo . ID , issueNumber )
2014-07-23 15:48:06 +04:00
}
2014-05-07 20:09:30 +04:00
// GetIssueByIndex returns issue by given index in repository.
2015-08-12 12:04:23 +03:00
func GetIssueByIndex ( repoID , index int64 ) ( * Issue , error ) {
issue := & Issue {
RepoID : repoID ,
Index : index ,
}
2014-06-21 08:51:41 +04:00
has , err := x . Get ( issue )
2014-03-23 00:00:46 +04:00
if err != nil {
return nil , err
} else if ! has {
2015-08-12 12:04:23 +03:00
return nil , ErrIssueNotExist { 0 , repoID , index }
2014-03-23 00:00:46 +04:00
}
return issue , nil
}
2015-08-12 12:04:23 +03:00
// GetIssueByID returns an issue by given ID.
func GetIssueByID ( id int64 ) ( * Issue , error ) {
issue := new ( Issue )
has , err := x . Id ( id ) . Get ( issue )
2014-05-08 00:51:14 +04:00
if err != nil {
return nil , err
} else if ! has {
2015-08-12 12:04:23 +03:00
return nil , ErrIssueNotExist { id , 0 , 0 }
2014-05-08 00:51:14 +04:00
}
return issue , nil
}
2015-08-04 17:24:04 +03:00
// Issues returns a list of issues by given conditions.
2015-08-10 16:47:23 +03:00
func Issues ( uid , assigneeID , repoID , posterID , milestoneID int64 , page int , isClosed , isMention bool , labels , sortType string ) ( [ ] * Issue , error ) {
2015-07-24 11:42:47 +03:00
sess := x . Limit ( setting . IssuePagingNum , ( page - 1 ) * setting . IssuePagingNum )
2014-03-23 00:00:46 +04:00
2015-07-24 21:52:25 +03:00
if repoID > 0 {
sess . Where ( "issue.repo_id=?" , repoID ) . And ( "issue.is_closed=?" , isClosed )
2014-03-23 00:00:46 +04:00
} else {
2015-07-24 21:52:25 +03:00
sess . Where ( "issue.is_closed=?" , isClosed )
2014-03-23 00:00:46 +04:00
}
2015-07-24 21:52:25 +03:00
if assigneeID > 0 {
sess . And ( "issue.assignee_id=?" , assigneeID )
} else if posterID > 0 {
sess . And ( "issue.poster_id=?" , posterID )
2014-03-22 21:50:50 +04:00
}
2015-07-24 21:52:25 +03:00
if milestoneID > 0 {
sess . And ( "issue.milestone_id=?" , milestoneID )
2014-03-22 21:50:50 +04:00
}
switch sortType {
case "oldest" :
2014-03-23 14:27:01 +04:00
sess . Asc ( "created" )
2014-03-22 21:50:50 +04:00
case "recentupdate" :
2014-03-23 14:27:01 +04:00
sess . Desc ( "updated" )
2014-03-22 21:50:50 +04:00
case "leastupdate" :
2014-03-23 14:27:01 +04:00
sess . Asc ( "updated" )
2014-03-22 21:50:50 +04:00
case "mostcomment" :
2014-03-23 14:27:01 +04:00
sess . Desc ( "num_comments" )
2014-03-22 21:50:50 +04:00
case "leastcomment" :
2014-03-23 14:27:01 +04:00
sess . Asc ( "num_comments" )
2014-05-08 04:36:00 +04:00
case "priority" :
sess . Desc ( "priority" )
2014-03-22 21:50:50 +04:00
default :
2014-03-23 14:27:01 +04:00
sess . Desc ( "created" )
2014-03-22 21:50:50 +04:00
}
2015-08-10 16:47:23 +03:00
labelIDs := base . StringsToInt64s ( strings . Split ( labels , "," ) )
if len ( labelIDs ) > 0 {
validJoin := false
queryStr := "issue.id=issue_label.issue_id"
for _ , id := range labelIDs {
if id == 0 {
continue
}
validJoin = true
queryStr += " AND issue_label.label_id=" + com . ToStr ( id )
}
if validJoin {
sess . Join ( "INNER" , "issue_label" , queryStr )
}
}
2015-07-24 21:52:25 +03:00
if isMention {
2015-08-10 16:47:23 +03:00
queryStr := "issue.id=issue_user.issue_id AND issue_user.is_mentioned=1"
2015-07-24 21:52:25 +03:00
if uid > 0 {
2015-08-10 16:47:23 +03:00
queryStr += " AND issue_user.uid=" + com . ToStr ( uid )
2015-07-24 21:52:25 +03:00
}
sess . Join ( "INNER" , "issue_user" , queryStr )
}
2015-08-04 17:24:04 +03:00
issues := make ( [ ] * Issue , 0 , setting . IssuePagingNum )
2015-07-24 11:42:47 +03:00
return issues , sess . Find ( & issues )
2014-03-22 21:50:50 +04:00
}
2014-05-24 11:05:41 +04:00
type IssueStatus int
const (
IS_OPEN = iota + 1
IS_CLOSE
)
2014-05-08 00:51:14 +04:00
// GetIssueCountByPoster returns number of issues of repository by poster.
func GetIssueCountByPoster ( uid , rid int64 , isClosed bool ) int64 {
2014-06-21 08:51:41 +04:00
count , _ := x . Where ( "repo_id=?" , rid ) . And ( "poster_id=?" , uid ) . And ( "is_closed=?" , isClosed ) . Count ( new ( Issue ) )
2014-05-08 00:51:14 +04:00
return count
}
2014-05-15 20:08:53 +04:00
// .___ ____ ___
// | | ______ ________ __ ____ | | \______ ___________
// | |/ ___// ___/ | \_/ __ \| | / ___// __ \_ __ \
// | |\___ \ \___ \| | /\ ___/| | /\___ \\ ___/| | \/
// |___/____ >____ >____/ \___ >______//____ >\___ >__|
// \/ \/ \/ \/ \/
2014-05-08 00:51:14 +04:00
// IssueUser represents an issue-user relation.
type IssueUser struct {
2015-08-10 16:47:23 +03:00
ID int64 ` xorm:"pk autoincr" `
UID int64 ` xorm:"uid INDEX" ` // User ID.
IssueID int64
RepoID int64 ` xorm:"INDEX" `
MilestoneID int64
2014-05-08 00:51:14 +04:00
IsRead bool
IsAssigned bool
IsMentioned bool
IsPoster bool
IsClosed bool
}
2015-08-10 16:47:23 +03:00
func newIssueUsers ( e * xorm . Session , repo * Repository , issue * Issue ) error {
users , err := repo . GetAssignees ( )
2014-05-08 00:51:14 +04:00
if err != nil {
return err
}
2015-02-12 05:58:37 +03:00
iu := & IssueUser {
2015-08-10 16:47:23 +03:00
IssueID : issue . ID ,
RepoID : repo . ID ,
2015-02-12 05:58:37 +03:00
}
2015-08-10 16:47:23 +03:00
// Poster can be anyone.
2014-05-08 20:24:11 +04:00
isNeedAddPoster := true
2015-02-12 05:58:37 +03:00
for _ , u := range users {
2015-08-10 16:47:23 +03:00
iu . ID = 0
iu . UID = u . Id
iu . IsPoster = iu . UID == issue . PosterID
2014-05-08 20:24:11 +04:00
if isNeedAddPoster && iu . IsPoster {
isNeedAddPoster = false
2014-05-08 00:51:14 +04:00
}
2015-08-10 16:47:23 +03:00
iu . IsAssigned = iu . UID == issue . AssigneeID
if _ , err = e . Insert ( iu ) ; err != nil {
2014-05-08 20:24:11 +04:00
return err
2014-05-08 00:51:14 +04:00
}
2014-05-08 20:24:11 +04:00
}
if isNeedAddPoster {
2015-08-10 16:47:23 +03:00
iu . ID = 0
iu . UID = issue . PosterID
2014-05-08 20:24:11 +04:00
iu . IsPoster = true
2015-08-10 16:47:23 +03:00
if _ , err = e . Insert ( iu ) ; err != nil {
2014-05-08 00:51:14 +04:00
return err
}
}
2015-08-10 16:47:23 +03:00
return nil
}
2014-05-08 20:24:11 +04:00
2015-08-10 16:47:23 +03:00
// NewIssueUsers adds new issue-user relations for new issue of repository.
func NewIssueUsers ( repo * Repository , issue * Issue ) ( err error ) {
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
2015-07-24 11:42:47 +03:00
}
2015-08-10 16:47:23 +03:00
if err = newIssueUsers ( sess , repo , issue ) ; err != nil {
return err
}
return sess . Commit ( )
2014-05-08 00:51:14 +04:00
}
2014-05-07 20:09:30 +04:00
// PairsContains returns true when pairs list contains given issue.
2015-07-24 11:42:47 +03:00
func PairsContains ( ius [ ] * IssueUser , issueId , uid int64 ) int {
2014-05-07 20:09:30 +04:00
for i := range ius {
2015-08-10 16:47:23 +03:00
if ius [ i ] . IssueID == issueId &&
ius [ i ] . UID == uid {
2014-05-08 00:51:14 +04:00
return i
2014-05-07 20:09:30 +04:00
}
}
2014-05-08 00:51:14 +04:00
return - 1
2014-05-07 20:09:30 +04:00
}
2015-08-10 17:45:49 +03:00
// GetIssueUsers returns issue-user pairs by given repository and user.
func GetIssueUsers ( rid , uid int64 , isClosed bool ) ( [ ] * IssueUser , error ) {
2014-05-08 00:51:14 +04:00
ius := make ( [ ] * IssueUser , 0 , 10 )
2015-08-10 16:47:23 +03:00
err := x . Where ( "is_closed=?" , isClosed ) . Find ( & ius , & IssueUser { RepoID : rid , UID : uid } )
2014-05-07 20:09:30 +04:00
return ius , err
}
2014-05-08 00:51:14 +04:00
// GetIssueUserPairsByRepoIds returns issue-user pairs by given repository IDs.
func GetIssueUserPairsByRepoIds ( rids [ ] int64 , isClosed bool , page int ) ( [ ] * IssueUser , error ) {
2014-07-07 02:25:07 +04:00
if len ( rids ) == 0 {
return [ ] * IssueUser { } , nil
}
2014-05-08 00:51:14 +04:00
buf := bytes . NewBufferString ( "" )
for _ , rid := range rids {
buf . WriteString ( "repo_id=" )
2014-07-26 08:24:27 +04:00
buf . WriteString ( com . ToStr ( rid ) )
2014-05-08 00:51:14 +04:00
buf . WriteString ( " OR " )
}
cond := strings . TrimSuffix ( buf . String ( ) , " OR " )
ius := make ( [ ] * IssueUser , 0 , 10 )
2014-06-21 08:51:41 +04:00
sess := x . Limit ( 20 , ( page - 1 ) * 20 ) . Where ( "is_closed=?" , isClosed )
2014-05-08 00:51:14 +04:00
if len ( cond ) > 0 {
sess . And ( cond )
}
err := sess . Find ( & ius )
return ius , err
}
// GetIssueUserPairsByMode returns issue-user pairs by given repository and user.
func GetIssueUserPairsByMode ( uid , rid int64 , isClosed bool , page , filterMode int ) ( [ ] * IssueUser , error ) {
ius := make ( [ ] * IssueUser , 0 , 10 )
2014-06-21 08:51:41 +04:00
sess := x . Limit ( 20 , ( page - 1 ) * 20 ) . Where ( "uid=?" , uid ) . And ( "is_closed=?" , isClosed )
2014-05-08 00:51:14 +04:00
if rid > 0 {
sess . And ( "repo_id=?" , rid )
}
switch filterMode {
case FM_ASSIGN :
sess . And ( "is_assigned=?" , true )
case FM_CREATE :
sess . And ( "is_poster=?" , true )
default :
return ius , nil
}
err := sess . Find ( & ius )
return ius , err
2014-03-28 00:31:32 +04:00
}
2014-05-07 20:09:30 +04:00
// IssueStats represents issue statistic information.
type IssueStats struct {
OpenCount , ClosedCount int64
AllCount int64
AssignCount int64
CreateCount int64
MentionCount int64
}
// Filter modes.
const (
2015-07-24 21:52:25 +03:00
FM_ALL = iota
FM_ASSIGN
2014-05-07 20:09:30 +04:00
FM_CREATE
FM_MENTION
)
2015-08-10 16:47:23 +03:00
func parseCountResult ( results [ ] map [ string ] [ ] byte ) int64 {
if len ( results ) == 0 {
return 0
}
for _ , result := range results [ 0 ] {
return com . StrTo ( string ( result ) ) . MustInt64 ( )
}
return 0
}
2014-05-08 00:51:14 +04:00
// GetIssueStats returns issue statistic information by given conditions.
2015-08-15 07:07:08 +03:00
func GetIssueStats ( repoID , uid , labelID , milestoneID , assigneeID int64 , isShowClosed bool , filterMode int ) * IssueStats {
2014-05-07 20:09:30 +04:00
stats := & IssueStats { }
2015-08-10 16:47:23 +03:00
// issue := new(Issue)
2015-07-24 21:52:25 +03:00
2015-08-10 16:47:23 +03:00
queryStr := "SELECT COUNT(*) FROM `issue` "
2015-07-25 08:07:00 +03:00
if labelID > 0 {
2015-08-10 16:47:23 +03:00
queryStr += "INNER JOIN `issue_label` ON `issue`.id=`issue_label`.issue_id AND `issue_label`.label_id=" + com . ToStr ( labelID )
2015-07-25 08:07:00 +03:00
}
2015-08-10 16:47:23 +03:00
baseCond := " WHERE issue.repo_id=? AND issue.is_closed=?"
2015-08-05 15:52:17 +03:00
if milestoneID > 0 {
2015-08-10 16:47:23 +03:00
baseCond += " AND issue.milestone_id=" + com . ToStr ( milestoneID )
2015-08-05 15:52:17 +03:00
}
2015-08-15 07:07:08 +03:00
if assigneeID > 0 {
baseCond += " AND assignee_id=" + com . ToStr ( assigneeID )
}
2015-07-24 21:52:25 +03:00
switch filterMode {
2015-08-15 07:07:08 +03:00
case FM_ALL , FM_ASSIGN :
2015-08-10 16:47:23 +03:00
resutls , _ := x . Query ( queryStr + baseCond , repoID , false )
stats . OpenCount = parseCountResult ( resutls )
resutls , _ = x . Query ( queryStr + baseCond , repoID , true )
stats . ClosedCount = parseCountResult ( resutls )
2015-07-24 21:52:25 +03:00
case FM_CREATE :
2015-08-10 16:47:23 +03:00
baseCond += " AND poster_id=?"
resutls , _ := x . Query ( queryStr + baseCond , repoID , false , uid )
stats . OpenCount = parseCountResult ( resutls )
resutls , _ = x . Query ( queryStr + baseCond , repoID , true , uid )
stats . ClosedCount = parseCountResult ( resutls )
2015-07-24 21:52:25 +03:00
case FM_MENTION :
2015-08-10 16:47:23 +03:00
queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id"
baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?"
resutls , _ := x . Query ( queryStr + baseCond , repoID , false , uid , true )
stats . OpenCount = parseCountResult ( resutls )
resutls , _ = x . Query ( queryStr + baseCond , repoID , true , uid , true )
stats . ClosedCount = parseCountResult ( resutls )
2015-07-24 21:52:25 +03:00
}
2014-05-07 20:09:30 +04:00
return stats
}
2014-05-08 00:51:14 +04:00
// GetUserIssueStats returns issue statistic information for dashboard by given conditions.
2014-05-07 20:09:30 +04:00
func GetUserIssueStats ( uid int64 , filterMode int ) * IssueStats {
stats := & IssueStats { }
issue := new ( Issue )
2014-06-21 08:51:41 +04:00
stats . AssignCount , _ = x . Where ( "assignee_id=?" , uid ) . And ( "is_closed=?" , false ) . Count ( issue )
stats . CreateCount , _ = x . Where ( "poster_id=?" , uid ) . And ( "is_closed=?" , false ) . Count ( issue )
2014-05-07 20:09:30 +04:00
return stats
}
2015-08-10 13:57:57 +03:00
func updateIssue ( e Engine , issue * Issue ) error {
_ , err := e . Id ( issue . ID ) . AllCols ( ) . Update ( issue )
return err
}
2014-03-24 03:09:11 +04:00
// UpdateIssue updates information of issue.
func UpdateIssue ( issue * Issue ) error {
2015-08-10 13:57:57 +03:00
return updateIssue ( x , issue )
2014-05-08 00:51:14 +04:00
}
2015-08-13 11:07:11 +03:00
func updateIssueUsersByStatus ( e Engine , issueID int64 , isClosed bool ) error {
_ , err := e . Exec ( "UPDATE `issue_user` SET is_closed=? WHERE issue_id=?" , isClosed , issueID )
2014-03-24 03:09:11 +04:00
return err
}
2015-08-13 11:07:11 +03:00
// UpdateIssueUsersByStatus updates issue-user relations by issue status.
func UpdateIssueUsersByStatus ( issueID int64 , isClosed bool ) error {
return updateIssueUsersByStatus ( x , issueID , isClosed )
}
2015-08-14 19:42:43 +03:00
func updateIssueUserByAssignee ( e * xorm . Session , issue * Issue ) ( err error ) {
if _ , err = e . Exec ( "UPDATE `issue_user` SET is_assigned=? WHERE issue_id=?" , false , issue . ID ) ; err != nil {
2014-05-09 01:17:45 +04:00
return err
}
2014-05-11 21:46:36 +04:00
// Assignee ID equals to 0 means clear assignee.
2015-08-14 19:42:43 +03:00
if issue . AssigneeID > 0 {
if _ , err = e . Exec ( "UPDATE `issue_user` SET is_assigned=? WHERE uid=? AND issue_id=?" , true , issue . AssigneeID , issue . ID ) ; err != nil {
return err
}
2014-05-11 21:46:36 +04:00
}
2015-08-14 19:42:43 +03:00
return updateIssue ( e , issue )
2014-05-09 01:17:45 +04:00
}
2015-08-10 16:47:23 +03:00
// UpdateIssueUserByAssignee updates issue-user relation for assignee.
2015-08-14 19:42:43 +03:00
func UpdateIssueUserByAssignee ( issue * Issue ) ( err error ) {
2015-08-10 16:47:23 +03:00
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
2015-08-14 19:42:43 +03:00
if err = updateIssueUserByAssignee ( sess , issue ) ; err != nil {
2015-08-10 16:47:23 +03:00
return err
}
return sess . Commit ( )
}
2015-08-12 13:44:09 +03:00
// UpdateIssueUserByRead updates issue-user relation for reading.
func UpdateIssueUserByRead ( uid , issueID int64 ) error {
_ , err := x . Exec ( "UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?" , true , uid , issueID )
2014-05-08 00:51:14 +04:00
return err
}
2015-08-10 18:31:59 +03:00
// UpdateIssueUsersByMentions updates issue-user pairs by mentioning.
func UpdateIssueUsersByMentions ( uids [ ] int64 , iid int64 ) error {
2014-05-08 00:51:14 +04:00
for _ , uid := range uids {
2015-08-10 16:47:23 +03:00
iu := & IssueUser { UID : uid , IssueID : iid }
2014-06-21 08:51:41 +04:00
has , err := x . Get ( iu )
2014-05-08 00:51:14 +04:00
if err != nil {
return err
}
iu . IsMentioned = true
if has {
2015-08-10 16:47:23 +03:00
_ , err = x . Id ( iu . ID ) . AllCols ( ) . Update ( iu )
2014-05-08 00:51:14 +04:00
} else {
2014-06-21 08:51:41 +04:00
_ , err = x . Insert ( iu )
2014-05-08 00:51:14 +04:00
}
if err != nil {
return err
}
}
return nil
}
2014-05-24 10:31:58 +04:00
// .____ ___. .__
// | | _____ \_ |__ ____ | |
// | | \__ \ | __ \_/ __ \| |
// | |___ / __ \| \_\ \ ___/| |__
// |_______ (____ /___ /\___ >____/
// \/ \/ \/ \/
// Label represents a label of repository for issues.
type Label struct {
2015-07-24 16:02:49 +03:00
ID int64 ` xorm:"pk autoincr" `
2015-08-10 09:42:50 +03:00
RepoID int64 ` xorm:"INDEX" `
2014-05-24 10:31:58 +04:00
Name string
Color string ` xorm:"VARCHAR(7)" `
NumIssues int
NumClosedIssues int
NumOpenIssues int ` xorm:"-" `
IsChecked bool ` xorm:"-" `
}
// CalOpenIssues calculates the open issues of label.
func ( m * Label ) CalOpenIssues ( ) {
m . NumOpenIssues = m . NumIssues - m . NumClosedIssues
}
// NewLabel creates new label of repository.
func NewLabel ( l * Label ) error {
2014-06-21 08:51:41 +04:00
_ , err := x . Insert ( l )
2014-05-24 10:31:58 +04:00
return err
}
2015-08-10 09:42:50 +03:00
func getLabelByID ( e Engine , id int64 ) ( * Label , error ) {
2014-05-24 23:34:02 +04:00
if id <= 0 {
2015-08-10 09:42:50 +03:00
return nil , ErrLabelNotExist { id }
2014-05-24 23:34:02 +04:00
}
2015-07-24 16:02:49 +03:00
l := & Label { ID : id }
2014-06-21 08:51:41 +04:00
has , err := x . Get ( l )
2014-05-24 10:31:58 +04:00
if err != nil {
return nil , err
} else if ! has {
2015-08-10 09:42:50 +03:00
return nil , ErrLabelNotExist { l . ID }
2014-05-24 10:31:58 +04:00
}
return l , nil
}
2015-08-10 09:42:50 +03:00
// GetLabelByID returns a label by given ID.
func GetLabelByID ( id int64 ) ( * Label , error ) {
return getLabelByID ( x , id )
}
// GetLabelsByRepoID returns all labels that belong to given repository by ID.
func GetLabelsByRepoID ( repoID int64 ) ( [ ] * Label , error ) {
2014-05-24 10:31:58 +04:00
labels := make ( [ ] * Label , 0 , 10 )
2015-08-10 09:42:50 +03:00
return labels , x . Where ( "repo_id=?" , repoID ) . Find ( & labels )
}
func getLabelsByIssueID ( e Engine , issueID int64 ) ( [ ] * Label , error ) {
issueLabels , err := getIssueLabels ( e , issueID )
if err != nil {
return nil , fmt . Errorf ( "getIssueLabels: %v" , err )
}
var label * Label
labels := make ( [ ] * Label , 0 , len ( issueLabels ) )
for idx := range issueLabels {
label , err = getLabelByID ( e , issueLabels [ idx ] . LabelID )
if err != nil && ! IsErrLabelNotExist ( err ) {
return nil , fmt . Errorf ( "getLabelByID: %v" , err )
}
labels = append ( labels , label )
}
return labels , nil
}
// GetLabelsByIssueID returns all labels that belong to given issue by ID.
func GetLabelsByIssueID ( issueID int64 ) ( [ ] * Label , error ) {
return getLabelsByIssueID ( x , issueID )
2014-05-24 10:31:58 +04:00
}
2015-08-13 11:07:11 +03:00
func updateLabel ( e Engine , l * Label ) error {
2015-08-13 18:21:43 +03:00
_ , err := e . Id ( l . ID ) . AllCols ( ) . Update ( l )
2014-05-24 10:31:58 +04:00
return err
}
2015-08-13 11:07:11 +03:00
// UpdateLabel updates label information.
func UpdateLabel ( l * Label ) error {
return updateLabel ( x , l )
}
2014-05-24 10:31:58 +04:00
// DeleteLabel delete a label of given repository.
2015-07-24 18:13:42 +03:00
func DeleteLabel ( repoID , labelID int64 ) error {
2015-08-10 09:42:50 +03:00
l , err := GetLabelByID ( labelID )
2014-05-24 10:31:58 +04:00
if err != nil {
2015-08-10 09:42:50 +03:00
if IsErrLabelNotExist ( err ) {
2014-05-24 10:31:58 +04:00
return nil
}
return err
}
2014-06-21 08:51:41 +04:00
sess := x . NewSession ( )
2015-07-24 18:13:42 +03:00
defer sessionRelease ( sess )
2014-05-24 10:31:58 +04:00
if err = sess . Begin ( ) ; err != nil {
return err
}
2015-08-10 09:42:50 +03:00
if _ , err = x . Where ( "label_id=?" , labelID ) . Delete ( new ( IssueLabel ) ) ; err != nil {
return err
} else if _ , err = sess . Delete ( l ) ; err != nil {
2014-05-24 10:31:58 +04:00
return err
}
return sess . Commit ( )
}
2015-08-10 09:42:50 +03:00
// .___ .____ ___. .__
// | | ______ ________ __ ____ | | _____ \_ |__ ____ | |
// | |/ ___// ___/ | \_/ __ \| | \__ \ | __ \_/ __ \| |
// | |\___ \ \___ \| | /\ ___/| |___ / __ \| \_\ \ ___/| |__
// |___/____ >____ >____/ \___ >_______ (____ /___ /\___ >____/
// \/ \/ \/ \/ \/ \/ \/
// IssueLabel represetns an issue-lable relation.
type IssueLabel struct {
ID int64 ` xorm:"pk autoincr" `
IssueID int64 ` xorm:"UNIQUE(s)" `
LabelID int64 ` xorm:"UNIQUE(s)" `
}
func hasIssueLabel ( e Engine , issueID , labelID int64 ) bool {
has , _ := e . Where ( "issue_id=? AND label_id=?" , issueID , labelID ) . Get ( new ( IssueLabel ) )
return has
}
// HasIssueLabel returns true if issue has been labeled.
func HasIssueLabel ( issueID , labelID int64 ) bool {
return hasIssueLabel ( x , issueID , labelID )
}
2015-08-14 19:42:43 +03:00
func newIssueLabel ( e * xorm . Session , issue * Issue , label * Label ) ( err error ) {
if _ , err = e . Insert ( & IssueLabel {
IssueID : issue . ID ,
LabelID : label . ID ,
} ) ; err != nil {
return err
2015-08-10 11:52:08 +03:00
}
2015-08-14 19:42:43 +03:00
label . NumIssues ++
if issue . IsClosed {
label . NumClosedIssues ++
}
return updateLabel ( e , label )
2015-08-10 09:42:50 +03:00
}
// NewIssueLabel creates a new issue-label relation.
2015-08-14 19:42:43 +03:00
func NewIssueLabel ( issue * Issue , label * Label ) ( err error ) {
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = newIssueLabel ( sess , issue , label ) ; err != nil {
return err
}
return sess . Commit ( )
2015-08-10 09:42:50 +03:00
}
func getIssueLabels ( e Engine , issueID int64 ) ( [ ] * IssueLabel , error ) {
issueLabels := make ( [ ] * IssueLabel , 0 , 10 )
2015-08-10 13:57:57 +03:00
return issueLabels , e . Where ( "issue_id=?" , issueID ) . Asc ( "label_id" ) . Find ( & issueLabels )
2015-08-10 09:42:50 +03:00
}
// GetIssueLabels returns all issue-label relations of given issue by ID.
func GetIssueLabels ( issueID int64 ) ( [ ] * IssueLabel , error ) {
return getIssueLabels ( x , issueID )
}
2015-08-14 19:42:43 +03:00
func deleteIssueLabel ( e * xorm . Session , issue * Issue , label * Label ) ( err error ) {
if _ , err = e . Delete ( & IssueLabel {
IssueID : issue . ID ,
LabelID : label . ID ,
} ) ; err != nil {
return err
}
label . NumIssues --
if issue . IsClosed {
label . NumClosedIssues --
}
return updateLabel ( e , label )
2015-08-10 09:42:50 +03:00
}
// DeleteIssueLabel deletes issue-label relation.
2015-08-14 19:42:43 +03:00
func DeleteIssueLabel ( issue * Issue , label * Label ) ( err error ) {
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = deleteIssueLabel ( sess , issue , label ) ; err != nil {
return err
}
return sess . Commit ( )
2015-08-10 09:42:50 +03:00
}
2014-05-15 20:08:53 +04:00
// _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
// \/ \/ \/ \/ \/
2014-03-22 21:50:50 +04:00
// Milestone represents a milestone of repository.
type Milestone struct {
2015-08-03 12:42:09 +03:00
ID int64 ` xorm:"pk autoincr" `
2015-08-04 17:24:04 +03:00
RepoID int64 ` xorm:"INDEX" `
2014-05-08 00:51:14 +04:00
Name string
2014-09-05 05:10:41 +04:00
Content string ` xorm:"TEXT" `
2014-05-12 22:06:42 +04:00
RenderedContent string ` xorm:"-" `
2014-05-08 00:51:14 +04:00
IsClosed bool
NumIssues int
NumClosedIssues int
2014-05-12 22:06:42 +04:00
NumOpenIssues int ` xorm:"-" `
2014-05-08 00:51:14 +04:00
Completeness int // Percentage(1-100).
Deadline time . Time
2014-05-13 21:28:21 +04:00
DeadlineString string ` xorm:"-" `
2015-08-05 10:24:26 +03:00
IsOverDue bool ` xorm:"-" `
2014-05-08 00:51:14 +04:00
ClosedDate time . Time
2014-03-22 21:50:50 +04:00
}
2015-08-09 17:45:38 +03:00
func ( m * Milestone ) BeforeUpdate ( ) {
if m . NumIssues > 0 {
m . Completeness = m . NumClosedIssues * 100 / m . NumIssues
} else {
m . Completeness = 0
}
}
2015-08-06 17:48:11 +03:00
func ( m * Milestone ) AfterSet ( colName string , _ xorm . Cell ) {
2015-08-04 17:24:04 +03:00
if colName == "deadline" {
2015-08-06 17:48:11 +03:00
if m . Deadline . Year ( ) == 9999 {
2015-08-04 17:24:04 +03:00
return
}
2015-08-06 17:48:11 +03:00
m . DeadlineString = m . Deadline . Format ( "2006-01-02" )
if time . Now ( ) . After ( m . Deadline ) {
2015-08-05 10:24:26 +03:00
m . IsOverDue = true
}
2015-08-04 17:24:04 +03:00
}
}
2014-05-12 22:06:42 +04:00
// CalOpenIssues calculates the open issues of milestone.
func ( m * Milestone ) CalOpenIssues ( ) {
m . NumOpenIssues = m . NumIssues - m . NumClosedIssues
}
// NewMilestone creates new milestone of repository.
func NewMilestone ( m * Milestone ) ( err error ) {
2014-06-21 08:51:41 +04:00
sess := x . NewSession ( )
2015-08-06 18:25:35 +03:00
defer sessionRelease ( sess )
2014-05-12 22:06:42 +04:00
if err = sess . Begin ( ) ; err != nil {
return err
}
if _ , err = sess . Insert ( m ) ; err != nil {
return err
}
2015-08-06 18:25:35 +03:00
if _ , err = sess . Exec ( "UPDATE `repository` SET num_milestones=num_milestones+1 WHERE id=?" , m . RepoID ) ; err != nil {
2014-05-12 22:06:42 +04:00
return err
}
return sess . Commit ( )
}
2015-08-10 13:57:57 +03:00
func getMilestoneByID ( e Engine , id int64 ) ( * Milestone , error ) {
m := & Milestone { ID : id }
2015-08-14 19:42:43 +03:00
has , err := e . Get ( m )
2015-08-10 13:57:57 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrMilestoneNotExist { id , 0 }
}
return m , nil
}
2015-08-06 18:25:35 +03:00
// GetMilestoneByID returns the milestone of given ID.
func GetMilestoneByID ( id int64 ) ( * Milestone , error ) {
2015-08-10 13:57:57 +03:00
return getMilestoneByID ( x , id )
}
// GetRepoMilestoneByID returns the milestone of given ID and repository.
func GetRepoMilestoneByID ( repoID , milestoneID int64 ) ( * Milestone , error ) {
m := & Milestone { ID : milestoneID , RepoID : repoID }
2014-06-21 08:51:41 +04:00
has , err := x . Get ( m )
2014-05-14 18:55:36 +04:00
if err != nil {
return nil , err
} else if ! has {
2015-08-10 13:57:57 +03:00
return nil , ErrMilestoneNotExist { milestoneID , repoID }
2014-05-13 21:28:21 +04:00
}
return m , nil
}
2015-08-05 15:23:08 +03:00
// GetAllRepoMilestones returns all milestones of given repository.
func GetAllRepoMilestones ( repoID int64 ) ( [ ] * Milestone , error ) {
miles := make ( [ ] * Milestone , 0 , 10 )
return miles , x . Where ( "repo_id=?" , repoID ) . Find ( & miles )
}
2015-08-05 06:18:24 +03:00
// GetMilestones returns a list of milestones of given repository and status.
func GetMilestones ( repoID int64 , page int , isClosed bool ) ( [ ] * Milestone , error ) {
2015-08-04 17:24:04 +03:00
miles := make ( [ ] * Milestone , 0 , setting . IssuePagingNum )
sess := x . Where ( "repo_id=? AND is_closed=?" , repoID , isClosed )
if page > 0 {
sess = sess . Limit ( setting . IssuePagingNum , ( page - 1 ) * setting . IssuePagingNum )
}
return miles , sess . Find ( & miles )
2014-05-12 22:06:42 +04:00
}
2015-08-05 15:23:08 +03:00
func updateMilestone ( e Engine , m * Milestone ) error {
_ , err := e . Id ( m . ID ) . AllCols ( ) . Update ( m )
return err
}
2014-05-13 21:28:21 +04:00
// UpdateMilestone updates information of given milestone.
func UpdateMilestone ( m * Milestone ) error {
2015-08-05 15:23:08 +03:00
return updateMilestone ( x , m )
2014-05-13 21:28:21 +04:00
}
2015-08-05 15:23:08 +03:00
func countRepoMilestones ( e Engine , repoID int64 ) int64 {
count , _ := e . Where ( "repo_id=?" , repoID ) . Count ( new ( Milestone ) )
return count
}
// CountRepoMilestones returns number of milestones in given repository.
func CountRepoMilestones ( repoID int64 ) int64 {
return countRepoMilestones ( x , repoID )
}
func countRepoClosedMilestones ( e Engine , repoID int64 ) int64 {
closed , _ := e . Where ( "repo_id=? AND is_closed=?" , repoID , true ) . Count ( new ( Milestone ) )
2015-08-04 17:24:04 +03:00
return closed
}
2015-08-05 15:23:08 +03:00
// CountRepoClosedMilestones returns number of closed milestones in given repository.
func CountRepoClosedMilestones ( repoID int64 ) int64 {
return countRepoClosedMilestones ( x , repoID )
}
2015-08-04 17:24:04 +03:00
// MilestoneStats returns number of open and closed milestones of given repository.
func MilestoneStats ( repoID int64 ) ( open int64 , closed int64 ) {
open , _ = x . Where ( "repo_id=? AND is_closed=?" , repoID , false ) . Count ( new ( Milestone ) )
2015-08-05 15:23:08 +03:00
return open , CountRepoClosedMilestones ( repoID )
2015-08-04 17:24:04 +03:00
}
2014-05-14 03:46:48 +04:00
// ChangeMilestoneStatus changes the milestone open/closed status.
func ChangeMilestoneStatus ( m * Milestone , isClosed bool ) ( err error ) {
2015-08-08 17:43:14 +03:00
repo , err := GetRepositoryByID ( m . RepoID )
2014-05-14 03:46:48 +04:00
if err != nil {
return err
}
2014-06-21 08:51:41 +04:00
sess := x . NewSession ( )
2015-08-04 17:24:04 +03:00
defer sessionRelease ( sess )
2014-05-14 03:46:48 +04:00
if err = sess . Begin ( ) ; err != nil {
return err
}
m . IsClosed = isClosed
2015-08-05 15:23:08 +03:00
if err = updateMilestone ( sess , m ) ; err != nil {
2014-05-14 03:46:48 +04:00
return err
}
2015-08-08 17:43:14 +03:00
repo . NumMilestones = int ( countRepoMilestones ( sess , repo . ID ) )
repo . NumClosedMilestones = int ( countRepoClosedMilestones ( sess , repo . ID ) )
if _ , err = sess . Id ( repo . ID ) . AllCols ( ) . Update ( repo ) ; err != nil {
2014-05-14 03:46:48 +04:00
return err
}
return sess . Commit ( )
}
2015-08-13 11:07:11 +03:00
func changeMilestoneIssueStats ( e * xorm . Session , issue * Issue ) error {
2015-08-05 15:23:08 +03:00
if issue . MilestoneID == 0 {
2014-07-23 15:48:06 +04:00
return nil
}
2015-08-13 11:07:11 +03:00
m , err := getMilestoneByID ( e , issue . MilestoneID )
2014-07-23 15:48:06 +04:00
if err != nil {
return err
}
if issue . IsClosed {
m . NumOpenIssues --
m . NumClosedIssues ++
} else {
m . NumOpenIssues ++
m . NumClosedIssues --
}
2015-08-13 11:07:11 +03:00
return updateMilestone ( e , m )
}
// ChangeMilestoneIssueStats updates the open/closed issues counter and progress
// for the milestone associated witht the given issue.
func ChangeMilestoneIssueStats ( issue * Issue ) ( err error ) {
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = changeMilestoneIssueStats ( sess , issue ) ; err != nil {
return err
}
return sess . Commit ( )
2014-07-23 15:48:06 +04:00
}
2015-08-10 13:57:57 +03:00
func changeMilestoneAssign ( e * xorm . Session , oldMid int64 , issue * Issue ) error {
2014-05-14 18:55:36 +04:00
if oldMid > 0 {
2015-08-10 13:57:57 +03:00
m , err := getMilestoneByID ( e , oldMid )
2014-05-14 18:55:36 +04:00
if err != nil {
return err
}
m . NumIssues --
2014-05-15 20:08:53 +04:00
if issue . IsClosed {
2014-05-14 18:55:36 +04:00
m . NumClosedIssues --
}
2014-07-23 15:48:06 +04:00
2015-08-10 13:57:57 +03:00
if err = updateMilestone ( e , m ) ; err != nil {
2014-05-14 18:55:36 +04:00
return err
2015-08-10 13:57:57 +03:00
} else if _ , err = e . Exec ( "UPDATE `issue_user` SET milestone_id=0 WHERE issue_id=?" , issue . ID ) ; err != nil {
2014-05-15 20:08:53 +04:00
return err
}
2014-05-14 18:55:36 +04:00
}
2015-08-10 13:57:57 +03:00
if issue . MilestoneID > 0 {
2015-08-14 19:42:43 +03:00
m , err := getMilestoneByID ( e , issue . MilestoneID )
2014-05-14 19:14:51 +04:00
if err != nil {
return err
}
2014-07-23 15:48:06 +04:00
2014-05-14 19:14:51 +04:00
m . NumIssues ++
2014-05-15 20:08:53 +04:00
if issue . IsClosed {
2014-05-14 19:14:51 +04:00
m . NumClosedIssues ++
}
2014-07-22 22:57:48 +04:00
if m . NumIssues == 0 {
return ErrWrongIssueCounter
}
2015-08-10 13:57:57 +03:00
if err = updateMilestone ( e , m ) ; err != nil {
2014-05-14 19:14:51 +04:00
return err
2015-08-10 13:57:57 +03:00
} else if _ , err = e . Exec ( "UPDATE `issue_user` SET milestone_id=? WHERE issue_id=?" , m . ID , issue . ID ) ; err != nil {
2014-05-15 20:08:53 +04:00
return err
}
2014-05-14 18:55:36 +04:00
}
2014-07-23 15:48:06 +04:00
2015-08-14 19:42:43 +03:00
return updateIssue ( e , issue )
2015-08-10 13:57:57 +03:00
}
// ChangeMilestoneAssign changes assignment of milestone for issue.
func ChangeMilestoneAssign ( oldMid int64 , issue * Issue ) ( err error ) {
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = changeMilestoneAssign ( sess , oldMid , issue ) ; err != nil {
return err
}
2014-05-14 18:55:36 +04:00
return sess . Commit ( )
}
2015-08-05 15:23:08 +03:00
// DeleteMilestoneByID deletes a milestone by given ID.
func DeleteMilestoneByID ( mid int64 ) error {
2015-08-06 18:25:35 +03:00
m , err := GetMilestoneByID ( mid )
2015-08-05 15:23:08 +03:00
if err != nil {
if IsErrMilestoneNotExist ( err ) {
return nil
}
2014-05-14 03:46:48 +04:00
return err
}
2015-08-08 17:43:14 +03:00
repo , err := GetRepositoryByID ( m . RepoID )
2015-08-05 15:23:08 +03:00
if err != nil {
2014-05-14 03:46:48 +04:00
return err
}
2015-08-05 15:23:08 +03:00
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
2014-05-14 03:46:48 +04:00
return err
}
2015-08-05 15:23:08 +03:00
if _ , err = sess . Id ( m . ID ) . Delete ( m ) ; err != nil {
2014-05-14 03:46:48 +04:00
return err
}
2014-05-15 20:08:53 +04:00
2015-08-08 17:43:14 +03:00
repo . NumMilestones = int ( countRepoMilestones ( sess , repo . ID ) )
repo . NumClosedMilestones = int ( countRepoClosedMilestones ( sess , repo . ID ) )
if _ , err = sess . Id ( repo . ID ) . AllCols ( ) . Update ( repo ) ; err != nil {
2015-08-05 15:23:08 +03:00
return err
}
if _ , err = sess . Exec ( "UPDATE `issue` SET milestone_id=0 WHERE milestone_id=?" , m . ID ) ; err != nil {
return err
} else if _ , err = sess . Exec ( "UPDATE `issue_user` SET milestone_id=0 WHERE milestone_id=?" , m . ID ) ; err != nil {
2014-05-15 20:08:53 +04:00
return err
}
2014-05-14 03:46:48 +04:00
return sess . Commit ( )
}
2014-05-15 20:08:53 +04:00
// _________ __
// \_ ___ \ ____ _____ _____ ____ _____/ |_
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\
// \ \___( <_> ) Y Y \ Y Y \ ___/| | \ |
// \______ /\____/|__|_| /__|_| /\___ >___| /__|
// \/ \/ \/ \/ \/
2014-07-24 12:15:05 +04:00
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
type CommentType int
2014-03-30 01:50:51 +04:00
const (
2014-07-24 12:15:05 +04:00
// Plain comment, can be associated with a commit (CommitId > 0) and a line (Line > 0)
2015-02-07 04:47:21 +03:00
COMMENT_TYPE_COMMENT CommentType = iota
COMMENT_TYPE_REOPEN
COMMENT_TYPE_CLOSE
2014-07-24 12:15:05 +04:00
2015-02-07 04:47:21 +03:00
// References.
2015-08-13 18:21:43 +03:00
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
2014-03-30 01:50:51 +04:00
)
2015-08-13 21:43:40 +03:00
type CommentTag int
const (
COMMENT_TAG_NONE CommentTag = iota
COMMENT_TAG_POSTER
COMMENT_TAG_ADMIN
COMMENT_TAG_OWNER
)
2014-03-22 21:50:50 +04:00
// Comment represents a comment in commit and issue page.
2014-03-21 00:04:56 +04:00
type Comment struct {
2015-08-13 11:07:11 +03:00
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" `
Attachments [ ] * Attachment ` xorm:"-" `
2015-08-13 21:43:40 +03:00
// For view issue page.
ShowTag CommentTag ` xorm:"-" `
2014-03-21 00:04:56 +04:00
}
2014-03-26 20:31:01 +04:00
2015-08-13 11:07:11 +03:00
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 )
}
2014-07-23 23:15:47 +04:00
2015-08-13 11:07:11 +03:00
case "poster_id" :
c . Poster , err = GetUserByID ( c . PosterID )
if err != nil {
if IsErrUserNotExist ( err ) {
c . PosterID = - 1
2015-08-14 21:48:05 +03:00
c . Poster = NewFakeUser ( )
2015-08-13 11:07:11 +03:00
} else {
log . Error ( 3 , "GetUserByID[%d]: %v" , c . ID , err )
}
}
2015-08-19 19:12:43 +03:00
case "created" :
2015-08-19 19:56:12 +03:00
c . Created = regulateTimeZone ( c . Created )
2015-08-13 11:07:11 +03:00
}
}
2015-08-19 23:31:28 +03:00
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 )
}
}
2015-08-19 19:12:43 +03:00
// 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 )
}
2015-08-13 11:07:11 +03:00
func createComment ( e * xorm . Session , u * User , repo * Repository , issue * Issue , commitID , line int64 , cmtType CommentType , content string , uuids [ ] string ) ( _ * Comment , err error ) {
comment := & Comment {
PosterID : u . Id ,
Type : cmtType ,
IssueID : issue . ID ,
CommitID : commitID ,
Line : line ,
Content : content ,
}
if _ , err = e . Insert ( comment ) ; err != nil {
2014-07-23 23:15:47 +04:00
return nil , err
2014-03-27 00:41:16 +04:00
}
2014-03-30 01:50:51 +04:00
// Check comment type.
switch cmtType {
2015-02-07 04:47:21 +03:00
case COMMENT_TYPE_COMMENT :
2015-08-13 11:07:11 +03:00
if _ , err = e . Exec ( "UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?" , issue . ID ) ; err != nil {
2014-07-23 23:15:47 +04:00
return nil , err
}
2015-08-13 11:07:11 +03:00
// Check attachments.
attachments := make ( [ ] * Attachment , 0 , len ( uuids ) )
for _ , uuid := range uuids {
attach , err := getAttachmentByUUID ( e , uuid )
if err != nil {
if IsErrAttachmentNotExist ( err ) {
continue
}
return nil , fmt . Errorf ( "getAttachmentByUUID[%s]: %v" , uuid , err )
2014-07-23 23:15:47 +04:00
}
2015-08-13 11:07:11 +03:00
attachments = append ( attachments , attach )
}
2014-07-23 23:15:47 +04:00
2015-08-13 11:07:11 +03:00
for i := range attachments {
attachments [ i ] . IssueID = 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 )
2014-07-23 23:15:47 +04:00
}
2014-03-30 01:50:51 +04:00
}
2015-08-13 11:07:11 +03:00
// Notify watchers.
act := & Action {
ActUserID : u . Id ,
ActUserName : u . LowerName ,
ActEmail : u . Email ,
OpType : COMMENT_ISSUE ,
Content : fmt . Sprintf ( "%d|%s" , issue . Index , strings . Split ( content , "\n" ) [ 0 ] ) ,
RepoID : repo . ID ,
RepoUserName : repo . Owner . LowerName ,
RepoName : repo . LowerName ,
IsPrivate : repo . IsPrivate ,
}
if err = notifyWatchers ( e , act ) ; err != nil {
return nil , err
}
2015-02-07 04:47:21 +03:00
case COMMENT_TYPE_REOPEN :
2015-08-13 11:07:11 +03:00
if _ , err = e . Exec ( "UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?" , repo . ID ) ; err != nil {
2014-07-23 23:15:47 +04:00
return nil , err
2014-03-30 01:50:51 +04:00
}
2015-02-07 04:47:21 +03:00
case COMMENT_TYPE_CLOSE :
2015-08-13 11:07:11 +03:00
if _ , err = e . Exec ( "UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?" , repo . ID ) ; err != nil {
2014-07-23 23:15:47 +04:00
return nil , err
2014-03-30 01:50:51 +04:00
}
2014-03-27 00:41:16 +04:00
}
2014-07-23 23:15:47 +04:00
2015-08-13 11:07:11 +03:00
return comment , nil
2014-03-26 20:31:01 +04:00
}
2015-08-13 11:07:11 +03:00
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 , doer , repo , issue , 0 , 0 , cmtType , "" , nil )
}
2014-07-24 11:04:09 +04:00
2015-08-13 11:07:11 +03:00
// CreateComment creates comment of issue or commit.
func CreateComment ( doer * User , repo * Repository , issue * Issue , commitID , line int64 , cmtType CommentType , content string , attachments [ ] string ) ( comment * Comment , err error ) {
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return nil , err
}
comment , err = createComment ( sess , doer , repo , issue , commitID , line , cmtType , content , attachments )
if err != nil {
return nil , err
}
return comment , sess . Commit ( )
2014-07-24 11:04:09 +04:00
}
2015-08-13 11:07:11 +03:00
// CreateIssueComment creates a plain issue comment.
func CreateIssueComment ( doer * User , repo * Repository , issue * Issue , content string , attachments [ ] string ) ( * Comment , error ) {
return CreateComment ( doer , repo , issue , 0 , 0 , COMMENT_TYPE_COMMENT , content , attachments )
2014-07-24 12:15:05 +04:00
}
2015-08-19 23:31:28 +03:00
// GetCommentByID returns the comment by given ID.
func GetCommentByID ( id int64 ) ( * Comment , error ) {
2015-08-13 11:07:11 +03:00
c := new ( Comment )
2015-08-19 23:31:28 +03:00
has , err := x . Id ( id ) . Get ( c )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrCommentNotExist { id }
}
return c , nil
2014-03-26 20:31:01 +04:00
}
2014-07-23 23:15:47 +04:00
2015-08-13 11:07:11 +03:00
// 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 )
2014-07-23 23:15:47 +04:00
}
2015-08-19 23:31:28 +03:00
// UpdateComment updates information of comment.
func UpdateComment ( c * Comment ) error {
_ , err := x . Id ( c . ID ) . AllCols ( ) . Update ( c )
return err
2014-07-23 23:15:47 +04:00
}
2015-08-11 18:24:40 +03:00
// Attachment represent a attachment of issue/comment/release.
2014-07-23 23:15:47 +04:00
type Attachment struct {
2015-08-11 18:24:40 +03:00
ID int64 ` xorm:"pk autoincr" `
UUID string ` xorm:"uuid UNIQUE" `
IssueID int64 ` xorm:"INDEX" `
CommentID int64
ReleaseID int64 ` xorm:"INDEX" `
2014-07-23 23:15:47 +04:00
Name string
Created time . Time ` xorm:"CREATED" `
}
2015-08-11 18:24:40 +03:00
// AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
func AttachmentLocalPath ( uuid string ) string {
return path . Join ( setting . AttachmentPath , uuid [ 0 : 1 ] , uuid [ 1 : 2 ] , uuid )
}
// LocalPath returns where attachment is stored in local file system.
func ( attach * Attachment ) LocalPath ( ) string {
return AttachmentLocalPath ( attach . UUID )
}
// NewAttachment creates a new attachment object.
func NewAttachment ( name string , buf [ ] byte , file multipart . File ) ( _ * Attachment , err error ) {
attach := & Attachment {
UUID : gouuid . NewV4 ( ) . String ( ) ,
Name : name ,
}
if err = os . MkdirAll ( path . Dir ( attach . LocalPath ( ) ) , os . ModePerm ) ; err != nil {
return nil , fmt . Errorf ( "MkdirAll: %v" , err )
}
2014-07-23 23:15:47 +04:00
2015-08-11 18:24:40 +03:00
fw , err := os . Create ( attach . LocalPath ( ) )
if err != nil {
return nil , fmt . Errorf ( "Create: %v" , err )
}
defer fw . Close ( )
if _ , err = fw . Write ( buf ) ; err != nil {
return nil , fmt . Errorf ( "Write: %v" , err )
} else if _ , err = io . Copy ( fw , file ) ; err != nil {
return nil , fmt . Errorf ( "Copy: %v" , err )
}
sess := x . NewSession ( )
defer sessionRelease ( sess )
2014-07-23 23:15:47 +04:00
if err := sess . Begin ( ) ; err != nil {
return nil , err
}
2015-08-11 18:24:40 +03:00
if _ , err := sess . Insert ( attach ) ; err != nil {
2014-07-23 23:15:47 +04:00
return nil , err
}
2015-08-11 18:24:40 +03:00
return attach , sess . Commit ( )
2014-07-23 23:15:47 +04:00
}
2015-08-13 11:07:11 +03:00
func getAttachmentByUUID ( e Engine , uuid string ) ( * Attachment , error ) {
2015-08-11 18:24:40 +03:00
attach := & Attachment { UUID : uuid }
has , err := x . Get ( attach )
2014-07-23 23:15:47 +04:00
if err != nil {
return nil , err
2015-08-11 18:24:40 +03:00
} else if ! has {
return nil , ErrAttachmentNotExist { 0 , uuid }
2014-07-23 23:15:47 +04:00
}
2015-08-11 18:24:40 +03:00
return attach , nil
2014-07-23 23:15:47 +04:00
}
2015-08-13 11:07:11 +03:00
// GetAttachmentByUUID returns attachment by given UUID.
func GetAttachmentByUUID ( uuid string ) ( * Attachment , error ) {
return getAttachmentByUUID ( x , uuid )
}
2015-08-12 12:04:23 +03:00
// GetAttachmentsByIssueID returns all attachments for given issue by ID.
func GetAttachmentsByIssueID ( issueID int64 ) ( [ ] * Attachment , error ) {
2014-07-23 23:15:47 +04:00
attachments := make ( [ ] * Attachment , 0 , 10 )
2015-08-12 12:04:23 +03:00
return attachments , x . Where ( "issue_id=? AND comment_id=0" , issueID ) . Find ( & attachments )
2014-07-23 23:15:47 +04:00
}
2015-08-13 11:07:11 +03:00
// GetAttachmentsByCommentID returns all attachments if comment by given ID.
func GetAttachmentsByCommentID ( commentID int64 ) ( [ ] * Attachment , error ) {
2014-07-23 23:15:47 +04:00
attachments := make ( [ ] * Attachment , 0 , 10 )
2015-08-13 11:07:11 +03:00
return attachments , x . Where ( "comment_id=?" , commentID ) . Find ( & attachments )
2014-07-23 23:15:47 +04:00
}
// 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 {
2015-08-11 18:24:40 +03:00
if err := os . Remove ( a . LocalPath ( ) ) ; err != nil {
2014-07-23 23:15:47 +04:00
return i , err
}
}
2015-08-11 18:24:40 +03:00
if _ , err := x . Delete ( a . ID ) ; err != nil {
2014-07-23 23:15:47 +04:00
return i , err
}
}
return len ( attachments ) , nil
}
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
func DeleteAttachmentsByIssue ( issueId int64 , remove bool ) ( int , error ) {
2015-08-12 12:04:23 +03:00
attachments , err := GetAttachmentsByIssueID ( issueId )
2014-07-23 23:15:47 +04:00
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 ) {
2015-08-13 11:07:11 +03:00
attachments , err := GetAttachmentsByCommentID ( commentId )
2014-07-23 23:15:47 +04:00
if err != nil {
return 0 , err
}
return DeleteAttachments ( attachments , remove )
}