|
|
|
@ -25,7 +25,7 @@ import ( |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
ErrMissingIssueNumber = errors.New("No issue number specified") |
|
|
|
|
errMissingIssueNumber = errors.New("No issue number specified") |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// Issue represents an issue or pull request of repository.
|
|
|
|
@ -62,16 +62,19 @@ type Issue struct { |
|
|
|
|
Comments []*Comment `xorm:"-"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// BeforeInsert ...
|
|
|
|
|
func (issue *Issue) BeforeInsert() { |
|
|
|
|
issue.CreatedUnix = time.Now().Unix() |
|
|
|
|
issue.UpdatedUnix = issue.CreatedUnix |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// BeforeUpdate ...
|
|
|
|
|
func (issue *Issue) BeforeUpdate() { |
|
|
|
|
issue.UpdatedUnix = time.Now().Unix() |
|
|
|
|
issue.DeadlineUnix = issue.Deadline.Unix() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AfterSet ...
|
|
|
|
|
func (issue *Issue) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
|
switch colName { |
|
|
|
|
case "deadline_unix": |
|
|
|
@ -83,6 +86,7 @@ func (issue *Issue) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// loadAttributes ...
|
|
|
|
|
func (issue *Issue) loadAttributes(e Engine) (err error) { |
|
|
|
|
if issue.Repo == nil { |
|
|
|
|
issue.Repo, err = getRepositoryByID(e, issue.RepoID) |
|
|
|
@ -150,10 +154,12 @@ func (issue *Issue) loadAttributes(e Engine) (err error) { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// LoadAttributes ...
|
|
|
|
|
func (issue *Issue) LoadAttributes() error { |
|
|
|
|
return issue.loadAttributes(x) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// HTMLURL ...
|
|
|
|
|
func (issue *Issue) HTMLURL() string { |
|
|
|
|
var path string |
|
|
|
|
if issue.IsPull { |
|
|
|
@ -165,14 +171,14 @@ func (issue *Issue) HTMLURL() string { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// State returns string representation of issue status.
|
|
|
|
|
func (i *Issue) State() api.StateType { |
|
|
|
|
if i.IsClosed { |
|
|
|
|
func (issue *Issue) State() api.StateType { |
|
|
|
|
if issue.IsClosed { |
|
|
|
|
return api.STATE_CLOSED |
|
|
|
|
} |
|
|
|
|
return api.STATE_OPEN |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// This method assumes some fields assigned with values:
|
|
|
|
|
// APIFormat assumes some fields assigned with values:
|
|
|
|
|
// Required - Poster, Labels,
|
|
|
|
|
// Optional - Milestone, Assignee, PullRequest
|
|
|
|
|
func (issue *Issue) APIFormat() *api.Issue { |
|
|
|
@ -213,24 +219,25 @@ func (issue *Issue) APIFormat() *api.Issue { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// HashTag returns unique hash tag for issue.
|
|
|
|
|
func (i *Issue) HashTag() string { |
|
|
|
|
return "issue-" + com.ToStr(i.ID) |
|
|
|
|
func (issue *Issue) HashTag() string { |
|
|
|
|
return "issue-" + com.ToStr(issue.ID) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// IsPoster returns true if given user by ID is the poster.
|
|
|
|
|
func (i *Issue) IsPoster(uid int64) bool { |
|
|
|
|
return i.PosterID == uid |
|
|
|
|
func (issue *Issue) IsPoster(uid int64) bool { |
|
|
|
|
return issue.PosterID == uid |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (i *Issue) hasLabel(e Engine, labelID int64) bool { |
|
|
|
|
return hasIssueLabel(e, i.ID, labelID) |
|
|
|
|
func (issue *Issue) hasLabel(e Engine, labelID int64) bool { |
|
|
|
|
return hasIssueLabel(e, issue.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) |
|
|
|
|
func (issue *Issue) HasLabel(labelID int64) bool { |
|
|
|
|
return issue.hasLabel(x, labelID) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// sendLabelUpdatedWebhook ...
|
|
|
|
|
func (issue *Issue) sendLabelUpdatedWebhook(doer *User) { |
|
|
|
|
var err error |
|
|
|
|
if issue.IsPull { |
|
|
|
@ -254,8 +261,9 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (i *Issue) addLabel(e *xorm.Session, label *Label) error { |
|
|
|
|
return newIssueLabel(e, i, label) |
|
|
|
|
// addLabel ...
|
|
|
|
|
func (issue *Issue) addLabel(e *xorm.Session, label *Label) error { |
|
|
|
|
return newIssueLabel(e, issue, label) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AddLabel adds a new label to the issue.
|
|
|
|
@ -268,6 +276,7 @@ func (issue *Issue) AddLabel(doer *User, label *Label) error { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// addLabels ...
|
|
|
|
|
func (issue *Issue) addLabels(e *xorm.Session, labels []*Label) error { |
|
|
|
|
return newIssueLabels(e, issue, labels) |
|
|
|
|
} |
|
|
|
@ -282,6 +291,7 @@ func (issue *Issue) AddLabels(doer *User, labels []*Label) error { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// getLabels ...
|
|
|
|
|
func (issue *Issue) getLabels(e Engine) (err error) { |
|
|
|
|
if len(issue.Labels) > 0 { |
|
|
|
|
return nil |
|
|
|
@ -294,6 +304,7 @@ func (issue *Issue) getLabels(e Engine) (err error) { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// removeLabel ...
|
|
|
|
|
func (issue *Issue) removeLabel(e *xorm.Session, label *Label) error { |
|
|
|
|
return deleteIssueLabel(e, issue, label) |
|
|
|
|
} |
|
|
|
@ -308,6 +319,7 @@ func (issue *Issue) RemoveLabel(doer *User, label *Label) error { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// clearLabels ...
|
|
|
|
|
func (issue *Issue) clearLabels(e *xorm.Session) (err error) { |
|
|
|
|
if err = issue.getLabels(e); err != nil { |
|
|
|
|
return fmt.Errorf("getLabels: %v", err) |
|
|
|
@ -322,6 +334,7 @@ func (issue *Issue) clearLabels(e *xorm.Session) (err error) { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ClearLabels ...
|
|
|
|
|
func (issue *Issue) ClearLabels(doer *User) (err error) { |
|
|
|
|
sess := x.NewSession() |
|
|
|
|
defer sessionRelease(sess) |
|
|
|
@ -377,12 +390,13 @@ func (issue *Issue) ReplaceLabels(labels []*Label) (err error) { |
|
|
|
|
return sess.Commit() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (i *Issue) GetAssignee() (err error) { |
|
|
|
|
if i.AssigneeID == 0 || i.Assignee != nil { |
|
|
|
|
// GetAssignee ...
|
|
|
|
|
func (issue *Issue) GetAssignee() (err error) { |
|
|
|
|
if issue.AssigneeID == 0 || issue.Assignee != nil { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
i.Assignee, err = GetUserByID(i.AssigneeID) |
|
|
|
|
issue.Assignee, err = GetUserByID(issue.AssigneeID) |
|
|
|
|
if IsErrUserNotExist(err) { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
@ -390,8 +404,8 @@ func (i *Issue) GetAssignee() (err error) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ReadBy sets issue to be read by given user.
|
|
|
|
|
func (i *Issue) ReadBy(uid int64) error { |
|
|
|
|
return UpdateIssueUserByRead(uid, i.ID) |
|
|
|
|
func (issue *Issue) ReadBy(uid int64) error { |
|
|
|
|
return UpdateIssueUserByRead(uid, issue.ID) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func updateIssueCols(e Engine, issue *Issue, cols ...string) error { |
|
|
|
@ -404,41 +418,42 @@ func UpdateIssueCols(issue *Issue, cols ...string) error { |
|
|
|
|
return updateIssueCols(x, issue, cols...) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (i *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, isClosed bool) (err error) { |
|
|
|
|
// changeStatus ...
|
|
|
|
|
func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, isClosed bool) (err error) { |
|
|
|
|
// Nothing should be performed if current status is same as target status
|
|
|
|
|
if i.IsClosed == isClosed { |
|
|
|
|
if issue.IsClosed == isClosed { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
i.IsClosed = isClosed |
|
|
|
|
issue.IsClosed = isClosed |
|
|
|
|
|
|
|
|
|
if err = updateIssueCols(e, i, "is_closed"); err != nil { |
|
|
|
|
if err = updateIssueCols(e, issue, "is_closed"); err != nil { |
|
|
|
|
return err |
|
|
|
|
} else if err = updateIssueUsersByStatus(e, i.ID, isClosed); err != nil { |
|
|
|
|
} else if err = updateIssueUsersByStatus(e, issue.ID, isClosed); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Update issue count of labels
|
|
|
|
|
if err = i.getLabels(e); err != nil { |
|
|
|
|
if err = issue.getLabels(e); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
for idx := range i.Labels { |
|
|
|
|
if i.IsClosed { |
|
|
|
|
i.Labels[idx].NumClosedIssues++ |
|
|
|
|
for idx := range issue.Labels { |
|
|
|
|
if issue.IsClosed { |
|
|
|
|
issue.Labels[idx].NumClosedIssues++ |
|
|
|
|
} else { |
|
|
|
|
i.Labels[idx].NumClosedIssues-- |
|
|
|
|
issue.Labels[idx].NumClosedIssues-- |
|
|
|
|
} |
|
|
|
|
if err = updateLabel(e, i.Labels[idx]); err != nil { |
|
|
|
|
if err = updateLabel(e, issue.Labels[idx]); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Update issue count of milestone
|
|
|
|
|
if err = changeMilestoneIssueStats(e, i); err != nil { |
|
|
|
|
if err = changeMilestoneIssueStats(e, issue); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// New action comment
|
|
|
|
|
if _, err = createStatusComment(e, doer, repo, i); err != nil { |
|
|
|
|
if _, err = createStatusComment(e, doer, repo, issue); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -486,6 +501,7 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ChangeTitle ...
|
|
|
|
|
func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { |
|
|
|
|
oldTitle := issue.Title |
|
|
|
|
issue.Title = title |
|
|
|
@ -517,6 +533,7 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ChangeContent ...
|
|
|
|
|
func (issue *Issue) ChangeContent(doer *User, content string) (err error) { |
|
|
|
|
oldContent := issue.Content |
|
|
|
|
issue.Content = content |
|
|
|
@ -548,6 +565,7 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ChangeAssignee ...
|
|
|
|
|
func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) { |
|
|
|
|
issue.AssigneeID = assigneeID |
|
|
|
|
if err = UpdateIssueUserByAssignee(issue); err != nil { |
|
|
|
@ -586,6 +604,7 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NewIssueOptions ...
|
|
|
|
|
type NewIssueOptions struct { |
|
|
|
|
Repo *Repository |
|
|
|
|
Issue *Issue |
|
|
|
@ -735,7 +754,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) |
|
|
|
|
func GetIssueByRef(ref string) (*Issue, error) { |
|
|
|
|
n := strings.IndexByte(ref, byte('#')) |
|
|
|
|
if n == -1 { |
|
|
|
|
return nil, ErrMissingIssueNumber |
|
|
|
|
return nil, errMissingIssueNumber |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
index, err := com.StrTo(ref[n+1:]).Int64() |
|
|
|
@ -756,7 +775,7 @@ func GetIssueByRef(ref string) (*Issue, error) { |
|
|
|
|
return issue, issue.LoadAttributes() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetIssueByIndex returns raw issue without loading attributes by index in a repository.
|
|
|
|
|
// GetRawIssueByIndex returns raw issue without loading attributes by index in a repository.
|
|
|
|
|
func GetRawIssueByIndex(repoID, index int64) (*Issue, error) { |
|
|
|
|
issue := &Issue{ |
|
|
|
|
RepoID: repoID, |
|
|
|
@ -796,6 +815,7 @@ func GetIssueByID(id int64) (*Issue, error) { |
|
|
|
|
return getIssueByID(x, id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// IssuesOptions ...
|
|
|
|
|
type IssuesOptions struct { |
|
|
|
|
UserID int64 |
|
|
|
|
AssigneeID int64 |
|
|
|
@ -967,9 +987,9 @@ func NewIssueUsers(repo *Repository, issue *Issue) (err error) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// PairsContains returns true when pairs list contains given issue.
|
|
|
|
|
func PairsContains(ius []*IssueUser, issueId, uid int64) int { |
|
|
|
|
func PairsContains(ius []*IssueUser, issueID, uid int64) int { |
|
|
|
|
for i := range ius { |
|
|
|
|
if ius[i].IssueID == issueId && |
|
|
|
|
if ius[i].IssueID == issueID && |
|
|
|
|
ius[i].UID == uid { |
|
|
|
|
return i |
|
|
|
|
} |
|
|
|
@ -1092,6 +1112,7 @@ func parseCountResult(results []map[string][]byte) int64 { |
|
|
|
|
return 0 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// IssueStatsOptions ...
|
|
|
|
|
type IssueStatsOptions struct { |
|
|
|
|
RepoID int64 |
|
|
|
|
UserID int64 |
|
|
|
@ -1350,10 +1371,12 @@ type Milestone struct { |
|
|
|
|
ClosedDateUnix int64 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// BeforeInsert ...
|
|
|
|
|
func (m *Milestone) BeforeInsert() { |
|
|
|
|
m.DeadlineUnix = m.Deadline.Unix() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// BeforeUpdate ...
|
|
|
|
|
func (m *Milestone) BeforeUpdate() { |
|
|
|
|
if m.NumIssues > 0 { |
|
|
|
|
m.Completeness = m.NumClosedIssues * 100 / m.NumIssues |
|
|
|
@ -1365,6 +1388,7 @@ func (m *Milestone) BeforeUpdate() { |
|
|
|
|
m.ClosedDateUnix = m.ClosedDate.Unix() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AfterSet ...
|
|
|
|
|
func (m *Milestone) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
|
switch colName { |
|
|
|
|
case "num_closed_issues": |
|
|
|
@ -1394,6 +1418,7 @@ func (m *Milestone) State() api.StateType { |
|
|
|
|
return api.STATE_OPEN |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// APIFormat ...
|
|
|
|
|
func (m *Milestone) APIFormat() *api.Milestone { |
|
|
|
|
apiMilestone := &api.Milestone{ |
|
|
|
|
ID: m.ID, |
|
|
|
@ -1444,7 +1469,7 @@ func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) { |
|
|
|
|
return m, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetWebhookByRepoID returns the milestone in a repository.
|
|
|
|
|
// GetMilestoneByRepoID returns the milestone in a repository.
|
|
|
|
|
func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) { |
|
|
|
|
return getMilestoneByRepoID(x, repoID, id) |
|
|
|
|
} |
|
|
|
@ -1676,10 +1701,12 @@ type Attachment struct { |
|
|
|
|
CreatedUnix int64 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// BeforeInsert ...
|
|
|
|
|
func (a *Attachment) BeforeInsert() { |
|
|
|
|
a.CreatedUnix = time.Now().Unix() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AfterSet ...
|
|
|
|
|
func (a *Attachment) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
|
switch colName { |
|
|
|
|
case "created_unix": |
|
|
|
@ -1693,8 +1720,8 @@ func AttachmentLocalPath(uuid string) string { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// LocalPath returns where attachment is stored in local file system.
|
|
|
|
|
func (attach *Attachment) LocalPath() string { |
|
|
|
|
return AttachmentLocalPath(attach.UUID) |
|
|
|
|
func (a *Attachment) LocalPath() string { |
|
|
|
|
return AttachmentLocalPath(a.UUID) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NewAttachment creates a new attachment object.
|
|
|
|
@ -1794,8 +1821,8 @@ func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
|
|
|
|
|
func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) { |
|
|
|
|
attachments, err := GetAttachmentsByIssueID(issueId) |
|
|
|
|
func DeleteAttachmentsByIssue(issueID int64, remove bool) (int, error) { |
|
|
|
|
attachments, err := GetAttachmentsByIssueID(issueID) |
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
return 0, err |
|
|
|
@ -1805,8 +1832,8 @@ func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
|
|
|
|
|
func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) { |
|
|
|
|
attachments, err := GetAttachmentsByCommentID(commentId) |
|
|
|
|
func DeleteAttachmentsByComment(commentID int64, remove bool) (int, error) { |
|
|
|
|
attachments, err := GetAttachmentsByCommentID(commentID) |
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
return 0, err |
|
|
|
|