|
|
@ -16,6 +16,7 @@ import ( |
|
|
|
|
|
|
|
|
|
|
|
"github.com/Unknwon/com" |
|
|
|
"github.com/Unknwon/com" |
|
|
|
"github.com/go-xorm/xorm" |
|
|
|
"github.com/go-xorm/xorm" |
|
|
|
|
|
|
|
api "github.com/gogits/go-gogs-client" |
|
|
|
gouuid "github.com/satori/go.uuid" |
|
|
|
gouuid "github.com/satori/go.uuid" |
|
|
|
|
|
|
|
|
|
|
|
"github.com/gogits/gogs/modules/base" |
|
|
|
"github.com/gogits/gogs/modules/base" |
|
|
@ -31,10 +32,10 @@ var ( |
|
|
|
|
|
|
|
|
|
|
|
// Issue represents an issue or pull request of repository.
|
|
|
|
// Issue represents an issue or pull request of repository.
|
|
|
|
type Issue struct { |
|
|
|
type Issue struct { |
|
|
|
ID int64 `xorm:"pk autoincr"` |
|
|
|
ID int64 `xorm:"pk autoincr"` |
|
|
|
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"` |
|
|
|
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"` |
|
|
|
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
|
|
|
|
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
|
|
|
|
Name string |
|
|
|
Title string `xorm:"name"` |
|
|
|
Repo *Repository `xorm:"-"` |
|
|
|
Repo *Repository `xorm:"-"` |
|
|
|
PosterID int64 |
|
|
|
PosterID int64 |
|
|
|
Poster *User `xorm:"-"` |
|
|
|
Poster *User `xorm:"-"` |
|
|
@ -73,19 +74,6 @@ func (i *Issue) BeforeUpdate() { |
|
|
|
i.DeadlineUnix = i.Deadline.Unix() |
|
|
|
i.DeadlineUnix = i.Deadline.Unix() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (issue *Issue) loadAttributes(e Engine) (err error) { |
|
|
|
|
|
|
|
issue.Repo, err = getRepositoryByID(e, issue.RepoID) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return fmt.Errorf("getRepositoryByID: %v", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (issue *Issue) LoadAttributes() error { |
|
|
|
|
|
|
|
return issue.loadAttributes(x) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (i *Issue) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
func (i *Issue) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
var err error |
|
|
|
var err error |
|
|
|
switch colName { |
|
|
|
switch colName { |
|
|
@ -110,7 +98,7 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
if IsErrUserNotExist(err) { |
|
|
|
if IsErrUserNotExist(err) { |
|
|
|
i.PosterID = -1 |
|
|
|
i.PosterID = -1 |
|
|
|
i.Poster = NewFakeUser() |
|
|
|
i.Poster = NewGhostUser() |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
log.Error(3, "GetUserByID[%d]: %v", i.ID, err) |
|
|
|
log.Error(3, "GetUserByID[%d]: %v", i.ID, err) |
|
|
|
} |
|
|
|
} |
|
|
@ -146,17 +134,80 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// HashTag returns unique hash tag for issue.
|
|
|
|
func (issue *Issue) loadAttributes(e Engine) (err error) { |
|
|
|
func (i *Issue) HashTag() string { |
|
|
|
if issue.Repo == nil { |
|
|
|
return "issue-" + com.ToStr(i.ID) |
|
|
|
issue.Repo, err = getRepositoryByID(e, issue.RepoID) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return fmt.Errorf("getRepositoryByID [%d]: %v", issue.RepoID, err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (issue *Issue) LoadAttributes() error { |
|
|
|
|
|
|
|
return issue.loadAttributes(x) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// State returns string representation of issue status.
|
|
|
|
// State returns string representation of issue status.
|
|
|
|
func (i *Issue) State() string { |
|
|
|
func (i *Issue) State() api.StateType { |
|
|
|
if i.IsClosed { |
|
|
|
if i.IsClosed { |
|
|
|
return "closed" |
|
|
|
return api.STATE_CLOSED |
|
|
|
} |
|
|
|
} |
|
|
|
return "open" |
|
|
|
return api.STATE_OPEN |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This method assumes some fields assigned with values:
|
|
|
|
|
|
|
|
// 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, |
|
|
|
|
|
|
|
State: issue.State(), |
|
|
|
|
|
|
|
Title: issue.Title, |
|
|
|
|
|
|
|
Body: issue.Content, |
|
|
|
|
|
|
|
User: issue.Poster.APIFormat(), |
|
|
|
|
|
|
|
Labels: apiLabels, |
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
func (i *Issue) HashTag() string { |
|
|
|
|
|
|
|
return "issue-" + com.ToStr(i.ID) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (issue *Issue) FullLink() string { |
|
|
|
func (issue *Issue) FullLink() string { |
|
|
@ -183,23 +234,37 @@ func (i *Issue) HasLabel(labelID int64) bool { |
|
|
|
return i.hasLabel(x, labelID) |
|
|
|
return i.hasLabel(x, labelID) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (issue *Issue) sendLabelUpdatedWebhook(doer *User) { |
|
|
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
if issue.IsPull { |
|
|
|
|
|
|
|
issue.PullRequest.Issue = issue |
|
|
|
|
|
|
|
err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ |
|
|
|
|
|
|
|
Action: api.HOOK_ISSUE_LABEL_UPDATED, |
|
|
|
|
|
|
|
Index: issue.Index, |
|
|
|
|
|
|
|
PullRequest: issue.PullRequest.APIFormat(), |
|
|
|
|
|
|
|
Repository: issue.Repo.APIFormat(nil), |
|
|
|
|
|
|
|
Sender: doer.APIFormat(), |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
go HookQueue.Add(issue.RepoID) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (i *Issue) addLabel(e *xorm.Session, label *Label) error { |
|
|
|
func (i *Issue) addLabel(e *xorm.Session, label *Label) error { |
|
|
|
return newIssueLabel(e, i, label) |
|
|
|
return newIssueLabel(e, i, label) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// AddLabel adds a new label to the issue.
|
|
|
|
// AddLabel adds a new label to the issue.
|
|
|
|
func (i *Issue) AddLabel(label *Label) (err error) { |
|
|
|
func (issue *Issue) AddLabel(doer *User, label *Label) error { |
|
|
|
sess := x.NewSession() |
|
|
|
if err := NewIssueLabel(issue, label); err != nil { |
|
|
|
defer sessionRelease(sess) |
|
|
|
|
|
|
|
if err = sess.Begin(); err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if err = i.addLabel(sess, label); err != nil { |
|
|
|
issue.sendLabelUpdatedWebhook(doer) |
|
|
|
return err |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return sess.Commit() |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (issue *Issue) addLabels(e *xorm.Session, labels []*Label) error { |
|
|
|
func (issue *Issue) addLabels(e *xorm.Session, labels []*Label) error { |
|
|
@ -207,8 +272,13 @@ func (issue *Issue) addLabels(e *xorm.Session, labels []*Label) error { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// AddLabels adds a list of new labels to the issue.
|
|
|
|
// AddLabels adds a list of new labels to the issue.
|
|
|
|
func (issue *Issue) AddLabels(labels []*Label) error { |
|
|
|
func (issue *Issue) AddLabels(doer *User, labels []*Label) error { |
|
|
|
return NewIssueLabels(issue, labels) |
|
|
|
if err := NewIssueLabels(issue, labels); err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
issue.sendLabelUpdatedWebhook(doer) |
|
|
|
|
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (issue *Issue) getLabels(e Engine) (err error) { |
|
|
|
func (issue *Issue) getLabels(e Engine) (err error) { |
|
|
@ -228,8 +298,13 @@ func (issue *Issue) removeLabel(e *xorm.Session, label *Label) error { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// RemoveLabel removes a label from issue by given ID.
|
|
|
|
// RemoveLabel removes a label from issue by given ID.
|
|
|
|
func (issue *Issue) RemoveLabel(label *Label) (err error) { |
|
|
|
func (issue *Issue) RemoveLabel(doer *User, label *Label) error { |
|
|
|
return DeleteIssueLabel(issue, label) |
|
|
|
if err := DeleteIssueLabel(issue, label); err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
issue.sendLabelUpdatedWebhook(doer) |
|
|
|
|
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (issue *Issue) clearLabels(e *xorm.Session) (err error) { |
|
|
|
func (issue *Issue) clearLabels(e *xorm.Session) (err error) { |
|
|
@ -246,7 +321,7 @@ func (issue *Issue) clearLabels(e *xorm.Session) (err error) { |
|
|
|
return nil |
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (issue *Issue) ClearLabels() (err error) { |
|
|
|
func (issue *Issue) ClearLabels(doer *User) (err error) { |
|
|
|
sess := x.NewSession() |
|
|
|
sess := x.NewSession() |
|
|
|
defer sessionRelease(sess) |
|
|
|
defer sessionRelease(sess) |
|
|
|
if err = sess.Begin(); err != nil { |
|
|
|
if err = sess.Begin(); err != nil { |
|
|
@ -257,7 +332,27 @@ func (issue *Issue) ClearLabels() (err error) { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return sess.Commit() |
|
|
|
if err = sess.Commit(); err != nil { |
|
|
|
|
|
|
|
return fmt.Errorf("Commit: %v", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if issue.IsPull { |
|
|
|
|
|
|
|
issue.PullRequest.Issue = issue |
|
|
|
|
|
|
|
err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ |
|
|
|
|
|
|
|
Action: api.HOOK_ISSUE_LABEL_CLEARED, |
|
|
|
|
|
|
|
Index: issue.Index, |
|
|
|
|
|
|
|
PullRequest: issue.PullRequest.APIFormat(), |
|
|
|
|
|
|
|
Repository: issue.Repo.APIFormat(nil), |
|
|
|
|
|
|
|
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 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ReplaceLabels removes all current labels and add new labels to the issue.
|
|
|
|
// ReplaceLabels removes all current labels and add new labels to the issue.
|
|
|
@ -294,6 +389,16 @@ func (i *Issue) ReadBy(uid int64) error { |
|
|
|
return UpdateIssueUserByRead(uid, i.ID) |
|
|
|
return UpdateIssueUserByRead(uid, i.ID) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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...) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (i *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, isClosed bool) (err error) { |
|
|
|
func (i *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
|
|
|
|
// Nothing should be performed if current status is same as target status
|
|
|
|
if i.IsClosed == isClosed { |
|
|
|
if i.IsClosed == isClosed { |
|
|
@ -336,32 +441,149 @@ func (i *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, isCl |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ChangeStatus changes issue status to open or closed.
|
|
|
|
// ChangeStatus changes issue status to open or closed.
|
|
|
|
func (i *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (err error) { |
|
|
|
func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (err error) { |
|
|
|
sess := x.NewSession() |
|
|
|
sess := x.NewSession() |
|
|
|
defer sessionRelease(sess) |
|
|
|
defer sessionRelease(sess) |
|
|
|
if err = sess.Begin(); err != nil { |
|
|
|
if err = sess.Begin(); err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if err = i.changeStatus(sess, doer, repo, isClosed); err != nil { |
|
|
|
if err = issue.changeStatus(sess, doer, repo, isClosed); err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return sess.Commit() |
|
|
|
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(), |
|
|
|
|
|
|
|
Repository: repo.APIFormat(nil), |
|
|
|
|
|
|
|
Sender: doer.APIFormat(), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if isClosed { |
|
|
|
|
|
|
|
apiPullRequest.Action = api.HOOK_ISSUE_CLOSED |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
apiPullRequest.Action = api.HOOK_ISSUE_REOPENED |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
err = PrepareWebhooks(repo, HOOK_EVENT_PULL_REQUEST, apiPullRequest) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ |
|
|
|
|
|
|
|
Action: api.HOOK_ISSUE_EDITED, |
|
|
|
|
|
|
|
Index: issue.Index, |
|
|
|
|
|
|
|
Changes: &api.ChangesPayload{ |
|
|
|
|
|
|
|
Title: &api.ChangesFromPayload{ |
|
|
|
|
|
|
|
From: oldTitle, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
PullRequest: issue.PullRequest.APIFormat(), |
|
|
|
|
|
|
|
Repository: issue.Repo.APIFormat(nil), |
|
|
|
|
|
|
|
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 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (i *Issue) GetPullRequest() (err error) { |
|
|
|
func (issue *Issue) ChangeContent(doer *User, content string) (err error) { |
|
|
|
if i.PullRequest != nil { |
|
|
|
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 |
|
|
|
|
|
|
|
err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, &api.PullRequestPayload{ |
|
|
|
|
|
|
|
Action: api.HOOK_ISSUE_EDITED, |
|
|
|
|
|
|
|
Index: issue.Index, |
|
|
|
|
|
|
|
Changes: &api.ChangesPayload{ |
|
|
|
|
|
|
|
Body: &api.ChangesFromPayload{ |
|
|
|
|
|
|
|
From: oldContent, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
PullRequest: issue.PullRequest.APIFormat(), |
|
|
|
|
|
|
|
Repository: issue.Repo.APIFormat(nil), |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
return nil |
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
i.PullRequest, err = GetPullRequestByIssueID(i.ID) |
|
|
|
// Error not nil here means user does not exist, which is remove assignee.
|
|
|
|
return err |
|
|
|
isRemoveAssignee := err != nil |
|
|
|
|
|
|
|
if issue.IsPull { |
|
|
|
|
|
|
|
issue.PullRequest.Issue = issue |
|
|
|
|
|
|
|
apiPullRequest := &api.PullRequestPayload{ |
|
|
|
|
|
|
|
Index: issue.Index, |
|
|
|
|
|
|
|
PullRequest: issue.PullRequest.APIFormat(), |
|
|
|
|
|
|
|
Repository: issue.Repo.APIFormat(nil), |
|
|
|
|
|
|
|
Sender: doer.APIFormat(), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if isRemoveAssignee { |
|
|
|
|
|
|
|
apiPullRequest.Action = api.HOOK_ISSUE_UNASSIGNED |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
apiPullRequest.Action = api.HOOK_ISSUE_ASSIGNED |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
err = PrepareWebhooks(issue.Repo, HOOK_EVENT_PULL_REQUEST, apiPullRequest) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
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 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// It's caller's responsibility to create action.
|
|
|
|
// It's caller's responsibility to create action.
|
|
|
|
func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string, isPull bool) (err error) { |
|
|
|
func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string, isPull bool) (err error) { |
|
|
|
issue.Name = strings.TrimSpace(issue.Name) |
|
|
|
issue.Title = strings.TrimSpace(issue.Title) |
|
|
|
issue.Index = repo.NextIssueIndex() |
|
|
|
issue.Index = repo.NextIssueIndex() |
|
|
|
|
|
|
|
|
|
|
|
if issue.AssigneeID > 0 { |
|
|
|
if issue.AssigneeID > 0 { |
|
|
@ -462,7 +684,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) |
|
|
|
ActUserName: issue.Poster.Name, |
|
|
|
ActUserName: issue.Poster.Name, |
|
|
|
ActEmail: issue.Poster.Email, |
|
|
|
ActEmail: issue.Poster.Email, |
|
|
|
OpType: ACTION_CREATE_ISSUE, |
|
|
|
OpType: ACTION_CREATE_ISSUE, |
|
|
|
Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name), |
|
|
|
Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title), |
|
|
|
RepoID: repo.ID, |
|
|
|
RepoID: repo.ID, |
|
|
|
RepoUserName: repo.Owner.Name, |
|
|
|
RepoUserName: repo.Owner.Name, |
|
|
|
RepoName: repo.Name, |
|
|
|
RepoName: repo.Name, |
|
|
@ -518,10 +740,9 @@ func GetIssueByIndex(repoID, index int64) (*Issue, error) { |
|
|
|
return issue, issue.LoadAttributes() |
|
|
|
return issue, issue.LoadAttributes() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// GetIssueByID returns an issue by given ID.
|
|
|
|
func getIssueByID(e Engine, id int64) (*Issue, error) { |
|
|
|
func GetIssueByID(id int64) (*Issue, error) { |
|
|
|
|
|
|
|
issue := new(Issue) |
|
|
|
issue := new(Issue) |
|
|
|
has, err := x.Id(id).Get(issue) |
|
|
|
has, err := e.Id(id).Get(issue) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
return nil, err |
|
|
|
} else if !has { |
|
|
|
} else if !has { |
|
|
@ -530,6 +751,11 @@ func GetIssueByID(id int64) (*Issue, error) { |
|
|
|
return issue, issue.LoadAttributes() |
|
|
|
return issue, issue.LoadAttributes() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// GetIssueByID returns an issue by given ID.
|
|
|
|
|
|
|
|
func GetIssueByID(id int64) (*Issue, error) { |
|
|
|
|
|
|
|
return getIssueByID(x, id) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type IssuesOptions struct { |
|
|
|
type IssuesOptions struct { |
|
|
|
UserID int64 |
|
|
|
UserID int64 |
|
|
|
AssigneeID int64 |
|
|
|
AssigneeID int64 |
|
|
@ -970,12 +1196,6 @@ func UpdateIssue(issue *Issue) error { |
|
|
|
return updateIssue(x, issue) |
|
|
|
return updateIssue(x, issue) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// updateIssueCols only updates values of specific columns for given issue.
|
|
|
|
|
|
|
|
func updateIssueCols(e Engine, issue *Issue, cols ...string) error { |
|
|
|
|
|
|
|
_, err := e.Id(issue.ID).Cols(cols...).Update(issue) |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error { |
|
|
|
func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error { |
|
|
|
_, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID) |
|
|
|
_, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID) |
|
|
|
return err |
|
|
|
return err |
|
|
@ -987,13 +1207,13 @@ func UpdateIssueUsersByStatus(issueID int64, isClosed bool) error { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func updateIssueUserByAssignee(e *xorm.Session, issue *Issue) (err error) { |
|
|
|
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 { |
|
|
|
if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?", false, issue.ID); err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Assignee ID equals to 0 means clear assignee.
|
|
|
|
// Assignee ID equals to 0 means clear assignee.
|
|
|
|
if issue.AssigneeID > 0 { |
|
|
|
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 { |
|
|
|
if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?", true, issue.AssigneeID, issue.ID); err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -1112,11 +1332,29 @@ func (m *Milestone) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// State returns string representation of milestone status.
|
|
|
|
// State returns string representation of milestone status.
|
|
|
|
func (m *Milestone) State() string { |
|
|
|
func (m *Milestone) State() api.StateType { |
|
|
|
if m.IsClosed { |
|
|
|
if m.IsClosed { |
|
|
|
return "closed" |
|
|
|
return api.STATE_CLOSED |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return api.STATE_OPEN |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
} |
|
|
|
} |
|
|
|
return "open" |
|
|
|
return apiMilestone |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// NewMilestone creates new milestone of repository.
|
|
|
|
// NewMilestone creates new milestone of repository.
|
|
|
|