@@ -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 | |||
} |