|
@@ -25,7 +25,6 @@ import (
|
|
)
|
|
)
|
|
|
|
|
|
var (
|
|
var (
|
|
- ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone")
|
|
|
|
ErrMissingIssueNumber = errors.New("No issue number specified")
|
|
ErrMissingIssueNumber = errors.New("No issue number specified")
|
|
)
|
|
)
|
|
|
|
|
|
@@ -580,80 +579,86 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
|
|
return nil
|
|
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 {
|
|
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 {
|
|
} 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
|
|
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 {
|
|
} 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 {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- if len(labelIDs) > 0 {
|
|
|
|
|
|
+ if len(opts.LableIDs) > 0 {
|
|
// During the session, SQLite3 dirver cannot handle retrieve objects after update something.
|
|
// During the session, SQLite3 dirver cannot handle retrieve objects after update something.
|
|
// So we have to get all needed labels first.
|
|
// 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 {
|
|
for _, label := range labels {
|
|
- if label.RepoID != repo.ID {
|
|
|
|
|
|
+ // Silently drop invalid labels.
|
|
|
|
+ if label.RepoID != opts.Repo.ID {
|
|
continue
|
|
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
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if err = newIssueUsers(e, repo, issue); err != nil {
|
|
|
|
|
|
+ if err = newIssueUsers(e, opts.Repo, opts.Issue); err != nil {
|
|
return err
|
|
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 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.
|
|
// NewIssue creates new issue with labels for repository.
|
|
@@ -664,7 +669,12 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
|
|
return err
|
|
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)
|
|
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)
|
|
return fmt.Errorf("Commit: %v", err)
|
|
}
|
|
}
|
|
|
|
|
|
- // Notify watchers.
|
|
|
|
- act := &Action{
|
|
|
|
|
|
+ if err = NotifyWatchers(&Action{
|
|
ActUserID: issue.Poster.ID,
|
|
ActUserID: issue.Poster.ID,
|
|
ActUserName: issue.Poster.Name,
|
|
ActUserName: issue.Poster.Name,
|
|
ActEmail: issue.Poster.Email,
|
|
ActEmail: issue.Poster.Email,
|
|
@@ -683,10 +692,10 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
|
|
RepoUserName: repo.Owner.Name,
|
|
RepoUserName: repo.Owner.Name,
|
|
RepoName: repo.Name,
|
|
RepoName: repo.Name,
|
|
IsPrivate: repo.IsPrivate,
|
|
IsPrivate: repo.IsPrivate,
|
|
- }
|
|
|
|
- if err = NotifyWatchers(act); err != nil {
|
|
|
|
|
|
+ }); err != nil {
|
|
log.Error(4, "NotifyWatchers: %v", err)
|
|
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)
|
|
log.Error(4, "MailParticipants: %v", err)
|
|
}
|
|
}
|
|
|
|
|
|
@@ -855,37 +864,41 @@ type IssueUser struct {
|
|
}
|
|
}
|
|
|
|
|
|
func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error {
|
|
func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error {
|
|
- users, err := repo.GetAssignees()
|
|
|
|
|
|
+ assignees, err := repo.getAssignees(e)
|
|
if err != nil {
|
|
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
|
|
return nil
|
|
}
|
|
}
|
|
@@ -1499,9 +1512,9 @@ func ChangeMilestoneIssueStats(issue *Issue) (err error) {
|
|
return sess.Commit()
|
|
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 {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -1513,7 +1526,7 @@ func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error {
|
|
|
|
|
|
if err = updateMilestone(e, m); err != nil {
|
|
if err = updateMilestone(e, m); err != nil {
|
|
return err
|
|
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
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1529,13 +1542,9 @@ func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error {
|
|
m.NumClosedIssues++
|
|
m.NumClosedIssues++
|
|
}
|
|
}
|
|
|
|
|
|
- if m.NumIssues == 0 {
|
|
|
|
- return ErrWrongIssueCounter
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
if err = updateMilestone(e, m); err != nil {
|
|
if err = updateMilestone(e, m); err != nil {
|
|
return err
|
|
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
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1544,14 +1553,14 @@ func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error {
|
|
}
|
|
}
|
|
|
|
|
|
// ChangeMilestoneAssign changes assignment of milestone for issue.
|
|
// 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()
|
|
sess := x.NewSession()
|
|
defer sess.Close()
|
|
defer sess.Close()
|
|
if err = sess.Begin(); err != nil {
|
|
if err = sess.Begin(); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- if err = changeMilestoneAssign(sess, oldMid, issue); err != nil {
|
|
|
|
|
|
+ if err = changeMilestoneAssign(sess, issue, oldMilestoneID); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
return sess.Commit()
|
|
return sess.Commit()
|
|
@@ -1677,6 +1686,16 @@ func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) {
|
|
return attach, nil
|
|
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.
|
|
// GetAttachmentByUUID returns attachment by given UUID.
|
|
func GetAttachmentByUUID(uuid string) (*Attachment, error) {
|
|
func GetAttachmentByUUID(uuid string) (*Attachment, error) {
|
|
return getAttachmentByUUID(x, uuid)
|
|
return getAttachmentByUUID(x, uuid)
|