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-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-03-22 21:50:50 +04:00
"strings"
"time"
2014-05-08 00:51:14 +04:00
2016-11-11 15:11:45 +03:00
api "code.gitea.io/sdk/gitea"
2014-07-26 08:24:27 +04:00
"github.com/Unknwon/com"
2016-11-10 18:16:32 +03:00
"github.com/go-xorm/xorm"
2016-02-21 02:13:12 +03:00
gouuid "github.com/satori/go.uuid"
2014-05-13 22:49:20 +04:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2014-03-22 21:50:50 +04:00
)
2014-03-23 00:00:46 +04:00
var (
2016-11-22 14:24:39 +03: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 {
2016-08-14 13:32:24 +03:00
ID int64 ` xorm:"pk autoincr" `
RepoID int64 ` xorm:"INDEX UNIQUE(repo_index)" `
2014-03-29 18:24:42 +04:00
Repo * Repository ` xorm:"-" `
2016-08-16 20:19:09 +03:00
Index int64 ` xorm:"UNIQUE(repo_index)" ` // Index in one repository.
2015-08-05 15:23:08 +03:00
PosterID int64
2014-05-24 10:31:58 +04:00
Poster * User ` xorm:"-" `
2016-08-16 20:19:09 +03:00
Title string ` xorm:"name" `
Content string ` xorm:"TEXT" `
RenderedContent string ` xorm:"-" `
2014-05-24 10:31:58 +04:00
Labels [ ] * Label ` xorm:"-" `
2015-08-05 15:23:08 +03:00
MilestoneID int64
Milestone * Milestone ` xorm:"-" `
2016-08-16 20:19:09 +03:00
Priority int
2015-08-05 15:23:08 +03:00
AssigneeID int64
2015-09-02 12:09:12 +03:00
Assignee * User ` xorm:"-" `
2014-03-29 18:24:42 +04:00
IsClosed bool
2016-08-16 20:19:09 +03:00
IsRead bool ` xorm:"-" `
IsPull bool // Indicates whether is a pull request or not.
PullRequest * PullRequest ` xorm:"-" `
2014-03-29 18:24:42 +04:00
NumComments int
2016-03-10 03:53:30 +03:00
Deadline time . Time ` xorm:"-" `
DeadlineUnix int64
Created time . Time ` xorm:"-" `
CreatedUnix int64
Updated time . Time ` xorm:"-" `
UpdatedUnix int64
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
}
2016-11-24 11:41:11 +03:00
// BeforeInsert is invoked from XORM before inserting an object of this type.
2016-08-25 02:05:56 +03:00
func ( issue * Issue ) BeforeInsert ( ) {
issue . CreatedUnix = time . Now ( ) . Unix ( )
issue . UpdatedUnix = issue . CreatedUnix
2016-03-10 03:53:30 +03:00
}
2016-11-24 11:41:11 +03:00
// BeforeUpdate is invoked from XORM before updating this object.
2016-08-25 02:05:56 +03:00
func ( issue * Issue ) BeforeUpdate ( ) {
issue . UpdatedUnix = time . Now ( ) . Unix ( )
issue . DeadlineUnix = issue . Deadline . Unix ( )
2016-03-10 03:53:30 +03:00
}
2016-11-24 11:41:11 +03:00
// AfterSet is invoked from XORM after setting the value of a field of
// this object.
2016-08-25 02:05:56 +03:00
func ( issue * Issue ) AfterSet ( colName string , _ xorm . Cell ) {
2015-08-05 15:23:08 +03:00
switch colName {
2016-08-26 23:40:53 +03:00
case "deadline_unix" :
issue . Deadline = time . Unix ( issue . DeadlineUnix , 0 ) . Local ( )
case "created_unix" :
issue . Created = time . Unix ( issue . CreatedUnix , 0 ) . Local ( )
case "updated_unix" :
issue . Updated = time . Unix ( issue . UpdatedUnix , 0 ) . Local ( )
}
}
2015-08-13 11:07:11 +03:00
2016-12-17 14:49:17 +03:00
func ( issue * Issue ) loadRepo ( e Engine ) ( err error ) {
2016-08-26 23:40:53 +03:00
if issue . Repo == nil {
issue . Repo , err = getRepositoryByID ( e , issue . RepoID )
2016-03-14 06:20:22 +03:00
if err != nil {
2016-08-26 23:40:53 +03:00
return fmt . Errorf ( "getRepositoryByID [%d]: %v" , issue . RepoID , err )
2016-03-14 06:20:22 +03:00
}
2016-08-26 23:40:53 +03:00
}
2016-12-17 14:49:17 +03:00
return nil
}
func ( issue * Issue ) loadAttributes ( e Engine ) ( err error ) {
if err := issue . loadRepo ( e ) ; err != nil {
return err
}
2016-03-14 06:20:22 +03:00
2016-08-26 23:40:53 +03:00
if issue . Poster == nil {
issue . Poster , err = getUserByID ( e , issue . PosterID )
2016-03-14 06:20:22 +03:00
if err != nil {
2016-09-20 12:54:47 +03:00
issue . PosterID = - 1
issue . Poster = NewGhostUser ( )
2016-11-04 21:47:54 +03:00
if ! IsErrUserNotExist ( err ) {
2016-08-26 23:40:53 +03:00
return fmt . Errorf ( "getUserByID.(poster) [%d]: %v" , issue . PosterID , err )
2016-03-14 06:20:22 +03:00
}
2016-11-09 08:07:01 +03:00
err = nil
2016-03-14 06:20:22 +03:00
return
}
2016-08-26 23:40:53 +03:00
}
2016-03-14 06:20:22 +03:00
2016-08-26 23:40:53 +03:00
if issue . Labels == nil {
issue . Labels , err = getLabelsByIssueID ( e , issue . ID )
2015-08-05 15:23:08 +03:00
if err != nil {
2016-08-26 23:40:53 +03:00
return fmt . Errorf ( "getLabelsByIssueID [%d]: %v" , issue . ID , err )
2015-08-10 16:47:23 +03:00
}
2016-08-26 23:40:53 +03:00
}
2015-08-10 16:47:23 +03:00
2016-08-26 23:40:53 +03:00
if issue . Milestone == nil && issue . MilestoneID > 0 {
issue . Milestone , err = getMilestoneByRepoID ( e , issue . RepoID , issue . MilestoneID )
2015-08-10 16:47:23 +03:00
if err != nil {
2016-08-26 23:40:53 +03:00
return fmt . Errorf ( "getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v" , issue . RepoID , issue . MilestoneID , err )
2015-08-10 16:47:23 +03:00
}
2015-08-05 15:23:08 +03:00
}
2016-08-26 23:40:53 +03:00
if issue . Assignee == nil && issue . AssigneeID > 0 {
issue . Assignee , err = getUserByID ( e , issue . AssigneeID )
2016-08-14 13:32:24 +03:00
if err != nil {
2016-08-26 23:40:53 +03:00
return fmt . Errorf ( "getUserByID.(assignee) [%d]: %v" , issue . AssigneeID , err )
2016-08-14 13:32:24 +03:00
}
}
if issue . IsPull && issue . PullRequest == nil {
// It is possible pull request is not yet created.
issue . PullRequest , err = getPullRequestByIssueID ( e , issue . ID )
if err != nil && ! IsErrPullRequestNotExist ( err ) {
return fmt . Errorf ( "getPullRequestByIssueID [%d]: %v" , issue . ID , err )
}
}
2016-08-26 23:40:53 +03:00
if issue . Attachments == nil {
issue . Attachments , err = getAttachmentsByIssueID ( e , issue . ID )
if err != nil {
return fmt . Errorf ( "getAttachmentsByIssueID [%d]: %v" , issue . ID , err )
}
}
if issue . Comments == nil {
issue . Comments , err = getCommentsByIssueID ( e , issue . ID )
if err != nil {
return fmt . Errorf ( "getCommentsByIssueID [%d]: %v" , issue . ID , err )
}
}
2016-08-14 13:32:24 +03:00
return nil
}
2016-11-24 11:41:11 +03:00
// LoadAttributes loads the attribute of this issue.
2016-08-14 13:32:24 +03:00
func ( issue * Issue ) LoadAttributes ( ) error {
return issue . loadAttributes ( x )
2015-08-19 19:12:43 +03:00
}
2016-11-24 11:41:11 +03:00
// HTMLURL returns the absolute URL to this issue.
2016-08-16 20:19:09 +03:00
func ( issue * Issue ) HTMLURL ( ) string {
var path string
if issue . IsPull {
path = "pulls"
} else {
path = "issues"
}
return fmt . Sprintf ( "%s/%s/%d" , issue . Repo . HTMLURL ( ) , path , issue . Index )
}
2016-12-02 14:10:39 +03:00
// DiffURL returns the absolute URL to this diff
func ( issue * Issue ) DiffURL ( ) string {
if issue . IsPull {
return fmt . Sprintf ( "%s/pulls/%d.diff" , issue . Repo . HTMLURL ( ) , issue . Index )
}
return ""
}
// PatchURL returns the absolute URL to this patch
func ( issue * Issue ) PatchURL ( ) string {
if issue . IsPull {
return fmt . Sprintf ( "%s/pulls/%d.patch" , issue . Repo . HTMLURL ( ) , issue . Index )
}
return ""
}
2016-03-14 06:20:22 +03:00
// State returns string representation of issue status.
2016-11-22 14:24:39 +03:00
func ( issue * Issue ) State ( ) api . StateType {
if issue . IsClosed {
2016-11-29 11:25:47 +03:00
return api . StateClosed
2016-03-14 06:20:22 +03:00
}
2016-11-29 11:25:47 +03:00
return api . StateOpen
2016-08-14 13:32:24 +03:00
}
2016-11-22 14:24:39 +03:00
// APIFormat assumes some fields assigned with values:
2016-08-14 13:32:24 +03:00
// Required - Poster, Labels,
// Optional - Milestone, Assignee, PullRequest
func ( issue * Issue ) APIFormat ( ) * api . Issue {
apiLabels := make ( [ ] * api . Label , len ( issue . Labels ) )
for i := range issue . Labels {
apiLabels [ i ] = issue . Labels [ i ] . APIFormat ( )
}
apiIssue := & api . Issue {
ID : issue . ID ,
Index : issue . Index ,
2016-08-16 20:19:09 +03:00
Poster : issue . Poster . APIFormat ( ) ,
2016-08-14 13:32:24 +03:00
Title : issue . Title ,
Body : issue . Content ,
Labels : apiLabels ,
2016-08-16 20:19:09 +03:00
State : issue . State ( ) ,
2016-08-14 13:32:24 +03:00
Comments : issue . NumComments ,
Created : issue . Created ,
Updated : issue . Updated ,
}
if issue . Milestone != nil {
apiIssue . Milestone = issue . Milestone . APIFormat ( )
}
if issue . Assignee != nil {
apiIssue . Assignee = issue . Assignee . APIFormat ( )
}
if issue . IsPull {
apiIssue . PullRequest = & api . PullRequestMeta {
HasMerged : issue . PullRequest . HasMerged ,
}
if issue . PullRequest . HasMerged {
apiIssue . PullRequest . Merged = & issue . PullRequest . Merged
}
}
return apiIssue
}
// HashTag returns unique hash tag for issue.
2016-11-22 14:24:39 +03:00
func ( issue * Issue ) HashTag ( ) string {
return "issue-" + com . ToStr ( issue . ID )
2016-03-14 06:20:22 +03:00
}
2015-08-13 11:07:11 +03:00
// IsPoster returns true if given user by ID is the poster.
2016-11-22 14:24:39 +03:00
func ( issue * Issue ) IsPoster ( uid int64 ) bool {
return issue . PosterID == uid
2015-08-13 11:07:11 +03:00
}
2016-11-22 14:24:39 +03:00
func ( issue * Issue ) hasLabel ( e Engine , labelID int64 ) bool {
return hasIssueLabel ( e , issue . ID , labelID )
2015-08-10 09:42:50 +03:00
}
// HasLabel returns true if issue has been labeled by given ID.
2016-11-22 14:24:39 +03:00
func ( issue * Issue ) HasLabel ( labelID int64 ) bool {
return issue . hasLabel ( x , labelID )
2015-08-10 09:42:50 +03:00
}
2016-08-14 13:32:24 +03:00
func ( issue * Issue ) sendLabelUpdatedWebhook ( doer * User ) {
var err error
if issue . IsPull {
2016-08-24 22:01:30 +03:00
err = issue . PullRequest . LoadIssue ( )
if err != nil {
log . Error ( 4 , "LoadIssue: %v" , err )
return
}
2016-11-07 18:37:32 +03:00
err = PrepareWebhooks ( issue . Repo , HookEventPullRequest , & api . PullRequestPayload {
Action : api . HookIssueLabelUpdated ,
2016-08-14 13:32:24 +03:00
Index : issue . Index ,
PullRequest : issue . PullRequest . APIFormat ( ) ,
2016-12-06 02:48:51 +03:00
Repository : issue . Repo . APIFormat ( AccessModeNone ) ,
2016-08-14 13:32:24 +03:00
Sender : doer . APIFormat ( ) ,
} )
}
if err != nil {
log . Error ( 4 , "PrepareWebhooks [is_pull: %v]: %v" , issue . IsPull , err )
} else {
go HookQueue . Add ( issue . RepoID )
}
}
2016-11-22 14:24:39 +03:00
func ( issue * Issue ) addLabel ( e * xorm . Session , label * Label ) error {
return newIssueLabel ( e , issue , label )
2015-08-10 09:42:50 +03:00
}
2016-08-03 21:51:22 +03:00
// AddLabel adds a new label to the issue.
2016-08-14 13:32:24 +03:00
func ( issue * Issue ) AddLabel ( doer * User , label * Label ) error {
if err := NewIssueLabel ( issue , label ) ; err != nil {
2015-08-14 19:42:43 +03:00
return err
}
2016-08-14 13:32:24 +03:00
issue . sendLabelUpdatedWebhook ( doer )
return nil
2015-08-10 09:42:50 +03:00
}
2016-08-03 21:51:22 +03:00
func ( issue * Issue ) addLabels ( e * xorm . Session , labels [ ] * Label ) error {
return newIssueLabels ( e , issue , labels )
}
// AddLabels adds a list of new labels to the issue.
2016-08-14 13:32:24 +03:00
func ( issue * Issue ) AddLabels ( doer * User , labels [ ] * Label ) error {
if err := NewIssueLabels ( issue , labels ) ; err != nil {
return err
}
issue . sendLabelUpdatedWebhook ( doer )
return nil
2016-08-03 21:51:22 +03:00
}
func ( issue * Issue ) getLabels ( e Engine ) ( err error ) {
if len ( issue . Labels ) > 0 {
2014-05-24 10:31:58 +04:00
return nil
}
2016-08-03 21:51:22 +03:00
issue . Labels , err = getLabelsByIssueID ( e , issue . ID )
2015-08-10 09:42:50 +03:00
if err != nil {
return fmt . Errorf ( "getLabelsByIssueID: %v" , err )
2014-05-24 10:31:58 +04:00
}
return nil
}
2016-08-03 21:51:22 +03:00
func ( issue * Issue ) removeLabel ( e * xorm . Session , label * Label ) error {
return deleteIssueLabel ( e , issue , label )
2015-08-10 09:42:50 +03:00
}
// RemoveLabel removes a label from issue by given ID.
2016-08-14 13:32:24 +03:00
func ( issue * Issue ) RemoveLabel ( doer * User , label * Label ) error {
2016-12-17 14:49:17 +03:00
if err := issue . loadRepo ( x ) ; err != nil {
return err
}
if has , err := HasAccess ( doer , issue . Repo , AccessModeWrite ) ; err != nil {
return err
} else if ! has {
return ErrLabelNotExist { }
}
2016-08-14 13:32:24 +03:00
if err := DeleteIssueLabel ( issue , label ) ; err != nil {
return err
}
issue . sendLabelUpdatedWebhook ( doer )
return nil
2016-08-03 21:51:22 +03:00
}
func ( issue * Issue ) clearLabels ( e * xorm . Session ) ( err error ) {
if err = issue . getLabels ( e ) ; err != nil {
return fmt . Errorf ( "getLabels: %v" , err )
}
for i := range issue . Labels {
if err = issue . removeLabel ( e , issue . Labels [ i ] ) ; err != nil {
return fmt . Errorf ( "removeLabel: %v" , err )
}
}
return nil
}
2016-11-24 11:41:11 +03:00
// ClearLabels removes all issue labels as the given user.
// Triggers appropriate WebHooks, if any.
2016-08-14 13:32:24 +03:00
func ( issue * Issue ) ClearLabels ( doer * User ) ( err error ) {
2015-08-14 19:42:43 +03:00
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
2016-12-17 14:49:17 +03:00
if err := issue . loadRepo ( sess ) ; err != nil {
return err
}
if has , err := hasAccess ( sess , doer , issue . Repo , AccessModeWrite ) ; err != nil {
return err
} else if ! has {
return ErrLabelNotExist { }
}
2016-08-03 21:51:22 +03:00
if err = issue . clearLabels ( sess ) ; err != nil {
2015-08-14 19:42:43 +03:00
return err
}
2016-08-14 13:32:24 +03:00
if err = sess . Commit ( ) ; err != nil {
return fmt . Errorf ( "Commit: %v" , err )
}
if issue . IsPull {
2016-08-24 22:01:30 +03:00
err = issue . PullRequest . LoadIssue ( )
if err != nil {
log . Error ( 4 , "LoadIssue: %v" , err )
return
}
2016-11-07 18:37:32 +03:00
err = PrepareWebhooks ( issue . Repo , HookEventPullRequest , & api . PullRequestPayload {
Action : api . HookIssueLabelCleared ,
2016-08-14 13:32:24 +03:00
Index : issue . Index ,
PullRequest : issue . PullRequest . APIFormat ( ) ,
2016-12-06 02:48:51 +03:00
Repository : issue . Repo . APIFormat ( AccessModeNone ) ,
2016-08-14 13:32:24 +03:00
Sender : doer . APIFormat ( ) ,
} )
}
if err != nil {
log . Error ( 4 , "PrepareWebhooks [is_pull: %v]: %v" , issue . IsPull , err )
} else {
go HookQueue . Add ( issue . RepoID )
}
return nil
2015-08-14 19:42:43 +03:00
}
2016-08-03 21:51:22 +03:00
// ReplaceLabels removes all current labels and add new labels to the issue.
2016-11-24 11:41:11 +03:00
// Triggers appropriate WebHooks, if any.
2016-08-03 21:51:22 +03:00
func ( issue * Issue ) ReplaceLabels ( labels [ ] * Label ) ( err error ) {
2015-08-14 19:42:43 +03:00
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
2016-08-03 21:51:22 +03:00
if err = issue . clearLabels ( sess ) ; err != nil {
return fmt . Errorf ( "clearLabels: %v" , err )
} else if err = issue . addLabels ( sess , labels ) ; err != nil {
return fmt . Errorf ( "addLabels: %v" , err )
2015-08-14 19:42:43 +03:00
}
return sess . Commit ( )
2015-08-10 09:42:50 +03:00
}
2016-11-24 11:41:11 +03:00
// GetAssignee sets the Assignee attribute of this issue.
2016-11-22 14:24:39 +03:00
func ( issue * Issue ) GetAssignee ( ) ( err error ) {
if issue . AssigneeID == 0 || issue . Assignee != nil {
2014-05-08 20:24:11 +04:00
return nil
}
2015-08-05 15:26:00 +03:00
2016-11-22 14:24:39 +03:00
issue . Assignee , err = GetUserByID ( issue . 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.
2016-11-22 14:24:39 +03:00
func ( issue * Issue ) ReadBy ( uid int64 ) error {
return UpdateIssueUserByRead ( uid , issue . ID )
2014-07-23 23:15:47 +04:00
}
2016-08-14 13:32:24 +03:00
func updateIssueCols ( e Engine , issue * Issue , cols ... string ) error {
_ , err := e . Id ( issue . ID ) . Cols ( cols ... ) . Update ( issue )
return err
}
// UpdateIssueCols only updates values of specific columns for given issue.
func UpdateIssueCols ( issue * Issue , cols ... string ) error {
return updateIssueCols ( x , issue , cols ... )
}
2016-11-22 14:24:39 +03:00
func ( issue * Issue ) changeStatus ( e * xorm . Session , doer * User , repo * Repository , isClosed bool ) ( err error ) {
2016-03-05 20:58:51 +03:00
// Nothing should be performed if current status is same as target status
2016-11-22 14:24:39 +03:00
if issue . IsClosed == isClosed {
2015-08-13 11:07:11 +03:00
return nil
}
2016-11-22 14:24:39 +03:00
issue . IsClosed = isClosed
2015-08-13 11:07:11 +03:00
2016-11-22 14:24:39 +03:00
if err = updateIssueCols ( e , issue , "is_closed" ) ; err != nil {
2015-08-13 11:07:11 +03:00
return err
2016-11-22 14:24:39 +03:00
} else if err = updateIssueUsersByStatus ( e , issue . ID , isClosed ) ; err != nil {
2015-08-13 11:07:11 +03:00
return err
}
2016-03-05 20:58:51 +03:00
// Update issue count of labels
2016-11-22 14:24:39 +03:00
if err = issue . getLabels ( e ) ; err != nil {
2015-08-13 11:07:11 +03:00
return err
}
2016-11-22 14:24:39 +03:00
for idx := range issue . Labels {
if issue . IsClosed {
issue . Labels [ idx ] . NumClosedIssues ++
2015-08-13 11:07:11 +03:00
} else {
2016-11-22 14:24:39 +03:00
issue . Labels [ idx ] . NumClosedIssues --
2015-08-13 11:07:11 +03:00
}
2016-11-22 14:24:39 +03:00
if err = updateLabel ( e , issue . Labels [ idx ] ) ; err != nil {
2015-08-13 11:07:11 +03:00
return err
}
}
2016-03-05 20:58:51 +03:00
// Update issue count of milestone
2016-11-22 14:24:39 +03:00
if err = changeMilestoneIssueStats ( e , issue ) ; err != nil {
2015-08-13 11:07:11 +03:00
return err
}
2016-03-05 20:58:51 +03:00
// New action comment
2016-11-22 14:24:39 +03:00
if _ , err = createStatusComment ( e , doer , repo , issue ) ; err != nil {
2015-08-13 11:07:11 +03:00
return err
}
return nil
}
2016-03-05 20:58:51 +03:00
// ChangeStatus changes issue status to open or closed.
2016-08-14 13:32:24 +03:00
func ( issue * Issue ) ChangeStatus ( doer * User , repo * Repository , isClosed bool ) ( err error ) {
2015-08-13 11:07:11 +03:00
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
2016-08-14 13:32:24 +03:00
if err = issue . changeStatus ( sess , doer , repo , isClosed ) ; err != nil {
2015-08-13 11:07:11 +03:00
return err
}
2016-08-14 13:32:24 +03:00
if err = sess . Commit ( ) ; err != nil {
return fmt . Errorf ( "Commit: %v" , err )
}
if issue . IsPull {
// Merge pull request calls issue.changeStatus so we need to handle separately.
issue . PullRequest . Issue = issue
apiPullRequest := & api . PullRequestPayload {
Index : issue . Index ,
PullRequest : issue . PullRequest . APIFormat ( ) ,
2016-12-06 02:48:51 +03:00
Repository : repo . APIFormat ( AccessModeNone ) ,
2016-08-14 13:32:24 +03:00
Sender : doer . APIFormat ( ) ,
}
if isClosed {
2016-11-07 18:37:32 +03:00
apiPullRequest . Action = api . HookIssueClosed
2016-08-14 13:32:24 +03:00
} else {
2016-11-29 11:25:47 +03:00
apiPullRequest . Action = api . HookIssueReOpened
2016-08-14 13:32:24 +03:00
}
2016-11-07 18:37:32 +03:00
err = PrepareWebhooks ( repo , HookEventPullRequest , apiPullRequest )
2016-08-14 13:32:24 +03:00
}
if err != nil {
log . Error ( 4 , "PrepareWebhooks [is_pull: %v, is_closed: %v]: %v" , issue . IsPull , isClosed , err )
} else {
go HookQueue . Add ( repo . ID )
}
return nil
}
2016-11-24 11:41:11 +03:00
// ChangeTitle changes the title of this issue, as the given user.
2016-08-14 13:32:24 +03:00
func ( issue * Issue ) ChangeTitle ( doer * User , title string ) ( err error ) {
oldTitle := issue . Title
issue . Title = title
if err = UpdateIssueCols ( issue , "name" ) ; err != nil {
return fmt . Errorf ( "UpdateIssueCols: %v" , err )
}
if issue . IsPull {
issue . PullRequest . Issue = issue
2016-11-07 18:37:32 +03:00
err = PrepareWebhooks ( issue . Repo , HookEventPullRequest , & api . PullRequestPayload {
Action : api . HookIssueEdited ,
2016-08-14 13:32:24 +03:00
Index : issue . Index ,
Changes : & api . ChangesPayload {
Title : & api . ChangesFromPayload {
From : oldTitle ,
} ,
} ,
PullRequest : issue . PullRequest . APIFormat ( ) ,
2016-12-06 02:48:51 +03:00
Repository : issue . Repo . APIFormat ( AccessModeNone ) ,
2016-08-14 13:32:24 +03:00
Sender : doer . APIFormat ( ) ,
} )
}
if err != nil {
log . Error ( 4 , "PrepareWebhooks [is_pull: %v]: %v" , issue . IsPull , err )
} else {
go HookQueue . Add ( issue . RepoID )
}
return nil
2015-08-13 11:07:11 +03:00
}
2016-11-24 11:41:11 +03:00
// ChangeContent changes issue content, as the given user.
2016-08-14 13:32:24 +03:00
func ( issue * Issue ) ChangeContent ( doer * User , content string ) ( err error ) {
oldContent := issue . Content
issue . Content = content
if err = UpdateIssueCols ( issue , "content" ) ; err != nil {
return fmt . Errorf ( "UpdateIssueCols: %v" , err )
}
if issue . IsPull {
issue . PullRequest . Issue = issue
2016-11-07 18:37:32 +03:00
err = PrepareWebhooks ( issue . Repo , HookEventPullRequest , & api . PullRequestPayload {
Action : api . HookIssueEdited ,
2016-08-14 13:32:24 +03:00
Index : issue . Index ,
Changes : & api . ChangesPayload {
Body : & api . ChangesFromPayload {
From : oldContent ,
} ,
} ,
PullRequest : issue . PullRequest . APIFormat ( ) ,
2016-12-06 02:48:51 +03:00
Repository : issue . Repo . APIFormat ( AccessModeNone ) ,
2016-08-14 13:32:24 +03:00
Sender : doer . APIFormat ( ) ,
} )
}
if err != nil {
log . Error ( 4 , "PrepareWebhooks [is_pull: %v]: %v" , issue . IsPull , err )
} else {
go HookQueue . Add ( issue . RepoID )
}
return nil
}
2016-11-24 11:41:11 +03:00
// ChangeAssignee changes the Asssignee field of this issue.
2016-08-14 13:32:24 +03:00
func ( issue * Issue ) ChangeAssignee ( doer * User , assigneeID int64 ) ( err error ) {
issue . AssigneeID = assigneeID
if err = UpdateIssueUserByAssignee ( issue ) ; err != nil {
return fmt . Errorf ( "UpdateIssueUserByAssignee: %v" , err )
}
issue . Assignee , err = GetUserByID ( issue . AssigneeID )
if err != nil && ! IsErrUserNotExist ( err ) {
log . Error ( 4 , "GetUserByID [assignee_id: %v]: %v" , issue . AssigneeID , err )
2015-10-25 10:10:22 +03:00
return nil
}
2016-08-14 13:32:24 +03:00
// Error not nil here means user does not exist, which is remove assignee.
isRemoveAssignee := err != nil
if issue . IsPull {
issue . PullRequest . Issue = issue
apiPullRequest := & api . PullRequestPayload {
Index : issue . Index ,
PullRequest : issue . PullRequest . APIFormat ( ) ,
2016-12-06 02:48:51 +03:00
Repository : issue . Repo . APIFormat ( AccessModeNone ) ,
2016-08-14 13:32:24 +03:00
Sender : doer . APIFormat ( ) ,
}
if isRemoveAssignee {
2016-11-07 18:37:32 +03:00
apiPullRequest . Action = api . HookIssueUnassigned
2016-08-14 13:32:24 +03:00
} else {
2016-11-07 18:37:32 +03:00
apiPullRequest . Action = api . HookIssueAssigned
2016-08-14 13:32:24 +03:00
}
2016-11-07 18:37:32 +03:00
err = PrepareWebhooks ( issue . Repo , HookEventPullRequest , apiPullRequest )
2016-08-14 13:32:24 +03:00
}
if err != nil {
log . Error ( 4 , "PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v" , issue . IsPull , isRemoveAssignee , err )
} else {
go HookQueue . Add ( issue . RepoID )
}
return nil
2015-10-25 10:10:22 +03:00
}
2016-11-24 11:41:11 +03:00
// NewIssueOptions represents the options of a new issue.
2016-08-16 04:40:32 +03:00
type NewIssueOptions struct {
Repo * Repository
Issue * Issue
LableIDs [ ] int64
Attachments [ ] string // In UUID format.
IsPull bool
}
2016-03-14 06:20:22 +03:00
2016-08-16 08:20:55 +03:00
func newIssue ( e * xorm . Session , opts NewIssueOptions ) ( err error ) {
2016-08-16 04:40:32 +03:00
opts . Issue . Title = strings . TrimSpace ( opts . Issue . Title )
opts . Issue . Index = opts . Repo . NextIssueIndex ( )
2016-08-16 20:19:09 +03:00
if opts . Issue . MilestoneID > 0 {
2016-08-25 02:05:56 +03:00
milestone , err := getMilestoneByRepoID ( e , opts . Issue . RepoID , opts . Issue . MilestoneID )
2016-08-16 20:19:09 +03:00
if err != nil && ! IsErrMilestoneNotExist ( err ) {
return fmt . Errorf ( "getMilestoneByID: %v" , err )
}
// Assume milestone is invalid and drop silently.
opts . Issue . MilestoneID = 0
if milestone != nil {
opts . Issue . MilestoneID = milestone . ID
opts . Issue . Milestone = milestone
if err = changeMilestoneAssign ( e , opts . Issue , - 1 ) ; err != nil {
return err
}
}
}
2016-08-16 04:40:32 +03:00
if opts . Issue . AssigneeID > 0 {
2016-08-16 20:19:09 +03:00
assignee , err := getUserByID ( e , opts . Issue . AssigneeID )
if err != nil && ! IsErrUserNotExist ( err ) {
return fmt . Errorf ( "getUserByID: %v" , err )
}
// Assume assignee is invalid and drop silently.
opts . Issue . AssigneeID = 0
if assignee != nil {
2016-11-07 19:20:37 +03:00
valid , err := hasAccess ( e , assignee , opts . Repo , AccessModeWrite )
2016-08-16 20:19:09 +03:00
if err != nil {
return fmt . Errorf ( "hasAccess [user_id: %d, repo_id: %d]: %v" , assignee . ID , opts . Repo . ID , err )
}
if valid {
opts . Issue . AssigneeID = assignee . ID
opts . Issue . Assignee = assignee
}
2016-03-14 06:20:22 +03:00
}
}
2016-08-16 20:19:09 +03:00
// Milestone and assignee validation should happen before insert actual object.
2016-08-16 04:40:32 +03:00
if _ , err = e . Insert ( opts . Issue ) ; err != nil {
2014-05-07 20:09:30 +04:00
return err
2015-09-02 23:18:09 +03:00
}
2016-08-16 04:40:32 +03:00
if opts . IsPull {
_ , err = e . Exec ( "UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?" , opts . Issue . RepoID )
2015-09-02 23:18:09 +03:00
} else {
2016-08-16 04:40:32 +03:00
_ , err = e . Exec ( "UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?" , opts . Issue . RepoID )
2015-09-02 23:18:09 +03:00
}
if 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
2016-08-16 04:40:32 +03:00
if len ( opts . LableIDs ) > 0 {
2016-11-21 22:08:21 +03:00
// During the session, SQLite3 driver cannot handle retrieve objects after update something.
2016-03-06 04:45:23 +03:00
// So we have to get all needed labels first.
2016-08-16 04:40:32 +03:00
labels := make ( [ ] * Label , 0 , len ( opts . LableIDs ) )
if err = e . In ( "id" , opts . LableIDs ) . Find ( & labels ) ; err != nil {
return fmt . Errorf ( "find all labels [label_ids: %v]: %v" , opts . LableIDs , err )
2016-03-06 04:45:23 +03:00
}
2015-08-14 19:42:43 +03:00
2016-03-06 04:45:23 +03:00
for _ , label := range labels {
2016-08-16 04:40:32 +03:00
// Silently drop invalid labels.
if label . RepoID != opts . Repo . ID {
2016-03-14 06:20:22 +03:00
continue
}
2016-08-16 04:40:32 +03:00
if err = opts . Issue . addLabel ( e , label ) ; err != nil {
return fmt . Errorf ( "addLabel [id: %d]: %v" , label . ID , err )
2016-03-06 04:45:23 +03:00
}
2015-08-10 11:52:08 +03:00
}
}
2016-08-16 04:40:32 +03:00
if err = newIssueUsers ( e , opts . Repo , opts . Issue ) ; err != nil {
2015-08-10 16:47:23 +03:00
return err
}
2016-08-16 04:40:32 +03:00
if len ( opts . Attachments ) > 0 {
attachments , err := getAttachmentsByUUIDs ( e , opts . Attachments )
2015-09-02 02:07:02 +03:00
if err != nil {
2016-08-16 04:40:32 +03:00
return fmt . Errorf ( "getAttachmentsByUUIDs [uuids: %v]: %v" , opts . Attachments , err )
2015-09-02 02:07:02 +03:00
}
2016-08-16 04:40:32 +03:00
for i := 0 ; i < len ( attachments ) ; i ++ {
attachments [ i ] . IssueID = opts . Issue . ID
if _ , err = e . Id ( attachments [ i ] . ID ) . Update ( attachments [ i ] ) ; err != nil {
return fmt . Errorf ( "update attachment [id: %d]: %v" , attachments [ i ] . ID , err )
}
2015-08-11 18:24:40 +03:00
}
}
2016-08-16 04:40:32 +03:00
return opts . Issue . loadAttributes ( e )
2015-09-02 02:07:02 +03:00
}
// NewIssue creates new issue with labels for repository.
func NewIssue ( repo * Repository , issue * Issue , labelIDs [ ] int64 , uuids [ ] string ) ( err error ) {
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
2016-08-16 08:20:55 +03:00
if err = newIssue ( sess , NewIssueOptions {
2016-08-16 04:40:32 +03:00
Repo : repo ,
Issue : issue ,
LableIDs : labelIDs ,
Attachments : uuids ,
} ) ; err != nil {
2015-09-02 02:07:02 +03:00
return fmt . Errorf ( "newIssue: %v" , err )
}
2016-03-14 06:20:22 +03:00
if err = sess . Commit ( ) ; err != nil {
return fmt . Errorf ( "Commit: %v" , err )
}
2016-08-16 04:40:32 +03:00
if err = NotifyWatchers ( & Action {
2016-07-23 20:08:22 +03:00
ActUserID : issue . Poster . ID ,
2015-08-10 18:31:59 +03:00
ActUserName : issue . Poster . Name ,
2016-11-07 18:37:32 +03:00
OpType : ActionCreateIssue ,
2016-08-14 13:32:24 +03:00
Content : fmt . Sprintf ( "%d|%s" , issue . Index , issue . Title ) ,
2015-08-10 18:31:59 +03:00
RepoID : repo . ID ,
RepoUserName : repo . Owner . Name ,
RepoName : repo . Name ,
IsPrivate : repo . IsPrivate ,
2016-08-16 04:40:32 +03:00
} ) ; err != nil {
2016-07-15 19:36:39 +03:00
log . Error ( 4 , "NotifyWatchers: %v" , err )
2016-08-16 04:40:32 +03:00
}
if err = issue . MailParticipants ( ) ; err != nil {
2016-07-15 19:36:39 +03:00
log . Error ( 4 , "MailParticipants: %v" , err )
2015-08-10 18:31:59 +03:00
}
2016-03-14 06:20:22 +03:00
return nil
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.
2015-09-03 11:34:08 +03:00
func GetIssueByRef ( ref string ) ( * Issue , error ) {
2014-07-23 15:48:06 +04:00
n := strings . IndexByte ( ref , byte ( '#' ) )
if n == - 1 {
2016-11-22 14:24:39 +03:00
return nil , errMissingIssueNumber
2014-07-23 15:48:06 +04:00
}
2015-09-03 11:34:08 +03:00
index , err := com . StrTo ( ref [ n + 1 : ] ) . Int64 ( )
if err != nil {
return nil , err
2014-07-23 15:48:06 +04:00
}
2015-09-03 11:34:08 +03:00
repo , err := GetRepositoryByRef ( ref [ : n ] )
if err != nil {
return nil , err
2014-07-23 15:48:06 +04:00
}
2015-09-03 11:34:08 +03:00
issue , err := GetIssueByIndex ( repo . ID , index )
if err != nil {
return nil , err
}
2016-07-21 09:26:30 +03:00
return issue , issue . LoadAttributes ( )
2014-07-23 15:48:06 +04:00
}
2016-11-22 14:24:39 +03:00
// GetRawIssueByIndex returns raw issue without loading attributes by index in a repository.
2016-08-26 23:40:53 +03:00
func GetRawIssueByIndex ( repoID , index int64 ) ( * Issue , error ) {
2015-08-12 12:04:23 +03:00
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
}
2016-08-26 23:40:53 +03:00
return issue , nil
}
// GetIssueByIndex returns issue by index in a repository.
func GetIssueByIndex ( repoID , index int64 ) ( * Issue , error ) {
issue , err := GetRawIssueByIndex ( repoID , index )
if err != nil {
return nil , err
}
2016-07-21 09:26:30 +03:00
return issue , issue . LoadAttributes ( )
2014-03-23 00:00:46 +04:00
}
2016-08-14 13:32:24 +03:00
func getIssueByID ( e Engine , id int64 ) ( * Issue , error ) {
2015-08-12 12:04:23 +03:00
issue := new ( Issue )
2016-08-14 13:32:24 +03:00
has , err := e . 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
}
2016-07-21 09:26:30 +03:00
return issue , issue . LoadAttributes ( )
2014-05-08 00:51:14 +04:00
}
2016-08-14 13:32:24 +03:00
// GetIssueByID returns an issue by given ID.
func GetIssueByID ( id int64 ) ( * Issue , error ) {
return getIssueByID ( x , id )
}
2016-11-24 11:41:11 +03:00
// IssuesOptions represents options of an issue.
2015-09-02 23:18:09 +03:00
type IssuesOptions struct {
UserID int64
AssigneeID int64
RepoID int64
PosterID int64
MilestoneID int64
RepoIDs [ ] int64
Page int
IsClosed bool
IsMention bool
IsPull bool
Labels string
SortType string
}
2015-08-04 17:24:04 +03:00
// Issues returns a list of issues by given conditions.
2015-09-02 23:18:09 +03:00
func Issues ( opts * IssuesOptions ) ( [ ] * Issue , error ) {
2016-03-14 06:20:22 +03:00
if opts . Page <= 0 {
opts . Page = 1
}
2016-07-23 19:23:54 +03:00
sess := x . Limit ( setting . UI . IssuePagingNum , ( opts . Page - 1 ) * setting . UI . IssuePagingNum )
2014-03-23 00:00:46 +04:00
2015-09-02 23:18:09 +03:00
if opts . RepoID > 0 {
2016-11-12 15:06:33 +03:00
sess . And ( "issue.repo_id=?" , opts . RepoID )
} else if len ( opts . RepoIDs ) > 0 {
2015-09-01 13:31:47 +03:00
// In case repository IDs are provided but actually no repository has issue.
2016-11-12 15:06:33 +03:00
sess . In ( "issue.repo_id" , opts . RepoIDs )
2014-03-23 00:00:46 +04:00
}
2016-11-12 15:06:33 +03:00
sess . And ( "issue.is_closed=?" , opts . IsClosed )
2014-03-23 00:00:46 +04:00
2015-09-02 23:18:09 +03:00
if opts . AssigneeID > 0 {
2016-07-17 04:18:35 +03:00
sess . And ( "issue.assignee_id=?" , opts . AssigneeID )
2015-09-02 23:18:09 +03:00
} else if opts . PosterID > 0 {
2016-07-17 04:18:35 +03:00
sess . And ( "issue.poster_id=?" , opts . PosterID )
2014-03-22 21:50:50 +04:00
}
2015-09-02 23:18:09 +03:00
if opts . MilestoneID > 0 {
2016-07-17 04:18:35 +03:00
sess . And ( "issue.milestone_id=?" , opts . MilestoneID )
2014-03-22 21:50:50 +04:00
}
2016-07-17 04:18:35 +03:00
sess . And ( "issue.is_pull=?" , opts . IsPull )
2015-09-02 23:18:09 +03:00
switch opts . SortType {
2014-03-22 21:50:50 +04:00
case "oldest" :
2016-07-17 04:18:35 +03:00
sess . Asc ( "issue.created_unix" )
2014-03-22 21:50:50 +04:00
case "recentupdate" :
2016-07-17 04:18:35 +03:00
sess . Desc ( "issue.updated_unix" )
2014-03-22 21:50:50 +04:00
case "leastupdate" :
2016-07-17 04:18:35 +03:00
sess . Asc ( "issue.updated_unix" )
2014-03-22 21:50:50 +04:00
case "mostcomment" :
2016-07-17 04:18:35 +03:00
sess . Desc ( "issue.num_comments" )
2014-03-22 21:50:50 +04:00
case "leastcomment" :
2016-07-17 04:18:35 +03:00
sess . Asc ( "issue.num_comments" )
2014-05-08 04:36:00 +04:00
case "priority" :
2016-07-17 04:18:35 +03:00
sess . Desc ( "issue.priority" )
2014-03-22 21:50:50 +04:00
default :
2016-07-17 04:18:35 +03:00
sess . Desc ( "issue.created_unix" )
2014-03-22 21:50:50 +04:00
}
2016-05-06 22:40:41 +03:00
if len ( opts . Labels ) > 0 && opts . Labels != "0" {
2016-12-22 11:58:04 +03:00
labelIDs , err := base . StringsToInt64s ( strings . Split ( opts . Labels , "," ) )
if err != nil {
return nil , err
}
2016-05-06 22:40:41 +03:00
if len ( labelIDs ) > 0 {
2016-11-10 18:16:32 +03:00
sess .
Join ( "INNER" , "issue_label" , "issue.id = issue_label.issue_id" ) .
In ( "issue_label.label_id" , labelIDs )
2016-04-26 07:22:03 +03:00
}
2015-08-10 16:47:23 +03:00
}
2015-09-02 23:18:09 +03:00
if opts . IsMention {
2016-11-10 18:16:32 +03:00
sess .
Join ( "INNER" , "issue_user" , "issue.id = issue_user.issue_id" ) .
And ( "issue_user.is_mentioned = ?" , true )
2016-03-28 00:26:45 +03:00
2015-09-02 23:18:09 +03:00
if opts . UserID > 0 {
2016-03-28 01:05:49 +03:00
sess . And ( "issue_user.uid = ?" , opts . UserID )
2015-07-24 21:52:25 +03:00
}
}
2016-07-23 19:23:54 +03:00
issues := make ( [ ] * Issue , 0 , setting . UI . IssuePagingNum )
2016-08-26 23:40:53 +03:00
if err := sess . Find ( & issues ) ; err != nil {
return nil , fmt . Errorf ( "Find: %v" , err )
}
// FIXME: use IssueList to improve performance.
for i := range issues {
if err := issues [ i ] . LoadAttributes ( ) ; err != nil {
return nil , fmt . Errorf ( "LoadAttributes [%d]: %v" , issues [ i ] . ID , err )
}
}
return issues , nil
2014-03-22 21:50:50 +04:00
}
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" `
2015-09-10 18:40:34 +03:00
UID int64 ` xorm:"INDEX" ` // User ID.
2015-08-10 16:47:23 +03:00
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 {
2016-08-16 04:40:32 +03:00
assignees , err := repo . getAssignees ( e )
2014-05-08 00:51:14 +04:00
if err != nil {
2016-08-16 04:40:32 +03:00
return fmt . Errorf ( "getAssignees: %v" , err )
}
// Poster can be anyone, append later if not one of assignees.
isPosterAssignee := false
// Leave a seat for poster itself to append later, but if poster is one of assignee
// and just waste 1 unit is cheaper than re-allocate memory once.
issueUsers := make ( [ ] * IssueUser , 0 , len ( assignees ) + 1 )
for _ , assignee := range assignees {
isPoster := assignee . ID == issue . PosterID
issueUsers = append ( issueUsers , & IssueUser {
IssueID : issue . ID ,
RepoID : repo . ID ,
UID : assignee . ID ,
IsPoster : isPoster ,
IsAssigned : assignee . ID == issue . AssigneeID ,
} )
if ! isPosterAssignee && isPoster {
isPosterAssignee = true
}
2014-05-08 00:51:14 +04:00
}
2016-08-16 04:40:32 +03:00
if ! isPosterAssignee {
issueUsers = append ( issueUsers , & IssueUser {
IssueID : issue . ID ,
RepoID : repo . ID ,
UID : issue . PosterID ,
IsPoster : true ,
} )
2015-02-12 05:58:37 +03:00
}
2016-08-16 04:40:32 +03:00
if _ , err = e . Insert ( issueUsers ) ; err != nil {
return err
2014-05-08 00:51:14 +04:00
}
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.
2016-11-22 14:24:39 +03:00
func PairsContains ( ius [ ] * IssueUser , issueID , uid int64 ) int {
2014-05-07 20:09:30 +04:00
for i := range ius {
2016-11-22 14:24:39 +03:00
if ius [ i ] . IssueID == issueID &&
2015-08-10 16:47:23 +03:00
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
ius := make ( [ ] * IssueUser , 0 , 10 )
2016-11-10 18:16:32 +03:00
sess := x .
Limit ( 20 , ( page - 1 ) * 20 ) .
Where ( "is_closed=?" , isClosed ) .
In ( "repo_id" , rids )
2014-05-08 00:51:14 +04:00
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 )
2016-11-10 18:16:32 +03: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 {
2016-11-07 19:24:59 +03:00
case FilterModeAssign :
2014-05-08 00:51:14 +04:00
sess . And ( "is_assigned=?" , true )
2016-11-07 19:24:59 +03:00
case FilterModeCreate :
2014-05-08 00:51:14 +04:00
sess . And ( "is_poster=?" , true )
default :
return ius , nil
}
err := sess . Find ( & ius )
return ius , err
2014-03-28 00:31:32 +04:00
}
2016-07-15 19:36:39 +03:00
// UpdateIssueMentions extracts mentioned people from content and
// updates issue-user relations for them.
2016-12-22 12:00:39 +03:00
func UpdateIssueMentions ( e Engine , issueID int64 , mentions [ ] string ) error {
2016-07-15 19:36:39 +03:00
if len ( mentions ) == 0 {
return nil
2015-12-21 15:24:11 +03:00
}
2016-07-15 19:36:39 +03:00
for i := range mentions {
mentions [ i ] = strings . ToLower ( mentions [ i ] )
}
users := make ( [ ] * User , 0 , len ( mentions ) )
2016-12-22 12:00:39 +03:00
if err := e . In ( "lower_name" , mentions ) . Asc ( "lower_name" ) . Find ( & users ) ; err != nil {
2016-07-15 19:36:39 +03:00
return fmt . Errorf ( "find mentioned users: %v" , err )
2015-12-21 15:24:11 +03:00
}
2016-07-15 19:36:39 +03:00
ids := make ( [ ] int64 , 0 , len ( mentions ) )
2015-12-21 15:24:11 +03:00
for _ , user := range users {
2016-07-23 20:08:22 +03:00
ids = append ( ids , user . ID )
2016-07-15 19:36:39 +03:00
if ! user . IsOrganization ( ) || user . NumMembers == 0 {
2015-12-21 15:24:11 +03:00
continue
}
2016-07-15 19:36:39 +03:00
memberIDs := make ( [ ] int64 , 0 , user . NumMembers )
2016-07-23 20:08:22 +03:00
orgUsers , err := GetOrgUsersByOrgID ( user . ID )
2015-12-21 15:24:11 +03:00
if err != nil {
2016-07-23 20:08:22 +03:00
return fmt . Errorf ( "GetOrgUsersByOrgID [%d]: %v" , user . ID , err )
2015-12-21 15:24:11 +03:00
}
for _ , orgUser := range orgUsers {
2016-07-15 19:36:39 +03:00
memberIDs = append ( memberIDs , orgUser . ID )
2015-12-21 15:24:11 +03:00
}
2016-07-15 19:36:39 +03:00
ids = append ( ids , memberIDs ... )
2015-12-21 15:24:11 +03:00
}
2016-12-22 12:00:39 +03:00
if err := UpdateIssueUsersByMentions ( e , issueID , ids ) ; err != nil {
2016-07-15 19:36:39 +03:00
return fmt . Errorf ( "UpdateIssueUsersByMentions: %v" , err )
2015-12-21 15:24:11 +03:00
}
return nil
}
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 (
2016-11-07 19:24:59 +03:00
FilterModeAll = iota
FilterModeAssign
FilterModeCreate
FilterModeMention
2014-05-07 20:09:30 +04:00
)
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
}
2016-11-24 11:41:11 +03:00
// IssueStatsOptions contains parameters accepted by GetIssueStats.
2015-09-02 23:18:09 +03:00
type IssueStatsOptions struct {
RepoID int64
UserID int64
2016-04-26 07:07:49 +03:00
Labels string
2015-09-02 23:18:09 +03:00
MilestoneID int64
AssigneeID int64
FilterMode int
IsPull bool
}
2014-05-08 00:51:14 +04:00
// GetIssueStats returns issue statistic information by given conditions.
2015-09-02 23:18:09 +03:00
func GetIssueStats ( opts * IssueStatsOptions ) * IssueStats {
2014-05-07 20:09:30 +04:00
stats := & IssueStats { }
2015-07-24 21:52:25 +03:00
2016-04-26 07:07:49 +03:00
countSession := func ( opts * IssueStatsOptions ) * xorm . Session {
2016-11-10 18:16:32 +03:00
sess := x .
Where ( "issue.repo_id = ?" , opts . RepoID ) .
And ( "is_pull = ?" , opts . IsPull )
2015-08-10 16:47:23 +03:00
2016-05-06 22:40:41 +03:00
if len ( opts . Labels ) > 0 && opts . Labels != "0" {
2016-12-22 11:58:04 +03:00
labelIDs , err := base . StringsToInt64s ( strings . Split ( opts . Labels , "," ) )
if err != nil {
log . Warn ( "Malformed Labels argument: %s" , opts . Labels )
} else if len ( labelIDs ) > 0 {
sess . Join ( "INNER" , "issue_label" , "issue.id = issue_id" ) .
2016-11-10 18:16:32 +03:00
In ( "label_id" , labelIDs )
2016-04-26 07:22:03 +03:00
}
2016-04-26 07:07:49 +03:00
}
if opts . MilestoneID > 0 {
sess . And ( "issue.milestone_id = ?" , opts . MilestoneID )
}
if opts . AssigneeID > 0 {
sess . And ( "assignee_id = ?" , opts . AssigneeID )
}
return sess
2015-08-15 07:07:08 +03:00
}
2015-09-02 23:18:09 +03:00
switch opts . FilterMode {
2016-11-07 19:24:59 +03:00
case FilterModeAll , FilterModeAssign :
2016-04-26 07:07:49 +03:00
stats . OpenCount , _ = countSession ( opts ) .
2016-04-26 07:22:03 +03:00
And ( "is_closed = ?" , false ) .
2016-04-26 07:07:49 +03:00
Count ( & Issue { } )
2015-07-24 21:52:25 +03:00
2016-04-26 07:07:49 +03:00
stats . ClosedCount , _ = countSession ( opts ) .
2016-04-26 07:22:03 +03:00
And ( "is_closed = ?" , true ) .
2016-04-26 07:07:49 +03:00
Count ( & Issue { } )
2016-11-07 19:24:59 +03:00
case FilterModeCreate :
2016-04-26 07:07:49 +03:00
stats . OpenCount , _ = countSession ( opts ) .
And ( "poster_id = ?" , opts . UserID ) .
2016-04-26 07:22:03 +03:00
And ( "is_closed = ?" , false ) .
2016-04-26 07:07:49 +03:00
Count ( & Issue { } )
stats . ClosedCount , _ = countSession ( opts ) .
And ( "poster_id = ?" , opts . UserID ) .
2016-04-26 07:22:03 +03:00
And ( "is_closed = ?" , true ) .
2016-04-26 07:07:49 +03:00
Count ( & Issue { } )
2016-11-07 19:24:59 +03:00
case FilterModeMention :
2016-04-26 07:07:49 +03:00
stats . OpenCount , _ = countSession ( opts ) .
Join ( "INNER" , "issue_user" , "issue.id = issue_user.issue_id" ) .
2016-07-17 04:18:35 +03:00
And ( "issue_user.uid = ?" , opts . UserID ) .
And ( "issue_user.is_mentioned = ?" , true ) .
And ( "issue.is_closed = ?" , false ) .
2016-04-26 07:07:49 +03:00
Count ( & Issue { } )
stats . ClosedCount , _ = countSession ( opts ) .
Join ( "INNER" , "issue_user" , "issue.id = issue_user.issue_id" ) .
2016-07-17 04:18:35 +03:00
And ( "issue_user.uid = ?" , opts . UserID ) .
And ( "issue_user.is_mentioned = ?" , true ) .
And ( "issue.is_closed = ?" , true ) .
2016-04-26 07:07:49 +03:00
Count ( & Issue { } )
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.
2015-09-02 23:18:09 +03:00
func GetUserIssueStats ( repoID , uid int64 , repoIDs [ ] int64 , filterMode int , isPull bool ) * IssueStats {
2014-05-07 20:09:30 +04:00
stats := & IssueStats { }
2015-08-25 17:58:34 +03:00
2016-04-26 07:07:49 +03:00
countSession := func ( isClosed , isPull bool , repoID int64 , repoIDs [ ] int64 ) * xorm . Session {
2016-11-10 18:16:32 +03:00
sess := x .
Where ( "issue.is_closed = ?" , isClosed ) .
And ( "issue.is_pull = ?" , isPull )
2015-09-02 23:18:09 +03:00
2016-11-12 15:06:33 +03:00
if repoID > 0 {
2016-04-26 07:22:03 +03:00
sess . And ( "repo_id = ?" , repoID )
2016-11-12 15:06:33 +03:00
} else if len ( repoIDs ) > 0 {
2016-04-26 07:22:03 +03:00
sess . In ( "repo_id" , repoIDs )
2016-04-26 07:07:49 +03:00
}
return sess
2015-09-02 23:18:09 +03:00
}
2016-04-26 07:07:49 +03:00
stats . AssignCount , _ = countSession ( false , isPull , repoID , repoIDs ) .
And ( "assignee_id = ?" , uid ) .
Count ( & Issue { } )
stats . CreateCount , _ = countSession ( false , isPull , repoID , repoIDs ) .
2016-08-10 09:19:52 +03:00
And ( "poster_id = ?" , uid ) .
2016-04-26 07:07:49 +03:00
Count ( & Issue { } )
openCountSession := countSession ( false , isPull , repoID , repoIDs )
closedCountSession := countSession ( true , isPull , repoID , repoIDs )
2015-09-02 23:18:09 +03:00
2015-08-25 17:58:34 +03:00
switch filterMode {
2016-11-07 19:24:59 +03:00
case FilterModeAssign :
2016-04-26 07:07:49 +03:00
openCountSession . And ( "assignee_id = ?" , uid )
closedCountSession . And ( "assignee_id = ?" , uid )
2016-11-07 19:24:59 +03:00
case FilterModeCreate :
2016-04-26 07:07:49 +03:00
openCountSession . And ( "poster_id = ?" , uid )
closedCountSession . And ( "poster_id = ?" , uid )
2015-08-25 17:58:34 +03:00
}
2016-04-26 07:07:49 +03:00
stats . OpenCount , _ = openCountSession . Count ( & Issue { } )
stats . ClosedCount , _ = closedCountSession . Count ( & Issue { } )
2014-05-07 20:09:30 +04:00
return stats
}
2015-08-25 17:58:34 +03:00
// GetRepoIssueStats returns number of open and closed repository issues by given filter mode.
2015-09-02 23:18:09 +03:00
func GetRepoIssueStats ( repoID , uid int64 , filterMode int , isPull bool ) ( numOpen int64 , numClosed int64 ) {
2016-04-26 07:07:49 +03:00
countSession := func ( isClosed , isPull bool , repoID int64 ) * xorm . Session {
2016-11-10 18:16:32 +03:00
sess := x .
Where ( "issue.repo_id = ?" , isClosed ) .
2016-04-26 07:22:03 +03:00
And ( "is_pull = ?" , isPull ) .
And ( "repo_id = ?" , repoID )
2015-09-02 23:18:09 +03:00
2016-04-26 07:07:49 +03:00
return sess
2015-09-02 23:18:09 +03:00
}
2016-04-26 07:07:49 +03:00
openCountSession := countSession ( false , isPull , repoID )
closedCountSession := countSession ( true , isPull , repoID )
2015-08-25 17:58:34 +03:00
switch filterMode {
2016-11-07 19:24:59 +03:00
case FilterModeAssign :
2016-04-26 07:07:49 +03:00
openCountSession . And ( "assignee_id = ?" , uid )
closedCountSession . And ( "assignee_id = ?" , uid )
2016-11-07 19:24:59 +03:00
case FilterModeCreate :
2016-04-26 07:07:49 +03:00
openCountSession . And ( "poster_id = ?" , uid )
closedCountSession . And ( "poster_id = ?" , uid )
2015-08-25 17:58:34 +03:00
}
2016-04-26 07:07:49 +03:00
openResult , _ := openCountSession . Count ( & Issue { } )
closedResult , _ := closedCountSession . Count ( & Issue { } )
return openResult , closedResult
2015-08-25 17:58:34 +03:00
}
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
}
2015-10-24 10:36:47 +03:00
// UpdateIssue updates all fields of given issue.
func UpdateIssue ( issue * Issue ) error {
return updateIssue ( x , issue )
}
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 ) {
2016-08-14 13:32:24 +03:00
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 {
2016-08-14 13:32:24 +03:00
if _ , err = e . Exec ( "UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?" , true , issue . AssigneeID , issue . ID ) ; err != nil {
2015-08-14 19:42:43 +03:00
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.
2016-12-22 12:00:39 +03:00
func UpdateIssueUsersByMentions ( e Engine , issueID int64 , uids [ ] int64 ) error {
2014-05-08 00:51:14 +04:00
for _ , uid := range uids {
2016-07-15 19:36:39 +03:00
iu := & IssueUser {
UID : uid ,
IssueID : issueID ,
}
2016-12-22 12:00:39 +03:00
has , err := e . Get ( iu )
2014-05-08 00:51:14 +04:00
if err != nil {
return err
}
iu . IsMentioned = true
if has {
2016-12-22 12:00:39 +03:00
_ , err = e . Id ( iu . ID ) . AllCols ( ) . Update ( iu )
2014-05-08 00:51:14 +04:00
} else {
2016-12-22 12:00:39 +03:00
_ , err = e . Insert ( iu )
2014-05-08 00:51:14 +04:00
}
if err != nil {
return err
}
}
return nil
}
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
2016-03-10 03:53:30 +03:00
NumOpenIssues int ` xorm:"-" `
Completeness int // Percentage(1-100).
IsOverDue bool ` xorm:"-" `
DeadlineString string ` xorm:"-" `
Deadline time . Time ` xorm:"-" `
DeadlineUnix int64
ClosedDate time . Time ` xorm:"-" `
ClosedDateUnix int64
}
2016-11-24 11:41:11 +03:00
// BeforeInsert is invoked from XORM before inserting an object of this type.
2016-03-10 03:53:30 +03:00
func ( m * Milestone ) BeforeInsert ( ) {
2016-07-23 15:24:44 +03:00
m . DeadlineUnix = m . Deadline . Unix ( )
2014-03-22 21:50:50 +04:00
}
2016-11-24 11:41:11 +03:00
// BeforeUpdate is invoked from XORM before updating this object.
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
}
2016-03-10 03:53:30 +03:00
2016-07-23 15:24:44 +03:00
m . DeadlineUnix = m . Deadline . Unix ( )
m . ClosedDateUnix = m . ClosedDate . Unix ( )
2015-08-09 17:45:38 +03:00
}
2016-11-24 11:41:11 +03:00
// AfterSet is invoked from XORM after setting the value of a field of
// this object.
2015-08-06 17:48:11 +03:00
func ( m * Milestone ) AfterSet ( colName string , _ xorm . Cell ) {
2016-03-10 03:53:30 +03:00
switch colName {
2016-03-14 06:20:22 +03:00
case "num_closed_issues" :
m . NumOpenIssues = m . NumIssues - m . NumClosedIssues
2016-03-10 03:53:30 +03:00
case "deadline_unix" :
m . Deadline = time . Unix ( m . DeadlineUnix , 0 ) . Local ( )
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" )
2016-03-10 03:53:30 +03:00
if time . Now ( ) . Local ( ) . After ( m . Deadline ) {
2015-08-05 10:24:26 +03:00
m . IsOverDue = true
}
2016-03-14 06:20:22 +03:00
2016-03-10 03:53:30 +03:00
case "closed_date_unix" :
m . ClosedDate = time . Unix ( m . ClosedDateUnix , 0 ) . Local ( )
2015-08-04 17:24:04 +03:00
}
}
2016-03-14 06:20:22 +03:00
// State returns string representation of milestone status.
2016-08-14 13:32:24 +03:00
func ( m * Milestone ) State ( ) api . StateType {
2016-03-14 06:20:22 +03:00
if m . IsClosed {
2016-11-29 11:25:47 +03:00
return api . StateClosed
2016-08-14 13:32:24 +03:00
}
2016-11-29 11:25:47 +03:00
return api . StateOpen
2016-08-14 13:32:24 +03:00
}
2016-11-24 11:41:11 +03:00
// APIFormat returns this Milestone in API format.
2016-08-14 13:32:24 +03:00
func ( m * Milestone ) APIFormat ( ) * api . Milestone {
apiMilestone := & api . Milestone {
ID : m . ID ,
State : m . State ( ) ,
Title : m . Name ,
Description : m . Content ,
OpenIssues : m . NumOpenIssues ,
ClosedIssues : m . NumClosedIssues ,
}
if m . IsClosed {
apiMilestone . Closed = & m . ClosedDate
}
if m . Deadline . Year ( ) < 9999 {
apiMilestone . Deadline = & m . Deadline
2016-03-14 06:20:22 +03:00
}
2016-08-14 13:32:24 +03:00
return apiMilestone
2014-05-12 22:06:42 +04:00
}
// 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
}
2016-08-25 02:05:56 +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 ( )
}
2016-08-25 02:05:56 +03:00
func getMilestoneByRepoID ( e Engine , repoID , id int64 ) ( * Milestone , error ) {
m := & Milestone {
ID : id ,
RepoID : repoID ,
}
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 {
2016-08-25 02:05:56 +03:00
return nil , ErrMilestoneNotExist { id , repoID }
2015-08-10 13:57:57 +03:00
}
return m , nil
}
2016-11-22 14:24:39 +03:00
// GetMilestoneByRepoID returns the milestone in a repository.
2016-08-25 02:05:56 +03:00
func GetMilestoneByRepoID ( repoID , id int64 ) ( * Milestone , error ) {
return getMilestoneByRepoID ( x , repoID , id )
2014-05-13 21:28:21 +04:00
}
2016-08-25 02:05:56 +03:00
// GetMilestonesByRepoID returns all milestones of a repository.
func GetMilestonesByRepoID ( repoID int64 ) ( [ ] * Milestone , error ) {
2015-08-05 15:23:08 +03:00
miles := make ( [ ] * Milestone , 0 , 10 )
2016-08-25 02:05:56 +03:00
return miles , x . Where ( "repo_id = ?" , repoID ) . Find ( & miles )
2015-08-05 15:23:08 +03:00
}
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 ) {
2016-07-23 19:23:54 +03:00
miles := make ( [ ] * Milestone , 0 , setting . UI . IssuePagingNum )
2016-08-25 02:05:56 +03:00
sess := x . Where ( "repo_id = ? AND is_closed = ?" , repoID , isClosed )
2015-08-04 17:24:04 +03:00
if page > 0 {
2016-07-23 19:23:54 +03:00
sess = sess . Limit ( setting . UI . IssuePagingNum , ( page - 1 ) * setting . UI . IssuePagingNum )
2015-08-04 17:24:04 +03:00
}
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 {
2016-11-10 18:16:32 +03:00
count , _ := e .
Where ( "repo_id=?" , repoID ) .
Count ( new ( Milestone ) )
2015-08-05 15:23:08 +03:00
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 {
2016-11-10 18:16:32 +03:00
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 ) {
2016-11-10 18:16:32 +03:00
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
}
2016-08-25 02:05:56 +03:00
m , err := getMilestoneByRepoID ( e , issue . RepoID , 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
2015-12-06 17:42:23 +03:00
// for the milestone associated with the given issue.
2015-08-13 11:07:11 +03:00
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
}
2016-08-16 04:40:32 +03:00
func changeMilestoneAssign ( e * xorm . Session , issue * Issue , oldMilestoneID int64 ) error {
if oldMilestoneID > 0 {
2016-08-25 02:05:56 +03:00
m , err := getMilestoneByRepoID ( e , issue . RepoID , oldMilestoneID )
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
2016-08-16 04:40:32 +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 {
2016-08-25 02:05:56 +03:00
m , err := getMilestoneByRepoID ( e , issue . RepoID , 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
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
2016-08-16 04:40:32 +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.
2016-08-16 04:40:32 +03:00
func ChangeMilestoneAssign ( issue * Issue , oldMilestoneID int64 ) ( err error ) {
2015-08-10 13:57:57 +03:00
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return err
}
2016-08-16 04:40:32 +03:00
if err = changeMilestoneAssign ( sess , issue , oldMilestoneID ) ; err != nil {
2015-08-10 13:57:57 +03:00
return err
}
2014-05-14 18:55:36 +04:00
return sess . Commit ( )
}
2016-08-25 02:05:56 +03:00
// DeleteMilestoneByRepoID deletes a milestone from a repository.
func DeleteMilestoneByRepoID ( repoID , id int64 ) error {
m , err := GetMilestoneByRepoID ( repoID , id )
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-09-15 17:03:50 +03:00
if _ , err = sess . Id ( m . ID ) . Delete ( new ( Milestone ) ) ; 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
}
2016-08-25 02:05:56 +03:00
if _ , err = sess . Exec ( "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?" , m . ID ) ; err != nil {
2015-08-05 15:23:08 +03:00
return err
2016-08-25 02:05:56 +03:00
} 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 ( )
}
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
2016-03-10 03:53:30 +03:00
Created time . Time ` xorm:"-" `
CreatedUnix int64
}
2016-11-24 11:41:11 +03:00
// BeforeInsert is invoked from XORM before inserting an object of this type.
2016-03-10 03:53:30 +03:00
func ( a * Attachment ) BeforeInsert ( ) {
2016-07-23 15:24:44 +03:00
a . CreatedUnix = time . Now ( ) . Unix ( )
2016-03-10 03:53:30 +03:00
}
2016-11-24 11:41:11 +03:00
// AfterSet is invoked from XORM after setting the value of a field of
// this object.
2016-03-10 03:53:30 +03:00
func ( a * Attachment ) AfterSet ( colName string , _ xorm . Cell ) {
switch colName {
case "created_unix" :
a . Created = time . Unix ( a . CreatedUnix , 0 ) . Local ( )
}
2014-07-23 23:15:47 +04:00
}
2016-11-24 11:41:11 +03:00
// AttachmentLocalPath returns where attachment is stored in local file
// system based on given UUID.
2015-08-11 18:24:40 +03:00
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.
2016-11-22 14:24:39 +03:00
func ( a * Attachment ) LocalPath ( ) string {
return AttachmentLocalPath ( a . UUID )
2015-08-11 18:24:40 +03:00
}
// 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 ,
}
2016-08-30 15:07:50 +03:00
localPath := attach . LocalPath ( )
if err = os . MkdirAll ( path . Dir ( localPath ) , os . ModePerm ) ; err != nil {
2015-08-11 18:24:40 +03:00
return nil , fmt . Errorf ( "MkdirAll: %v" , err )
}
2014-07-23 23:15:47 +04:00
2016-08-30 15:07:50 +03:00
fw , err := os . Create ( localPath )
2015-08-11 18:24:40 +03:00
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 )
}
2016-08-30 15:07:50 +03:00
if _ , err := x . Insert ( attach ) ; err != nil {
2014-07-23 23:15:47 +04:00
return nil , err
}
2016-08-30 15:07:50 +03:00
return attach , nil
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
}
2016-08-16 04:40:32 +03:00
func getAttachmentsByUUIDs ( e Engine , uuids [ ] string ) ( [ ] * Attachment , error ) {
if len ( uuids ) == 0 {
return [ ] * Attachment { } , nil
}
// Silently drop invalid uuids.
attachments := make ( [ ] * Attachment , 0 , len ( uuids ) )
return attachments , e . In ( "uuid" , uuids ) . Find ( & attachments )
}
2015-08-13 11:07:11 +03:00
// GetAttachmentByUUID returns attachment by given UUID.
func GetAttachmentByUUID ( uuid string ) ( * Attachment , error ) {
return getAttachmentByUUID ( x , uuid )
}
2016-08-26 23:40:53 +03:00
func getAttachmentsByIssueID ( e Engine , issueID int64 ) ( [ ] * Attachment , error ) {
2014-07-23 23:15:47 +04:00
attachments := make ( [ ] * Attachment , 0 , 10 )
2016-08-26 23:40:53 +03:00
return attachments , e . Where ( "issue_id = ? AND comment_id = 0" , issueID ) . Find ( & attachments )
}
// GetAttachmentsByIssueID returns all attachments of an issue.
func GetAttachmentsByIssueID ( issueID int64 ) ( [ ] * Attachment , error ) {
return getAttachmentsByIssueID ( x , issueID )
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
}
}
2016-08-11 15:48:08 +03:00
if _ , err := x . Delete ( a ) ; 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.
2016-11-22 14:24:39 +03:00
func DeleteAttachmentsByIssue ( issueID int64 , remove bool ) ( int , error ) {
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.
2016-11-22 14:24:39 +03:00
func DeleteAttachmentsByComment ( commentID int64 , remove bool ) ( int , error ) {
attachments , err := GetAttachmentsByCommentID ( commentID )
2014-07-23 23:15:47 +04:00
if err != nil {
return 0 , err
}
return DeleteAttachments ( attachments , remove )
}