|
|
|
@ -25,7 +25,6 @@ import ( |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone") |
|
|
|
|
ErrMissingIssueNumber = errors.New("No issue number specified") |
|
|
|
|
) |
|
|
|
|
|
|
|
|
@ -580,80 +579,86 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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) { |
|
|
|
|
issue.Title = strings.TrimSpace(issue.Title) |
|
|
|
|
issue.Index = repo.NextIssueIndex() |
|
|
|
|
type NewIssueOptions struct { |
|
|
|
|
Repo *Repository |
|
|
|
|
Issue *Issue |
|
|
|
|
LableIDs []int64 |
|
|
|
|
Attachments []string // In UUID format.
|
|
|
|
|
IsPull bool |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if issue.AssigneeID > 0 { |
|
|
|
|
// Silently drop invalid assignee
|
|
|
|
|
valid, err := hasAccess(e, &User{ID: issue.AssigneeID}, repo, ACCESS_MODE_WRITE) |
|
|
|
|
func newIssue(e *xorm.Session, opts *NewIssueOptions) (err error) { |
|
|
|
|
opts.Issue.Title = strings.TrimSpace(opts.Issue.Title) |
|
|
|
|
opts.Issue.Index = opts.Repo.NextIssueIndex() |
|
|
|
|
|
|
|
|
|
if opts.Issue.AssigneeID > 0 { |
|
|
|
|
// Silently drop invalid assignee.
|
|
|
|
|
valid, err := hasAccess(e, &User{ID: opts.Issue.AssigneeID}, opts.Repo, ACCESS_MODE_WRITE) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("hasAccess: %v", err) |
|
|
|
|
return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", opts.Issue.AssigneeID, opts.Repo.ID, err) |
|
|
|
|
} else if !valid { |
|
|
|
|
issue.AssigneeID = 0 |
|
|
|
|
opts.Issue.AssigneeID = 0 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if _, err = e.Insert(issue); err != nil { |
|
|
|
|
if _, err = e.Insert(opts.Issue); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if isPull { |
|
|
|
|
_, err = e.Exec("UPDATE `repository` SET num_pulls=num_pulls+1 WHERE id=?", issue.RepoID) |
|
|
|
|
if opts.IsPull { |
|
|
|
|
_, err = e.Exec("UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?", opts.Issue.RepoID) |
|
|
|
|
} else { |
|
|
|
|
_, err = e.Exec("UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?", issue.RepoID) |
|
|
|
|
_, err = e.Exec("UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?", opts.Issue.RepoID) |
|
|
|
|
} |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(labelIDs) > 0 { |
|
|
|
|
if len(opts.LableIDs) > 0 { |
|
|
|
|
// During the session, SQLite3 dirver cannot handle retrieve objects after update something.
|
|
|
|
|
// So we have to get all needed labels first.
|
|
|
|
|
labels := make([]*Label, 0, len(labelIDs)) |
|
|
|
|
if err = e.In("id", labelIDs).Find(&labels); err != nil { |
|
|
|
|
return fmt.Errorf("find all labels: %v", err) |
|
|
|
|
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) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, label := range labels { |
|
|
|
|
if label.RepoID != repo.ID { |
|
|
|
|
// Silently drop invalid labels.
|
|
|
|
|
if label.RepoID != opts.Repo.ID { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err = issue.addLabel(e, label); err != nil { |
|
|
|
|
return fmt.Errorf("addLabel: %v", err) |
|
|
|
|
if err = opts.Issue.addLabel(e, label); err != nil { |
|
|
|
|
return fmt.Errorf("addLabel [id: %d]: %v", label.ID, err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if issue.MilestoneID > 0 { |
|
|
|
|
if err = changeMilestoneAssign(e, 0, issue); err != nil { |
|
|
|
|
if opts.Issue.MilestoneID > 0 { |
|
|
|
|
if err = changeMilestoneAssign(e, opts.Issue, -1); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err = newIssueUsers(e, repo, issue); err != nil { |
|
|
|
|
if err = newIssueUsers(e, opts.Repo, opts.Issue); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check attachments.
|
|
|
|
|
for _, uuid := range uuids { |
|
|
|
|
attachment, err := getAttachmentByUUID(e, uuid) |
|
|
|
|
if len(opts.Attachments) > 0 { |
|
|
|
|
attachments, err := getAttachmentsByUUIDs(e, opts.Attachments) |
|
|
|
|
if err != nil { |
|
|
|
|
if IsErrAttachmentNotExist(err) { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
return fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err) |
|
|
|
|
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", opts.Attachments, err) |
|
|
|
|
} |
|
|
|
|
attachment.IssueID = issue.ID |
|
|
|
|
// No assign value could be 0, so ignore AllCols().
|
|
|
|
|
if _, err = e.Id(attachment.ID).Update(attachment); err != nil { |
|
|
|
|
return fmt.Errorf("update attachment [%d]: %v", attachment.ID, err) |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return issue.loadAttributes(e) |
|
|
|
|
return opts.Issue.loadAttributes(e) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NewIssue creates new issue with labels for repository.
|
|
|
|
@ -664,7 +669,12 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err = newIssue(sess, repo, issue, labelIDs, uuids, false); err != nil { |
|
|
|
|
if err = newIssue(sess, &NewIssueOptions{ |
|
|
|
|
Repo: repo, |
|
|
|
|
Issue: issue, |
|
|
|
|
LableIDs: labelIDs, |
|
|
|
|
Attachments: uuids, |
|
|
|
|
}); err != nil { |
|
|
|
|
return fmt.Errorf("newIssue: %v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -672,8 +682,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) |
|
|
|
|
return fmt.Errorf("Commit: %v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Notify watchers.
|
|
|
|
|
act := &Action{ |
|
|
|
|
if err = NotifyWatchers(&Action{ |
|
|
|
|
ActUserID: issue.Poster.ID, |
|
|
|
|
ActUserName: issue.Poster.Name, |
|
|
|
|
ActEmail: issue.Poster.Email, |
|
|
|
@ -683,10 +692,10 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) |
|
|
|
|
RepoUserName: repo.Owner.Name, |
|
|
|
|
RepoName: repo.Name, |
|
|
|
|
IsPrivate: repo.IsPrivate, |
|
|
|
|
} |
|
|
|
|
if err = NotifyWatchers(act); err != nil { |
|
|
|
|
}); err != nil { |
|
|
|
|
log.Error(4, "NotifyWatchers: %v", err) |
|
|
|
|
} else if err = issue.MailParticipants(); err != nil { |
|
|
|
|
} |
|
|
|
|
if err = issue.MailParticipants(); err != nil { |
|
|
|
|
log.Error(4, "MailParticipants: %v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -855,37 +864,41 @@ type IssueUser struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error { |
|
|
|
|
users, err := repo.GetAssignees() |
|
|
|
|
assignees, err := repo.getAssignees(e) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
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 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
iu := &IssueUser{ |
|
|
|
|
IssueID: issue.ID, |
|
|
|
|
RepoID: repo.ID, |
|
|
|
|
if !isPosterAssignee { |
|
|
|
|
issueUsers = append(issueUsers, &IssueUser{ |
|
|
|
|
IssueID: issue.ID, |
|
|
|
|
RepoID: repo.ID, |
|
|
|
|
UID: issue.PosterID, |
|
|
|
|
IsPoster: true, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Poster can be anyone.
|
|
|
|
|
isNeedAddPoster := true |
|
|
|
|
for _, u := range users { |
|
|
|
|
iu.ID = 0 |
|
|
|
|
iu.UID = u.ID |
|
|
|
|
iu.IsPoster = iu.UID == issue.PosterID |
|
|
|
|
if isNeedAddPoster && iu.IsPoster { |
|
|
|
|
isNeedAddPoster = false |
|
|
|
|
} |
|
|
|
|
iu.IsAssigned = iu.UID == issue.AssigneeID |
|
|
|
|
if _, err = e.Insert(iu); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if isNeedAddPoster { |
|
|
|
|
iu.ID = 0 |
|
|
|
|
iu.UID = issue.PosterID |
|
|
|
|
iu.IsPoster = true |
|
|
|
|
if _, err = e.Insert(iu); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if _, err = e.Insert(issueUsers); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
@ -1499,9 +1512,9 @@ func ChangeMilestoneIssueStats(issue *Issue) (err error) { |
|
|
|
|
return sess.Commit() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error { |
|
|
|
|
if oldMid > 0 { |
|
|
|
|
m, err := getMilestoneByID(e, oldMid) |
|
|
|
|
func changeMilestoneAssign(e *xorm.Session, issue *Issue, oldMilestoneID int64) error { |
|
|
|
|
if oldMilestoneID > 0 { |
|
|
|
|
m, err := getMilestoneByID(e, oldMilestoneID) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
@ -1513,7 +1526,7 @@ func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error { |
|
|
|
|
|
|
|
|
|
if err = updateMilestone(e, m); err != nil { |
|
|
|
|
return err |
|
|
|
|
} else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id=0 WHERE issue_id=?", issue.ID); err != nil { |
|
|
|
|
} else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id = 0 WHERE issue_id = ?", issue.ID); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -1529,13 +1542,9 @@ func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error { |
|
|
|
|
m.NumClosedIssues++ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if m.NumIssues == 0 { |
|
|
|
|
return ErrWrongIssueCounter |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err = updateMilestone(e, m); err != nil { |
|
|
|
|
return err |
|
|
|
|
} else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id=? WHERE issue_id=?", m.ID, issue.ID); err != nil { |
|
|
|
|
} else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id = ? WHERE issue_id = ?", m.ID, issue.ID); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -1544,14 +1553,14 @@ func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ChangeMilestoneAssign changes assignment of milestone for issue.
|
|
|
|
|
func ChangeMilestoneAssign(oldMid int64, issue *Issue) (err error) { |
|
|
|
|
func ChangeMilestoneAssign(issue *Issue, oldMilestoneID int64) (err error) { |
|
|
|
|
sess := x.NewSession() |
|
|
|
|
defer sess.Close() |
|
|
|
|
if err = sess.Begin(); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err = changeMilestoneAssign(sess, oldMid, issue); err != nil { |
|
|
|
|
if err = changeMilestoneAssign(sess, issue, oldMilestoneID); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
return sess.Commit() |
|
|
|
@ -1677,6 +1686,16 @@ func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) { |
|
|
|
|
return attach, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetAttachmentByUUID returns attachment by given UUID.
|
|
|
|
|
func GetAttachmentByUUID(uuid string) (*Attachment, error) { |
|
|
|
|
return getAttachmentByUUID(x, uuid) |
|
|
|
|