| @@ -194,6 +194,10 @@ func (a *Action) GetRepoLink() string { | |||
| // GetCommentLink returns link to action comment. | |||
| func (a *Action) GetCommentLink() string { | |||
| return a.getCommentLink(x) | |||
| } | |||
| func (a *Action) getCommentLink(e Engine) string { | |||
| if a == nil { | |||
| return "#" | |||
| } | |||
| @@ -213,11 +217,15 @@ func (a *Action) GetCommentLink() string { | |||
| return "#" | |||
| } | |||
| issue, err := GetIssueByID(issueID) | |||
| issue, err := getIssueByID(e, issueID) | |||
| if err != nil { | |||
| return "#" | |||
| } | |||
| if err = issue.loadRepo(e); err != nil { | |||
| return "#" | |||
| } | |||
| return issue.HTMLURL() | |||
| } | |||
| @@ -330,13 +338,15 @@ type PushCommits struct { | |||
| Commits []*PushCommit | |||
| CompareURL string | |||
| avatars map[string]string | |||
| avatars map[string]string | |||
| emailUsers map[string]*User | |||
| } | |||
| // NewPushCommits creates a new PushCommits object. | |||
| func NewPushCommits() *PushCommits { | |||
| return &PushCommits{ | |||
| avatars: make(map[string]string), | |||
| avatars: make(map[string]string), | |||
| emailUsers: make(map[string]*User), | |||
| } | |||
| } | |||
| @@ -344,16 +354,34 @@ func NewPushCommits() *PushCommits { | |||
| // api.PayloadCommit format. | |||
| func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*api.PayloadCommit { | |||
| commits := make([]*api.PayloadCommit, len(pc.Commits)) | |||
| if pc.emailUsers == nil { | |||
| pc.emailUsers = make(map[string]*User) | |||
| } | |||
| var err error | |||
| for i, commit := range pc.Commits { | |||
| authorUsername := "" | |||
| author, err := GetUserByEmail(commit.AuthorEmail) | |||
| if err == nil { | |||
| author, ok := pc.emailUsers[commit.AuthorEmail] | |||
| if !ok { | |||
| author, err = GetUserByEmail(commit.AuthorEmail) | |||
| if err == nil { | |||
| authorUsername = author.Name | |||
| pc.emailUsers[commit.AuthorEmail] = author | |||
| } | |||
| } else { | |||
| authorUsername = author.Name | |||
| } | |||
| committerUsername := "" | |||
| committer, err := GetUserByEmail(commit.CommitterEmail) | |||
| if err == nil { | |||
| // TODO: check errors other than email not found. | |||
| committer, ok := pc.emailUsers[commit.CommitterEmail] | |||
| if !ok { | |||
| committer, err = GetUserByEmail(commit.CommitterEmail) | |||
| if err == nil { | |||
| // TODO: check errors other than email not found. | |||
| committerUsername = committer.Name | |||
| pc.emailUsers[commit.CommitterEmail] = committer | |||
| } | |||
| } else { | |||
| committerUsername = committer.Name | |||
| } | |||
| commits[i] = &api.PayloadCommit{ | |||
| @@ -379,18 +407,28 @@ func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*api.PayloadCommit | |||
| // AvatarLink tries to match user in database with e-mail | |||
| // in order to show custom avatar, and falls back to general avatar link. | |||
| func (pc *PushCommits) AvatarLink(email string) string { | |||
| _, ok := pc.avatars[email] | |||
| avatar, ok := pc.avatars[email] | |||
| if ok { | |||
| return avatar | |||
| } | |||
| u, ok := pc.emailUsers[email] | |||
| if !ok { | |||
| u, err := GetUserByEmail(email) | |||
| var err error | |||
| u, err = GetUserByEmail(email) | |||
| if err != nil { | |||
| pc.avatars[email] = base.AvatarLink(email) | |||
| if !IsErrUserNotExist(err) { | |||
| log.Error(4, "GetUserByEmail: %v", err) | |||
| return "" | |||
| } | |||
| } else { | |||
| pc.avatars[email] = u.RelAvatarLink() | |||
| pc.emailUsers[email] = u | |||
| } | |||
| } | |||
| if u != nil { | |||
| pc.avatars[email] = u.RelAvatarLink() | |||
| } | |||
| return pc.avatars[email] | |||
| } | |||
| @@ -479,7 +517,8 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err | |||
| continue | |||
| } | |||
| if err = issue.ChangeStatus(doer, repo, true); err != nil { | |||
| issue.Repo = repo | |||
| if err = issue.ChangeStatus(doer, true); err != nil { | |||
| // Don't return an error when dependencies are open as this would let the push fail | |||
| if IsErrDependenciesLeft(err) { | |||
| return nil | |||
| @@ -504,7 +543,8 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err | |||
| continue | |||
| } | |||
| if err = issue.ChangeStatus(doer, repo, false); err != nil { | |||
| issue.Repo = repo | |||
| if err = issue.ChangeStatus(doer, false); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| @@ -86,6 +86,11 @@ func (issue *Issue) IsOverdue() bool { | |||
| return util.TimeStampNow() >= issue.DeadlineUnix | |||
| } | |||
| // LoadRepo loads issue's repository | |||
| func (issue *Issue) LoadRepo() error { | |||
| return issue.loadRepo(x) | |||
| } | |||
| func (issue *Issue) loadRepo(e Engine) (err error) { | |||
| if issue.Repo == nil { | |||
| issue.Repo, err = getRepositoryByID(e, issue.RepoID) | |||
| @@ -129,6 +134,11 @@ func (issue *Issue) loadLabels(e Engine) (err error) { | |||
| return nil | |||
| } | |||
| // LoadPoster loads poster | |||
| func (issue *Issue) LoadPoster() error { | |||
| return issue.loadPoster(x) | |||
| } | |||
| func (issue *Issue) loadPoster(e Engine) (err error) { | |||
| if issue.Poster == nil { | |||
| issue.Poster, err = getUserByID(e, issue.PosterID) | |||
| @@ -154,10 +164,16 @@ func (issue *Issue) loadPullRequest(e Engine) (err error) { | |||
| } | |||
| return fmt.Errorf("getPullRequestByIssueID [%d]: %v", issue.ID, err) | |||
| } | |||
| issue.PullRequest.Issue = issue | |||
| } | |||
| return nil | |||
| } | |||
| // LoadPullRequest loads pull request info | |||
| func (issue *Issue) LoadPullRequest() error { | |||
| return issue.loadPullRequest(x) | |||
| } | |||
| func (issue *Issue) loadComments(e Engine) (err error) { | |||
| if issue.Comments != nil { | |||
| return nil | |||
| @@ -310,11 +326,18 @@ func (issue *Issue) State() api.StateType { | |||
| // Required - Poster, Labels, | |||
| // Optional - Milestone, Assignee, PullRequest | |||
| func (issue *Issue) APIFormat() *api.Issue { | |||
| return issue.apiFormat(x) | |||
| } | |||
| func (issue *Issue) apiFormat(e Engine) *api.Issue { | |||
| issue.loadLabels(e) | |||
| apiLabels := make([]*api.Label, len(issue.Labels)) | |||
| for i := range issue.Labels { | |||
| apiLabels[i] = issue.Labels[i].APIFormat() | |||
| } | |||
| issue.loadPoster(e) | |||
| issue.loadRepo(e) | |||
| apiIssue := &api.Issue{ | |||
| ID: issue.ID, | |||
| URL: issue.APIURL(), | |||
| @@ -336,6 +359,8 @@ func (issue *Issue) APIFormat() *api.Issue { | |||
| if issue.Milestone != nil { | |||
| apiIssue.Milestone = issue.Milestone.APIFormat() | |||
| } | |||
| issue.loadAssignees(e) | |||
| if len(issue.Assignees) > 0 { | |||
| for _, assignee := range issue.Assignees { | |||
| apiIssue.Assignees = append(apiIssue.Assignees, assignee.APIFormat()) | |||
| @@ -343,6 +368,7 @@ func (issue *Issue) APIFormat() *api.Issue { | |||
| apiIssue.Assignee = issue.Assignees[0].APIFormat() // For compatibility, we're keeping the first assignee as `apiIssue.Assignee` | |||
| } | |||
| if issue.IsPull { | |||
| issue.loadPullRequest(e) | |||
| apiIssue.PullRequest = &api.PullRequestMeta{ | |||
| HasMerged: issue.PullRequest.HasMerged, | |||
| } | |||
| @@ -656,7 +682,7 @@ func UpdateIssueCols(issue *Issue, cols ...string) error { | |||
| return updateIssueCols(x, issue, cols...) | |||
| } | |||
| func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, isClosed bool) (err error) { | |||
| func (issue *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (err error) { | |||
| // Nothing should be performed if current status is same as target status | |||
| if issue.IsClosed == isClosed { | |||
| return nil | |||
| @@ -707,7 +733,7 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, | |||
| } | |||
| // New action comment | |||
| if _, err = createStatusComment(e, doer, repo, issue); err != nil { | |||
| if _, err = createStatusComment(e, doer, issue); err != nil { | |||
| return err | |||
| } | |||
| @@ -715,14 +741,21 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, | |||
| } | |||
| // ChangeStatus changes issue status to open or closed. | |||
| func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (err error) { | |||
| func (issue *Issue) ChangeStatus(doer *User, isClosed bool) (err error) { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if err = issue.changeStatus(sess, doer, repo, isClosed); err != nil { | |||
| if err = issue.loadRepo(sess); err != nil { | |||
| return err | |||
| } | |||
| if err = issue.loadPoster(sess); err != nil { | |||
| return err | |||
| } | |||
| if err = issue.changeStatus(sess, doer, isClosed); err != nil { | |||
| return err | |||
| } | |||
| @@ -733,12 +766,14 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e | |||
| mode, _ := AccessLevel(issue.Poster, issue.Repo) | |||
| if issue.IsPull { | |||
| if err = issue.loadPullRequest(sess); err != nil { | |||
| return err | |||
| } | |||
| // 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(mode), | |||
| Repository: issue.Repo.APIFormat(mode), | |||
| Sender: doer.APIFormat(), | |||
| } | |||
| if isClosed { | |||
| @@ -746,12 +781,12 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e | |||
| } else { | |||
| apiPullRequest.Action = api.HookIssueReOpened | |||
| } | |||
| err = PrepareWebhooks(repo, HookEventPullRequest, apiPullRequest) | |||
| err = PrepareWebhooks(issue.Repo, HookEventPullRequest, apiPullRequest) | |||
| } else { | |||
| apiIssue := &api.IssuePayload{ | |||
| Index: issue.Index, | |||
| Issue: issue.APIFormat(), | |||
| Repository: repo.APIFormat(mode), | |||
| Repository: issue.Repo.APIFormat(mode), | |||
| Sender: doer.APIFormat(), | |||
| } | |||
| if isClosed { | |||
| @@ -759,12 +794,12 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e | |||
| } else { | |||
| apiIssue.Action = api.HookIssueReOpened | |||
| } | |||
| err = PrepareWebhooks(repo, HookEventIssues, apiIssue) | |||
| err = PrepareWebhooks(issue.Repo, HookEventIssues, apiIssue) | |||
| } | |||
| if err != nil { | |||
| log.Error(4, "PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err) | |||
| } else { | |||
| go HookQueue.Add(repo.ID) | |||
| go HookQueue.Add(issue.Repo.ID) | |||
| } | |||
| return nil | |||
| @@ -785,6 +820,10 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { | |||
| return fmt.Errorf("updateIssueCols: %v", err) | |||
| } | |||
| if err = issue.loadRepo(sess); err != nil { | |||
| return fmt.Errorf("loadRepo: %v", err) | |||
| } | |||
| if _, err = createChangeTitleComment(sess, doer, issue.Repo, issue, oldTitle, title); err != nil { | |||
| return fmt.Errorf("createChangeTitleComment: %v", err) | |||
| } | |||
| @@ -795,6 +834,9 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { | |||
| mode, _ := AccessLevel(issue.Poster, issue.Repo) | |||
| if issue.IsPull { | |||
| if err = issue.loadPullRequest(sess); err != nil { | |||
| return fmt.Errorf("loadPullRequest: %v", err) | |||
| } | |||
| issue.PullRequest.Issue = issue | |||
| err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ | |||
| Action: api.HookIssueEdited, | |||
| @@ -1099,8 +1141,8 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in | |||
| return nil | |||
| } | |||
| // GetRawIssueByIndex returns raw issue without loading attributes by index in a repository. | |||
| func GetRawIssueByIndex(repoID, index int64) (*Issue, error) { | |||
| // GetIssueByIndex returns raw issue without loading attributes by index in a repository. | |||
| func GetIssueByIndex(repoID, index int64) (*Issue, error) { | |||
| issue := &Issue{ | |||
| RepoID: repoID, | |||
| Index: index, | |||
| @@ -1114,9 +1156,9 @@ func GetRawIssueByIndex(repoID, index int64) (*Issue, error) { | |||
| return issue, nil | |||
| } | |||
| // GetIssueByIndex returns issue by index in a repository. | |||
| func GetIssueByIndex(repoID, index int64) (*Issue, error) { | |||
| issue, err := GetRawIssueByIndex(repoID, index) | |||
| // GetIssueWithAttrsByIndex returns issue by index in a repository. | |||
| func GetIssueWithAttrsByIndex(repoID, index int64) (*Issue, error) { | |||
| issue, err := GetIssueByIndex(repoID, index) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -1131,7 +1173,16 @@ func getIssueByID(e Engine, id int64) (*Issue, error) { | |||
| } else if !has { | |||
| return nil, ErrIssueNotExist{id, 0, 0} | |||
| } | |||
| return issue, issue.loadAttributes(e) | |||
| return issue, nil | |||
| } | |||
| // GetIssueWithAttrsByID returns an issue with attributes by given ID. | |||
| func GetIssueWithAttrsByID(id int64) (*Issue, error) { | |||
| issue, err := getIssueByID(x, id) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return issue, issue.loadAttributes(x) | |||
| } | |||
| // GetIssueByID returns an issue by given ID. | |||
| @@ -174,7 +174,7 @@ func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID in | |||
| apiPullRequest := &api.PullRequestPayload{ | |||
| Index: issue.Index, | |||
| PullRequest: issue.PullRequest.APIFormat(), | |||
| Repository: issue.Repo.APIFormat(mode), | |||
| Repository: issue.Repo.innerAPIFormat(sess, mode, false), | |||
| Sender: doer.APIFormat(), | |||
| } | |||
| if removed { | |||
| @@ -191,8 +191,8 @@ func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID in | |||
| apiIssue := &api.IssuePayload{ | |||
| Index: issue.Index, | |||
| Issue: issue.APIFormat(), | |||
| Repository: issue.Repo.APIFormat(mode), | |||
| Issue: issue.apiFormat(sess), | |||
| Repository: issue.Repo.innerAPIFormat(sess, mode, false), | |||
| Sender: doer.APIFormat(), | |||
| } | |||
| if removed { | |||
| @@ -14,7 +14,7 @@ func TestUpdateAssignee(t *testing.T) { | |||
| assert.NoError(t, PrepareTestDatabase()) | |||
| // Fake issue with assignees | |||
| issue, err := GetIssueByID(1) | |||
| issue, err := GetIssueWithAttrsByID(1) | |||
| assert.NoError(t, err) | |||
| // Assign multiple users | |||
| @@ -150,25 +150,6 @@ func (c *Comment) LoadIssue() (err error) { | |||
| return | |||
| } | |||
| // AfterLoad is invoked from XORM after setting the values of all fields of this object. | |||
| func (c *Comment) AfterLoad(session *xorm.Session) { | |||
| var err error | |||
| c.Attachments, err = getAttachmentsByCommentID(session, c.ID) | |||
| if err != nil { | |||
| log.Error(3, "getAttachmentsByCommentID[%d]: %v", c.ID, err) | |||
| } | |||
| c.Poster, err = getUserByID(session, c.PosterID) | |||
| if err != nil { | |||
| if IsErrUserNotExist(err) { | |||
| c.PosterID = -1 | |||
| c.Poster = NewGhostUser() | |||
| } else { | |||
| log.Error(3, "getUserByID[%d]: %v", c.ID, err) | |||
| } | |||
| } | |||
| } | |||
| // AfterDelete is invoked from XORM after the object is deleted. | |||
| func (c *Comment) AfterDelete() { | |||
| if c.ID <= 0 { | |||
| @@ -189,6 +170,11 @@ func (c *Comment) HTMLURL() string { | |||
| log.Error(4, "LoadIssue(%d): %v", c.IssueID, err) | |||
| return "" | |||
| } | |||
| err = c.Issue.loadRepo(x) | |||
| if err != nil { // Silently dropping errors :unamused: | |||
| log.Error(4, "loadRepo(%d): %v", c.Issue.RepoID, err) | |||
| return "" | |||
| } | |||
| if c.Type == CommentTypeCode { | |||
| if c.ReviewID == 0 { | |||
| return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag()) | |||
| @@ -217,6 +203,12 @@ func (c *Comment) IssueURL() string { | |||
| if c.Issue.IsPull { | |||
| return "" | |||
| } | |||
| err = c.Issue.loadRepo(x) | |||
| if err != nil { // Silently dropping errors :unamused: | |||
| log.Error(4, "loadRepo(%d): %v", c.Issue.RepoID, err) | |||
| return "" | |||
| } | |||
| return c.Issue.HTMLURL() | |||
| } | |||
| @@ -228,6 +220,12 @@ func (c *Comment) PRURL() string { | |||
| return "" | |||
| } | |||
| err = c.Issue.loadRepo(x) | |||
| if err != nil { // Silently dropping errors :unamused: | |||
| log.Error(4, "loadRepo(%d): %v", c.Issue.RepoID, err) | |||
| return "" | |||
| } | |||
| if !c.Issue.IsPull { | |||
| return "" | |||
| } | |||
| @@ -303,6 +301,39 @@ func (c *Comment) LoadMilestone() error { | |||
| return nil | |||
| } | |||
| // LoadPoster loads comment poster | |||
| func (c *Comment) LoadPoster() error { | |||
| if c.PosterID <= 0 || c.Poster != nil { | |||
| return nil | |||
| } | |||
| var err error | |||
| c.Poster, err = getUserByID(x, c.PosterID) | |||
| if err != nil { | |||
| if IsErrUserNotExist(err) { | |||
| c.PosterID = -1 | |||
| c.Poster = NewGhostUser() | |||
| } else { | |||
| log.Error(3, "getUserByID[%d]: %v", c.ID, err) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // LoadAttachments loads attachments | |||
| func (c *Comment) LoadAttachments() error { | |||
| if len(c.Attachments) > 0 { | |||
| return nil | |||
| } | |||
| var err error | |||
| c.Attachments, err = getAttachmentsByCommentID(x, c.ID) | |||
| if err != nil { | |||
| log.Error(3, "getAttachmentsByCommentID[%d]: %v", c.ID, err) | |||
| } | |||
| return nil | |||
| } | |||
| // LoadAssigneeUser if comment.Type is CommentTypeAssignees, then load assignees | |||
| func (c *Comment) LoadAssigneeUser() error { | |||
| var err error | |||
| @@ -375,8 +406,10 @@ func (c *Comment) LoadReactions() error { | |||
| } | |||
| func (c *Comment) loadReview(e Engine) (err error) { | |||
| if c.Review, err = getReviewByID(e, c.ReviewID); err != nil { | |||
| return err | |||
| if c.Review == nil { | |||
| if c.Review, err = getReviewByID(e, c.ReviewID); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| @@ -454,6 +487,11 @@ func (c *Comment) CodeCommentURL() string { | |||
| log.Error(4, "LoadIssue(%d): %v", c.IssueID, err) | |||
| return "" | |||
| } | |||
| err = c.Issue.loadRepo(x) | |||
| if err != nil { // Silently dropping errors :unamused: | |||
| log.Error(4, "loadRepo(%d): %v", c.Issue.RepoID, err) | |||
| return "" | |||
| } | |||
| return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag()) | |||
| } | |||
| @@ -601,7 +639,7 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen | |||
| return nil | |||
| } | |||
| func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) { | |||
| func createStatusComment(e *xorm.Session, doer *User, issue *Issue) (*Comment, error) { | |||
| cmtType := CommentTypeClose | |||
| if !issue.IsClosed { | |||
| cmtType = CommentTypeReopen | |||
| @@ -609,7 +647,7 @@ func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *I | |||
| return createComment(e, &CreateCommentOptions{ | |||
| Type: cmtType, | |||
| Doer: doer, | |||
| Repo: repo, | |||
| Repo: issue.Repo, | |||
| Issue: issue, | |||
| }) | |||
| } | |||
| @@ -983,6 +1021,9 @@ func UpdateComment(doer *User, c *Comment, oldContent string) error { | |||
| UpdateIssueIndexer(c.IssueID) | |||
| } | |||
| if err := c.LoadPoster(); err != nil { | |||
| return err | |||
| } | |||
| if err := c.LoadIssue(); err != nil { | |||
| return err | |||
| } | |||
| @@ -1040,6 +1081,9 @@ func DeleteComment(doer *User, comment *Comment) error { | |||
| UpdateIssueIndexer(comment.IssueID) | |||
| } | |||
| if err := comment.LoadPoster(); err != nil { | |||
| return err | |||
| } | |||
| if err := comment.LoadIssue(); err != nil { | |||
| return err | |||
| } | |||
| @@ -1095,6 +1139,10 @@ func fetchCodeCommentsByReview(e Engine, issue *Issue, currentUser *User, review | |||
| return nil, err | |||
| } | |||
| if err := CommentList(comments).loadPosters(e); err != nil { | |||
| return nil, err | |||
| } | |||
| if err := issue.loadRepo(e); err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -0,0 +1,58 @@ | |||
| // Copyright 2018 The Gitea 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 | |||
| // CommentList defines a list of comments | |||
| type CommentList []*Comment | |||
| func (comments CommentList) getPosterIDs() []int64 { | |||
| commentIDs := make(map[int64]struct{}, len(comments)) | |||
| for _, comment := range comments { | |||
| if _, ok := commentIDs[comment.PosterID]; !ok { | |||
| commentIDs[comment.PosterID] = struct{}{} | |||
| } | |||
| } | |||
| return keysInt64(commentIDs) | |||
| } | |||
| // LoadPosters loads posters from database | |||
| func (comments CommentList) LoadPosters() error { | |||
| return comments.loadPosters(x) | |||
| } | |||
| func (comments CommentList) loadPosters(e Engine) error { | |||
| if len(comments) == 0 { | |||
| return nil | |||
| } | |||
| posterIDs := comments.getPosterIDs() | |||
| posterMaps := make(map[int64]*User, len(posterIDs)) | |||
| var left = len(posterIDs) | |||
| for left > 0 { | |||
| var limit = defaultMaxInSize | |||
| if left < limit { | |||
| limit = left | |||
| } | |||
| err := e. | |||
| In("id", posterIDs[:limit]). | |||
| Find(&posterMaps) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| left = left - limit | |||
| posterIDs = posterIDs[limit:] | |||
| } | |||
| for _, comment := range comments { | |||
| if comment.PosterID <= 0 { | |||
| continue | |||
| } | |||
| var ok bool | |||
| if comment.Poster, ok = posterMaps[comment.PosterID]; !ok { | |||
| comment.Poster = NewGhostUser() | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| @@ -19,8 +19,11 @@ func TestCreateIssueDependency(t *testing.T) { | |||
| issue1, err := GetIssueByID(1) | |||
| assert.NoError(t, err) | |||
| issue1.LoadAttributes() | |||
| issue2, err := GetIssueByID(2) | |||
| assert.NoError(t, err) | |||
| issue2.LoadAttributes() | |||
| // Create a dependency and check if it was successful | |||
| err = CreateIssueDependency(user1, issue1, issue2) | |||
| @@ -44,7 +47,7 @@ func TestCreateIssueDependency(t *testing.T) { | |||
| assert.False(t, left) | |||
| // Close #2 and check again | |||
| err = issue2.ChangeStatus(user1, issue2.Repo, true) | |||
| err = issue2.ChangeStatus(user1, true) | |||
| assert.NoError(t, err) | |||
| left, err = IssueNoDependenciesLeft(issue1) | |||
| @@ -39,7 +39,7 @@ func mailIssueCommentToParticipants(e Engine, issue *Issue, doer *User, content | |||
| // In case the issue poster is not watching the repository and is active, | |||
| // even if we have duplicated in watchers, can be safely filtered out. | |||
| poster, err := GetUserByID(issue.PosterID) | |||
| poster, err := getUserByID(e, issue.PosterID) | |||
| if err != nil { | |||
| return fmt.Errorf("GetUserByID [%d]: %v", issue.PosterID, err) | |||
| } | |||
| @@ -49,6 +49,10 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error { | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err := issue.loadRepo(x); err != nil { | |||
| return err | |||
| } | |||
| if exists { | |||
| // Create tracked time out of the time difference between start date and actual date | |||
| timediff := time.Now().Unix() - int64(sw.CreatedUnix) | |||
| @@ -112,6 +116,10 @@ func CancelStopwatch(user *User, issue *Issue) error { | |||
| return err | |||
| } | |||
| if err := issue.loadRepo(x); err != nil { | |||
| return err | |||
| } | |||
| if _, err := CreateComment(&CreateCommentOptions{ | |||
| Doer: user, | |||
| Issue: issue, | |||
| @@ -90,6 +90,9 @@ func AddTime(user *User, issue *Issue, time int64) (*TrackedTime, error) { | |||
| if _, err := x.Insert(tt); err != nil { | |||
| return nil, err | |||
| } | |||
| if err := issue.loadRepo(x); err != nil { | |||
| return nil, err | |||
| } | |||
| if _, err := CreateComment(&CreateCommentOptions{ | |||
| Issue: issue, | |||
| Repo: issue.Repo, | |||
| @@ -151,6 +151,7 @@ func composeTplData(subject, body, link string) map[string]interface{} { | |||
| func composeIssueCommentMessage(issue *Issue, doer *User, content string, comment *Comment, tplName base.TplName, tos []string, info string) *mailer.Message { | |||
| subject := issue.mailSubject() | |||
| issue.LoadRepo() | |||
| body := string(markup.RenderByType(markdown.MarkupName, []byte(content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas())) | |||
| data := make(map[string]interface{}, 10) | |||
| @@ -148,6 +148,10 @@ func (pr *PullRequest) GetGitRefName() string { | |||
| // Required - Issue | |||
| // Optional - Merger | |||
| func (pr *PullRequest) APIFormat() *api.PullRequest { | |||
| return pr.apiFormat(x) | |||
| } | |||
| func (pr *PullRequest) apiFormat(e Engine) *api.PullRequest { | |||
| var ( | |||
| baseBranch *Branch | |||
| headBranch *Branch | |||
| @@ -155,16 +159,20 @@ func (pr *PullRequest) APIFormat() *api.PullRequest { | |||
| headCommit *git.Commit | |||
| err error | |||
| ) | |||
| apiIssue := pr.Issue.APIFormat() | |||
| if err = pr.Issue.loadRepo(e); err != nil { | |||
| log.Error(log.ERROR, "loadRepo[%d]: %v", pr.ID, err) | |||
| return nil | |||
| } | |||
| apiIssue := pr.Issue.apiFormat(e) | |||
| if pr.BaseRepo == nil { | |||
| pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID) | |||
| pr.BaseRepo, err = getRepositoryByID(e, pr.BaseRepoID) | |||
| if err != nil { | |||
| log.Error(log.ERROR, "GetRepositoryById[%d]: %v", pr.ID, err) | |||
| return nil | |||
| } | |||
| } | |||
| if pr.HeadRepo == nil { | |||
| pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID) | |||
| pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID) | |||
| if err != nil { | |||
| log.Error(log.ERROR, "GetRepositoryById[%d]: %v", pr.ID, err) | |||
| return nil | |||
| @@ -187,15 +195,18 @@ func (pr *PullRequest) APIFormat() *api.PullRequest { | |||
| Ref: pr.BaseBranch, | |||
| Sha: baseCommit.ID.String(), | |||
| RepoID: pr.BaseRepoID, | |||
| Repository: pr.BaseRepo.APIFormat(AccessModeNone), | |||
| Repository: pr.BaseRepo.innerAPIFormat(e, AccessModeNone, false), | |||
| } | |||
| apiHeadBranchInfo := &api.PRBranchInfo{ | |||
| Name: pr.HeadBranch, | |||
| Ref: pr.HeadBranch, | |||
| Sha: headCommit.ID.String(), | |||
| RepoID: pr.HeadRepoID, | |||
| Repository: pr.HeadRepo.APIFormat(AccessModeNone), | |||
| Repository: pr.HeadRepo.innerAPIFormat(e, AccessModeNone, false), | |||
| } | |||
| pr.Issue.loadRepo(e) | |||
| apiPullRequest := &api.PullRequest{ | |||
| ID: pr.ID, | |||
| Index: pr.Index, | |||
| @@ -542,7 +553,7 @@ func (pr *PullRequest) setMerged() (err error) { | |||
| return err | |||
| } | |||
| if err = pr.Issue.changeStatus(sess, pr.Merger, pr.Issue.Repo, true); err != nil { | |||
| if err = pr.Issue.changeStatus(sess, pr.Merger, true); err != nil { | |||
| return fmt.Errorf("Issue.changeStatus: %v", err) | |||
| } | |||
| if _, err = sess.ID(pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil { | |||
| @@ -51,7 +51,7 @@ func ListToPushCommits(l *list.List) *PushCommits { | |||
| } | |||
| commits = append(commits, CommitToPushCommit(commit)) | |||
| } | |||
| return &PushCommits{l.Len(), commits, "", nil} | |||
| return &PushCommits{l.Len(), commits, "", make(map[string]string), make(map[string]*User)} | |||
| } | |||
| // PushUpdateOptions defines the push update options | |||
| @@ -175,6 +175,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) { | |||
| issue := &models.Issue{ | |||
| RepoID: ctx.Repo.Repository.ID, | |||
| Repo: ctx.Repo.Repository, | |||
| Title: form.Title, | |||
| PosterID: ctx.User.ID, | |||
| Poster: ctx.User, | |||
| @@ -212,7 +213,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) { | |||
| notification.NotifyNewIssue(issue) | |||
| if form.Closed { | |||
| if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, true); err != nil { | |||
| if err := issue.ChangeStatus(ctx.User, true); err != nil { | |||
| if models.IsErrDependenciesLeft(err) { | |||
| ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") | |||
| return | |||
| @@ -273,6 +274,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||
| } | |||
| return | |||
| } | |||
| issue.Repo = ctx.Repo.Repository | |||
| if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) { | |||
| ctx.Status(403) | |||
| @@ -333,7 +335,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||
| return | |||
| } | |||
| if form.State != nil { | |||
| if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil { | |||
| if err = issue.ChangeStatus(ctx.User, api.StateClosed == api.StateType(*form.State)); err != nil { | |||
| if models.IsErrDependenciesLeft(err) { | |||
| ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") | |||
| return | |||
| @@ -51,7 +51,7 @@ func ListIssueComments(ctx *context.APIContext) { | |||
| } | |||
| // comments,err:=models.GetCommentsByIssueIDSince(, since) | |||
| issue, err := models.GetRawIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | |||
| issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | |||
| if err != nil { | |||
| ctx.Error(500, "GetRawIssueByIndex", err) | |||
| return | |||
| @@ -68,6 +68,10 @@ func ListIssueComments(ctx *context.APIContext) { | |||
| } | |||
| apiComments := make([]*api.Comment, len(comments)) | |||
| if err = models.CommentList(comments).LoadPosters(); err != nil { | |||
| ctx.Error(500, "LoadPosters", err) | |||
| return | |||
| } | |||
| for i := range comments { | |||
| apiComments[i] = comments[i].APIFormat() | |||
| } | |||
| @@ -114,6 +118,11 @@ func ListRepoIssueComments(ctx *context.APIContext) { | |||
| return | |||
| } | |||
| if err = models.CommentList(comments).LoadPosters(); err != nil { | |||
| ctx.Error(500, "LoadPosters", err) | |||
| return | |||
| } | |||
| apiComments := make([]*api.Comment, len(comments)) | |||
| for i := range comments { | |||
| apiComments[i] = comments[i].APIFormat() | |||
| @@ -350,6 +350,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||
| pr.LoadIssue() | |||
| issue := pr.Issue | |||
| issue.Repo = ctx.Repo.Repository | |||
| if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypePullRequests) { | |||
| ctx.Status(403) | |||
| @@ -383,7 +384,6 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||
| // Send an empty array ([]) to clear all assignees from the Issue. | |||
| if ctx.Repo.CanWrite(models.UnitTypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) { | |||
| err = models.UpdateAPIAssignee(issue, form.Assignee, form.Assignees, ctx.User) | |||
| if err != nil { | |||
| if models.IsErrUserNotExist(err) { | |||
| @@ -422,7 +422,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||
| return | |||
| } | |||
| if form.State != nil { | |||
| if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil { | |||
| if err = issue.ChangeStatus(ctx.User, api.StateClosed == api.StateType(*form.State)); err != nil { | |||
| if models.IsErrDependenciesLeft(err) { | |||
| ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies") | |||
| return | |||
| @@ -576,6 +576,12 @@ func ViewIssue(ctx *context.Context) { | |||
| ctx.Data["RequireTribute"] = true | |||
| renderAttachmentSettings(ctx) | |||
| err = issue.LoadAttributes() | |||
| if err != nil { | |||
| ctx.ServerError("GetIssueByIndex", err) | |||
| return | |||
| } | |||
| ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) | |||
| var iw *models.IssueWatch | |||
| @@ -677,6 +683,10 @@ func ViewIssue(ctx *context.Context) { | |||
| ctx.ServerError("GetIssueByID", err) | |||
| return | |||
| } | |||
| if err = otherIssue.LoadRepo(); err != nil { | |||
| ctx.ServerError("LoadRepo", err) | |||
| return | |||
| } | |||
| // Add link to the issue of the already running stopwatch | |||
| ctx.Data["OtherStopwatchURL"] = otherIssue.HTMLURL() | |||
| } | |||
| @@ -697,7 +707,17 @@ func ViewIssue(ctx *context.Context) { | |||
| // Render comments and and fetch participants. | |||
| participants[0] = issue.Poster | |||
| for _, comment = range issue.Comments { | |||
| if err := comment.LoadPoster(); err != nil { | |||
| ctx.ServerError("LoadPoster", err) | |||
| return | |||
| } | |||
| if comment.Type == models.CommentTypeComment { | |||
| if err := comment.LoadAttachments(); err != nil { | |||
| ctx.ServerError("LoadAttachments", err) | |||
| return | |||
| } | |||
| comment.RenderedContent = string(markdown.Render([]byte(comment.Content), ctx.Repo.RepoLink, | |||
| ctx.Repo.Repository.ComposeMetas())) | |||
| @@ -868,6 +888,7 @@ func GetActionIssue(ctx *context.Context) *models.Issue { | |||
| ctx.NotFoundOrServerError("GetIssueByIndex", models.IsErrIssueNotExist, err) | |||
| return nil | |||
| } | |||
| issue.Repo = ctx.Repo.Repository | |||
| checkIssueRights(ctx, issue) | |||
| if ctx.Written() { | |||
| return nil | |||
| @@ -1049,7 +1070,7 @@ func UpdateIssueStatus(ctx *context.Context) { | |||
| } | |||
| for _, issue := range issues { | |||
| if issue.IsClosed != isClosed { | |||
| if err := issue.ChangeStatus(ctx.User, issue.Repo, isClosed); err != nil { | |||
| if err := issue.ChangeStatus(ctx.User, isClosed); err != nil { | |||
| if models.IsErrDependenciesLeft(err) { | |||
| ctx.JSON(http.StatusPreconditionFailed, map[string]interface{}{ | |||
| "error": "cannot close this issue because it still has open dependencies", | |||
| @@ -1126,7 +1147,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) { | |||
| ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index)) | |||
| } else { | |||
| isClosed := form.Status == "close" | |||
| if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, isClosed); err != nil { | |||
| if err := issue.ChangeStatus(ctx.User, isClosed); err != nil { | |||
| log.Error(4, "ChangeStatus: %v", err) | |||
| if models.IsErrDependenciesLeft(err) { | |||
| @@ -223,6 +223,10 @@ func checkPullInfo(ctx *context.Context) *models.Issue { | |||
| } | |||
| return nil | |||
| } | |||
| if err = issue.LoadPoster(); err != nil { | |||
| ctx.ServerError("LoadPoster", err) | |||
| return nil | |||
| } | |||
| ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) | |||
| ctx.Data["Issue"] = issue | |||
| @@ -231,6 +235,11 @@ func checkPullInfo(ctx *context.Context) *models.Issue { | |||
| return nil | |||
| } | |||
| if err = issue.LoadPullRequest(); err != nil { | |||
| ctx.ServerError("LoadPullRequest", err) | |||
| return nil | |||
| } | |||
| if err = issue.PullRequest.GetHeadRepo(); err != nil { | |||
| ctx.ServerError("GetHeadRepo", err) | |||
| return nil | |||
| @@ -519,16 +528,7 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { | |||
| return | |||
| } | |||
| pr, err := models.GetPullRequestByIssueID(issue.ID) | |||
| if err != nil { | |||
| if models.IsErrPullRequestNotExist(err) { | |||
| ctx.NotFound("GetPullRequestByIssueID", nil) | |||
| } else { | |||
| ctx.ServerError("GetPullRequestByIssueID", err) | |||
| } | |||
| return | |||
| } | |||
| pr.Issue = issue | |||
| pr := issue.PullRequest | |||
| if !pr.CanAutoMerge() || pr.HasMerged { | |||
| ctx.NotFound("MergePullRequest", nil) | |||
| @@ -949,15 +949,7 @@ func CleanUpPullRequest(ctx *context.Context) { | |||
| return | |||
| } | |||
| pr, err := models.GetPullRequestByIssueID(issue.ID) | |||
| if err != nil { | |||
| if models.IsErrPullRequestNotExist(err) { | |||
| ctx.NotFound("GetPullRequestByIssueID", nil) | |||
| } else { | |||
| ctx.ServerError("GetPullRequestByIssueID", err) | |||
| } | |||
| return | |||
| } | |||
| pr := issue.PullRequest | |||
| // Allow cleanup only for merged PR | |||
| if !pr.HasMerged { | |||
| @@ -965,7 +957,7 @@ func CleanUpPullRequest(ctx *context.Context) { | |||
| return | |||
| } | |||
| if err = pr.GetHeadRepo(); err != nil { | |||
| if err := pr.GetHeadRepo(); err != nil { | |||
| ctx.ServerError("GetHeadRepo", err) | |||
| return | |||
| } else if pr.HeadRepo == nil { | |||
| @@ -1077,8 +1069,12 @@ func DownloadPullDiff(ctx *context.Context) { | |||
| return | |||
| } | |||
| pr := issue.PullRequest | |||
| if err = issue.LoadPullRequest(); err != nil { | |||
| ctx.ServerError("LoadPullRequest", err) | |||
| return | |||
| } | |||
| pr := issue.PullRequest | |||
| if err = pr.GetBaseRepo(); err != nil { | |||
| ctx.ServerError("GetBaseRepo", err) | |||
| return | |||
| @@ -1111,8 +1107,12 @@ func DownloadPullPatch(ctx *context.Context) { | |||
| return | |||
| } | |||
| pr := issue.PullRequest | |||
| if err = issue.LoadPullRequest(); err != nil { | |||
| ctx.ServerError("LoadPullRequest", err) | |||
| return | |||
| } | |||
| pr := issue.PullRequest | |||
| if err = pr.GetHeadRepo(); err != nil { | |||
| ctx.ServerError("GetHeadRepo", err) | |||
| return | |||
| @@ -138,6 +138,7 @@ func Dashboard(ctx *context.Context) { | |||
| OnlyPerformedBy: false, | |||
| IncludeDeleted: false, | |||
| }) | |||
| if ctx.Written() { | |||
| return | |||
| } | |||