Browse Source

Improve performance of dashboard (#4977)

tags/v1.7.0-dev
Lunny Xiao 5 years ago
parent
commit
b3b7598ec6

+ 53
- 13
models/action.go View File



// GetCommentLink returns link to action comment. // GetCommentLink returns link to action comment.
func (a *Action) GetCommentLink() string { func (a *Action) GetCommentLink() string {
return a.getCommentLink(x)
}

func (a *Action) getCommentLink(e Engine) string {
if a == nil { if a == nil {
return "#" return "#"
} }
return "#" return "#"
} }


issue, err := GetIssueByID(issueID)
issue, err := getIssueByID(e, issueID)
if err != nil { if err != nil {
return "#" return "#"
} }


if err = issue.loadRepo(e); err != nil {
return "#"
}

return issue.HTMLURL() return issue.HTMLURL()
} }


Commits []*PushCommit Commits []*PushCommit
CompareURL string CompareURL string


avatars map[string]string
avatars map[string]string
emailUsers map[string]*User
} }


// NewPushCommits creates a new PushCommits object. // NewPushCommits creates a new PushCommits object.
func NewPushCommits() *PushCommits { func NewPushCommits() *PushCommits {
return &PushCommits{ return &PushCommits{
avatars: make(map[string]string),
avatars: make(map[string]string),
emailUsers: make(map[string]*User),
} }
} }


// api.PayloadCommit format. // api.PayloadCommit format.
func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*api.PayloadCommit { func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*api.PayloadCommit {
commits := make([]*api.PayloadCommit, len(pc.Commits)) 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 { for i, commit := range pc.Commits {
authorUsername := "" 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 authorUsername = author.Name
} }

committerUsername := "" 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 committerUsername = committer.Name
} }
commits[i] = &api.PayloadCommit{ commits[i] = &api.PayloadCommit{
// AvatarLink tries to match user in database with e-mail // AvatarLink tries to match user in database with e-mail
// in order to show custom avatar, and falls back to general avatar link. // in order to show custom avatar, and falls back to general avatar link.
func (pc *PushCommits) AvatarLink(email string) string { 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 { if !ok {
u, err := GetUserByEmail(email)
var err error
u, err = GetUserByEmail(email)
if err != nil { if err != nil {
pc.avatars[email] = base.AvatarLink(email) pc.avatars[email] = base.AvatarLink(email)
if !IsErrUserNotExist(err) { if !IsErrUserNotExist(err) {
log.Error(4, "GetUserByEmail: %v", err) log.Error(4, "GetUserByEmail: %v", err)
return ""
} }
} else { } else {
pc.avatars[email] = u.RelAvatarLink()
pc.emailUsers[email] = u
} }
} }
if u != nil {
pc.avatars[email] = u.RelAvatarLink()
}


return pc.avatars[email] return pc.avatars[email]
} }
continue 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 // Don't return an error when dependencies are open as this would let the push fail
if IsErrDependenciesLeft(err) { if IsErrDependenciesLeft(err) {
return nil return nil
continue continue
} }


if err = issue.ChangeStatus(doer, repo, false); err != nil {
issue.Repo = repo
if err = issue.ChangeStatus(doer, false); err != nil {
return err return err
} }
} }

+ 67
- 16
models/issue.go View File

return util.TimeStampNow() >= issue.DeadlineUnix 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) { func (issue *Issue) loadRepo(e Engine) (err error) {
if issue.Repo == nil { if issue.Repo == nil {
issue.Repo, err = getRepositoryByID(e, issue.RepoID) issue.Repo, err = getRepositoryByID(e, issue.RepoID)
return nil return nil
} }


// LoadPoster loads poster
func (issue *Issue) LoadPoster() error {
return issue.loadPoster(x)
}

func (issue *Issue) loadPoster(e Engine) (err error) { func (issue *Issue) loadPoster(e Engine) (err error) {
if issue.Poster == nil { if issue.Poster == nil {
issue.Poster, err = getUserByID(e, issue.PosterID) issue.Poster, err = getUserByID(e, issue.PosterID)
} }
return fmt.Errorf("getPullRequestByIssueID [%d]: %v", issue.ID, err) return fmt.Errorf("getPullRequestByIssueID [%d]: %v", issue.ID, err)
} }
issue.PullRequest.Issue = issue
} }
return nil return nil
} }


// LoadPullRequest loads pull request info
func (issue *Issue) LoadPullRequest() error {
return issue.loadPullRequest(x)
}

func (issue *Issue) loadComments(e Engine) (err error) { func (issue *Issue) loadComments(e Engine) (err error) {
if issue.Comments != nil { if issue.Comments != nil {
return nil return nil
// Required - Poster, Labels, // Required - Poster, Labels,
// Optional - Milestone, Assignee, PullRequest // Optional - Milestone, Assignee, PullRequest
func (issue *Issue) APIFormat() *api.Issue { 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)) apiLabels := make([]*api.Label, len(issue.Labels))
for i := range issue.Labels { for i := range issue.Labels {
apiLabels[i] = issue.Labels[i].APIFormat() apiLabels[i] = issue.Labels[i].APIFormat()
} }


issue.loadPoster(e)
issue.loadRepo(e)
apiIssue := &api.Issue{ apiIssue := &api.Issue{
ID: issue.ID, ID: issue.ID,
URL: issue.APIURL(), URL: issue.APIURL(),
if issue.Milestone != nil { if issue.Milestone != nil {
apiIssue.Milestone = issue.Milestone.APIFormat() apiIssue.Milestone = issue.Milestone.APIFormat()
} }
issue.loadAssignees(e)

if len(issue.Assignees) > 0 { if len(issue.Assignees) > 0 {
for _, assignee := range issue.Assignees { for _, assignee := range issue.Assignees {
apiIssue.Assignees = append(apiIssue.Assignees, assignee.APIFormat()) apiIssue.Assignees = append(apiIssue.Assignees, assignee.APIFormat())
apiIssue.Assignee = issue.Assignees[0].APIFormat() // For compatibility, we're keeping the first assignee as `apiIssue.Assignee` apiIssue.Assignee = issue.Assignees[0].APIFormat() // For compatibility, we're keeping the first assignee as `apiIssue.Assignee`
} }
if issue.IsPull { if issue.IsPull {
issue.loadPullRequest(e)
apiIssue.PullRequest = &api.PullRequestMeta{ apiIssue.PullRequest = &api.PullRequestMeta{
HasMerged: issue.PullRequest.HasMerged, HasMerged: issue.PullRequest.HasMerged,
} }
return updateIssueCols(x, issue, cols...) 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 // Nothing should be performed if current status is same as target status
if issue.IsClosed == isClosed { if issue.IsClosed == isClosed {
return nil return nil
} }


// New action comment // New action comment
if _, err = createStatusComment(e, doer, repo, issue); err != nil {
if _, err = createStatusComment(e, doer, issue); err != nil {
return err return err
} }


} }


// ChangeStatus changes issue status to open or closed. // 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() 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 = 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 return err
} }




mode, _ := AccessLevel(issue.Poster, issue.Repo) mode, _ := AccessLevel(issue.Poster, issue.Repo)
if issue.IsPull { if issue.IsPull {
if err = issue.loadPullRequest(sess); err != nil {
return err
}
// Merge pull request calls issue.changeStatus so we need to handle separately. // Merge pull request calls issue.changeStatus so we need to handle separately.
issue.PullRequest.Issue = issue
apiPullRequest := &api.PullRequestPayload{ apiPullRequest := &api.PullRequestPayload{
Index: issue.Index, Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(), PullRequest: issue.PullRequest.APIFormat(),
Repository: repo.APIFormat(mode),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
} }
if isClosed { if isClosed {
} else { } else {
apiPullRequest.Action = api.HookIssueReOpened apiPullRequest.Action = api.HookIssueReOpened
} }
err = PrepareWebhooks(repo, HookEventPullRequest, apiPullRequest)
err = PrepareWebhooks(issue.Repo, HookEventPullRequest, apiPullRequest)
} else { } else {
apiIssue := &api.IssuePayload{ apiIssue := &api.IssuePayload{
Index: issue.Index, Index: issue.Index,
Issue: issue.APIFormat(), Issue: issue.APIFormat(),
Repository: repo.APIFormat(mode),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
} }
if isClosed { if isClosed {
} else { } else {
apiIssue.Action = api.HookIssueReOpened apiIssue.Action = api.HookIssueReOpened
} }
err = PrepareWebhooks(repo, HookEventIssues, apiIssue)
err = PrepareWebhooks(issue.Repo, HookEventIssues, apiIssue)
} }
if err != nil { if err != nil {
log.Error(4, "PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err) log.Error(4, "PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err)
} else { } else {
go HookQueue.Add(repo.ID)
go HookQueue.Add(issue.Repo.ID)
} }


return nil return nil
return fmt.Errorf("updateIssueCols: %v", err) 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 { if _, err = createChangeTitleComment(sess, doer, issue.Repo, issue, oldTitle, title); err != nil {
return fmt.Errorf("createChangeTitleComment: %v", err) return fmt.Errorf("createChangeTitleComment: %v", err)
} }


mode, _ := AccessLevel(issue.Poster, issue.Repo) mode, _ := AccessLevel(issue.Poster, issue.Repo)
if issue.IsPull { if issue.IsPull {
if err = issue.loadPullRequest(sess); err != nil {
return fmt.Errorf("loadPullRequest: %v", err)
}
issue.PullRequest.Issue = issue issue.PullRequest.Issue = issue
err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueEdited, Action: api.HookIssueEdited,
return nil 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{ issue := &Issue{
RepoID: repoID, RepoID: repoID,
Index: index, Index: index,
return issue, nil 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 { if err != nil {
return nil, err return nil, err
} }
} else if !has { } else if !has {
return nil, ErrIssueNotExist{id, 0, 0} 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. // GetIssueByID returns an issue by given ID.

+ 3
- 3
models/issue_assignees.go View File

apiPullRequest := &api.PullRequestPayload{ apiPullRequest := &api.PullRequestPayload{
Index: issue.Index, Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(), PullRequest: issue.PullRequest.APIFormat(),
Repository: issue.Repo.APIFormat(mode),
Repository: issue.Repo.innerAPIFormat(sess, mode, false),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
} }
if removed { if removed {


apiIssue := &api.IssuePayload{ apiIssue := &api.IssuePayload{
Index: issue.Index, 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(), Sender: doer.APIFormat(),
} }
if removed { if removed {

+ 1
- 1
models/issue_assignees_test.go View File

assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())


// Fake issue with assignees // Fake issue with assignees
issue, err := GetIssueByID(1)
issue, err := GetIssueWithAttrsByID(1)
assert.NoError(t, err) assert.NoError(t, err)


// Assign multiple users // Assign multiple users

+ 71
- 23
models/issue_comment.go View File

return 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. // AfterDelete is invoked from XORM after the object is deleted.
func (c *Comment) AfterDelete() { func (c *Comment) AfterDelete() {
if c.ID <= 0 { if c.ID <= 0 {
log.Error(4, "LoadIssue(%d): %v", c.IssueID, err) log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return "" 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.Type == CommentTypeCode {
if c.ReviewID == 0 { if c.ReviewID == 0 {
return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag()) return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag())
if c.Issue.IsPull { if c.Issue.IsPull {
return "" 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() return c.Issue.HTMLURL()
} }


return "" 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 { if !c.Issue.IsPull {
return "" return ""
} }
return nil 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 // LoadAssigneeUser if comment.Type is CommentTypeAssignees, then load assignees
func (c *Comment) LoadAssigneeUser() error { func (c *Comment) LoadAssigneeUser() error {
var err error var err error
} }


func (c *Comment) loadReview(e Engine) (err 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 return nil
} }
log.Error(4, "LoadIssue(%d): %v", c.IssueID, err) log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return "" 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()) return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag())
} }


return nil 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 cmtType := CommentTypeClose
if !issue.IsClosed { if !issue.IsClosed {
cmtType = CommentTypeReopen cmtType = CommentTypeReopen
return createComment(e, &CreateCommentOptions{ return createComment(e, &CreateCommentOptions{
Type: cmtType, Type: cmtType,
Doer: doer, Doer: doer,
Repo: repo,
Repo: issue.Repo,
Issue: issue, Issue: issue,
}) })
} }
UpdateIssueIndexer(c.IssueID) UpdateIssueIndexer(c.IssueID)
} }


if err := c.LoadPoster(); err != nil {
return err
}
if err := c.LoadIssue(); err != nil { if err := c.LoadIssue(); err != nil {
return err return err
} }
UpdateIssueIndexer(comment.IssueID) UpdateIssueIndexer(comment.IssueID)
} }


if err := comment.LoadPoster(); err != nil {
return err
}
if err := comment.LoadIssue(); err != nil { if err := comment.LoadIssue(); err != nil {
return err return err
} }
return nil, err return nil, err
} }


if err := CommentList(comments).loadPosters(e); err != nil {
return nil, err
}

if err := issue.loadRepo(e); err != nil { if err := issue.loadRepo(e); err != nil {
return nil, err return nil, err
} }

+ 58
- 0
models/issue_comment_list.go View File

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

+ 4
- 1
models/issue_dependency_test.go View File



issue1, err := GetIssueByID(1) issue1, err := GetIssueByID(1)
assert.NoError(t, err) assert.NoError(t, err)
issue1.LoadAttributes()

issue2, err := GetIssueByID(2) issue2, err := GetIssueByID(2)
assert.NoError(t, err) assert.NoError(t, err)
issue2.LoadAttributes()


// Create a dependency and check if it was successful // Create a dependency and check if it was successful
err = CreateIssueDependency(user1, issue1, issue2) err = CreateIssueDependency(user1, issue1, issue2)
assert.False(t, left) assert.False(t, left)


// Close #2 and check again // Close #2 and check again
err = issue2.ChangeStatus(user1, issue2.Repo, true)
err = issue2.ChangeStatus(user1, true)
assert.NoError(t, err) assert.NoError(t, err)


left, err = IssueNoDependenciesLeft(issue1) left, err = IssueNoDependenciesLeft(issue1)

+ 1
- 1
models/issue_mail.go View File



// In case the issue poster is not watching the repository and is active, // 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. // 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 { if err != nil {
return fmt.Errorf("GetUserByID [%d]: %v", issue.PosterID, err) return fmt.Errorf("GetUserByID [%d]: %v", issue.PosterID, err)
} }

+ 8
- 0
models/issue_stopwatch.go View File

if err != nil { if err != nil {
return err return err
} }
if err := issue.loadRepo(x); err != nil {
return err
}

if exists { if exists {
// Create tracked time out of the time difference between start date and actual date // Create tracked time out of the time difference between start date and actual date
timediff := time.Now().Unix() - int64(sw.CreatedUnix) timediff := time.Now().Unix() - int64(sw.CreatedUnix)
return err return err
} }


if err := issue.loadRepo(x); err != nil {
return err
}

if _, err := CreateComment(&CreateCommentOptions{ if _, err := CreateComment(&CreateCommentOptions{
Doer: user, Doer: user,
Issue: issue, Issue: issue,

+ 3
- 0
models/issue_tracked_time.go View File

if _, err := x.Insert(tt); err != nil { if _, err := x.Insert(tt); err != nil {
return nil, err return nil, err
} }
if err := issue.loadRepo(x); err != nil {
return nil, err
}
if _, err := CreateComment(&CreateCommentOptions{ if _, err := CreateComment(&CreateCommentOptions{
Issue: issue, Issue: issue,
Repo: issue.Repo, Repo: issue.Repo,

+ 1
- 0
models/mail.go View File



func composeIssueCommentMessage(issue *Issue, doer *User, content string, comment *Comment, tplName base.TplName, tos []string, info string) *mailer.Message { func composeIssueCommentMessage(issue *Issue, doer *User, content string, comment *Comment, tplName base.TplName, tos []string, info string) *mailer.Message {
subject := issue.mailSubject() subject := issue.mailSubject()
issue.LoadRepo()
body := string(markup.RenderByType(markdown.MarkupName, []byte(content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas())) body := string(markup.RenderByType(markdown.MarkupName, []byte(content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas()))


data := make(map[string]interface{}, 10) data := make(map[string]interface{}, 10)

+ 17
- 6
models/pull.go View File

// Required - Issue // Required - Issue
// Optional - Merger // Optional - Merger
func (pr *PullRequest) APIFormat() *api.PullRequest { func (pr *PullRequest) APIFormat() *api.PullRequest {
return pr.apiFormat(x)
}

func (pr *PullRequest) apiFormat(e Engine) *api.PullRequest {
var ( var (
baseBranch *Branch baseBranch *Branch
headBranch *Branch headBranch *Branch
headCommit *git.Commit headCommit *git.Commit
err error 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 { if pr.BaseRepo == nil {
pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
pr.BaseRepo, err = getRepositoryByID(e, pr.BaseRepoID)
if err != nil { if err != nil {
log.Error(log.ERROR, "GetRepositoryById[%d]: %v", pr.ID, err) log.Error(log.ERROR, "GetRepositoryById[%d]: %v", pr.ID, err)
return nil return nil
} }
} }
if pr.HeadRepo == nil { if pr.HeadRepo == nil {
pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID)
pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
if err != nil { if err != nil {
log.Error(log.ERROR, "GetRepositoryById[%d]: %v", pr.ID, err) log.Error(log.ERROR, "GetRepositoryById[%d]: %v", pr.ID, err)
return nil return nil
Ref: pr.BaseBranch, Ref: pr.BaseBranch,
Sha: baseCommit.ID.String(), Sha: baseCommit.ID.String(),
RepoID: pr.BaseRepoID, RepoID: pr.BaseRepoID,
Repository: pr.BaseRepo.APIFormat(AccessModeNone),
Repository: pr.BaseRepo.innerAPIFormat(e, AccessModeNone, false),
} }
apiHeadBranchInfo := &api.PRBranchInfo{ apiHeadBranchInfo := &api.PRBranchInfo{
Name: pr.HeadBranch, Name: pr.HeadBranch,
Ref: pr.HeadBranch, Ref: pr.HeadBranch,
Sha: headCommit.ID.String(), Sha: headCommit.ID.String(),
RepoID: pr.HeadRepoID, RepoID: pr.HeadRepoID,
Repository: pr.HeadRepo.APIFormat(AccessModeNone),
Repository: pr.HeadRepo.innerAPIFormat(e, AccessModeNone, false),
} }

pr.Issue.loadRepo(e)

apiPullRequest := &api.PullRequest{ apiPullRequest := &api.PullRequest{
ID: pr.ID, ID: pr.ID,
Index: pr.Index, Index: pr.Index,
return err 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) 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 { if _, err = sess.ID(pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil {

+ 1
- 1
models/update.go View File

} }
commits = append(commits, CommitToPushCommit(commit)) 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 // PushUpdateOptions defines the push update options

+ 4
- 2
routers/api/v1/repo/issue.go View File



issue := &models.Issue{ issue := &models.Issue{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
Repo: ctx.Repo.Repository,
Title: form.Title, Title: form.Title,
PosterID: ctx.User.ID, PosterID: ctx.User.ID,
Poster: ctx.User, Poster: ctx.User,
notification.NotifyNewIssue(issue) notification.NotifyNewIssue(issue)


if form.Closed { 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) { if models.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return return
} }
return return
} }
issue.Repo = ctx.Repo.Repository


if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) { if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) {
ctx.Status(403) ctx.Status(403)
return return
} }
if form.State != nil { 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) { if models.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return return

+ 10
- 1
routers/api/v1/repo/issue_comment.go View File

} }


// comments,err:=models.GetCommentsByIssueIDSince(, since) // 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 { if err != nil {
ctx.Error(500, "GetRawIssueByIndex", err) ctx.Error(500, "GetRawIssueByIndex", err)
return return
} }


apiComments := make([]*api.Comment, len(comments)) apiComments := make([]*api.Comment, len(comments))
if err = models.CommentList(comments).LoadPosters(); err != nil {
ctx.Error(500, "LoadPosters", err)
return
}
for i := range comments { for i := range comments {
apiComments[i] = comments[i].APIFormat() apiComments[i] = comments[i].APIFormat()
} }
return return
} }


if err = models.CommentList(comments).LoadPosters(); err != nil {
ctx.Error(500, "LoadPosters", err)
return
}

apiComments := make([]*api.Comment, len(comments)) apiComments := make([]*api.Comment, len(comments))
for i := range comments { for i := range comments {
apiComments[i] = comments[i].APIFormat() apiComments[i] = comments[i].APIFormat()

+ 2
- 2
routers/api/v1/repo/pull.go View File



pr.LoadIssue() pr.LoadIssue()
issue := pr.Issue issue := pr.Issue
issue.Repo = ctx.Repo.Repository


if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypePullRequests) { if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypePullRequests) {
ctx.Status(403) ctx.Status(403)
// Send an empty array ([]) to clear all assignees from the Issue. // Send an empty array ([]) to clear all assignees from the Issue.


if ctx.Repo.CanWrite(models.UnitTypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) { if ctx.Repo.CanWrite(models.UnitTypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) {

err = models.UpdateAPIAssignee(issue, form.Assignee, form.Assignees, ctx.User) err = models.UpdateAPIAssignee(issue, form.Assignee, form.Assignees, ctx.User)
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
return return
} }
if form.State != nil { 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) { if models.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies") ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
return return

+ 23
- 2
routers/repo/issue.go View File

ctx.Data["RequireTribute"] = true ctx.Data["RequireTribute"] = true
renderAttachmentSettings(ctx) renderAttachmentSettings(ctx)


err = issue.LoadAttributes()
if err != nil {
ctx.ServerError("GetIssueByIndex", err)
return
}

ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)


var iw *models.IssueWatch var iw *models.IssueWatch
ctx.ServerError("GetIssueByID", err) ctx.ServerError("GetIssueByID", err)
return return
} }
if err = otherIssue.LoadRepo(); err != nil {
ctx.ServerError("LoadRepo", err)
return
}
// Add link to the issue of the already running stopwatch // Add link to the issue of the already running stopwatch
ctx.Data["OtherStopwatchURL"] = otherIssue.HTMLURL() ctx.Data["OtherStopwatchURL"] = otherIssue.HTMLURL()
} }
// Render comments and and fetch participants. // Render comments and and fetch participants.
participants[0] = issue.Poster participants[0] = issue.Poster
for _, comment = range issue.Comments { for _, comment = range issue.Comments {
if err := comment.LoadPoster(); err != nil {
ctx.ServerError("LoadPoster", err)
return
}

if comment.Type == models.CommentTypeComment { 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, comment.RenderedContent = string(markdown.Render([]byte(comment.Content), ctx.Repo.RepoLink,
ctx.Repo.Repository.ComposeMetas())) ctx.Repo.Repository.ComposeMetas()))


ctx.NotFoundOrServerError("GetIssueByIndex", models.IsErrIssueNotExist, err) ctx.NotFoundOrServerError("GetIssueByIndex", models.IsErrIssueNotExist, err)
return nil return nil
} }
issue.Repo = ctx.Repo.Repository
checkIssueRights(ctx, issue) checkIssueRights(ctx, issue)
if ctx.Written() { if ctx.Written() {
return nil return nil
} }
for _, issue := range issues { for _, issue := range issues {
if issue.IsClosed != isClosed { 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) { if models.IsErrDependenciesLeft(err) {
ctx.JSON(http.StatusPreconditionFailed, map[string]interface{}{ ctx.JSON(http.StatusPreconditionFailed, map[string]interface{}{
"error": "cannot close this issue because it still has open dependencies", "error": "cannot close this issue because it still has open dependencies",
ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index)) ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index))
} else { } else {
isClosed := form.Status == "close" 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) log.Error(4, "ChangeStatus: %v", err)


if models.IsErrDependenciesLeft(err) { if models.IsErrDependenciesLeft(err) {

+ 22
- 22
routers/repo/pull.go View File

} }
return nil 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["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
ctx.Data["Issue"] = issue ctx.Data["Issue"] = issue


return nil return nil
} }


if err = issue.LoadPullRequest(); err != nil {
ctx.ServerError("LoadPullRequest", err)
return nil
}

if err = issue.PullRequest.GetHeadRepo(); err != nil { if err = issue.PullRequest.GetHeadRepo(); err != nil {
ctx.ServerError("GetHeadRepo", err) ctx.ServerError("GetHeadRepo", err)
return nil return nil
return 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 { if !pr.CanAutoMerge() || pr.HasMerged {
ctx.NotFound("MergePullRequest", nil) ctx.NotFound("MergePullRequest", nil)
return 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 // Allow cleanup only for merged PR
if !pr.HasMerged { if !pr.HasMerged {
return return
} }


if err = pr.GetHeadRepo(); err != nil {
if err := pr.GetHeadRepo(); err != nil {
ctx.ServerError("GetHeadRepo", err) ctx.ServerError("GetHeadRepo", err)
return return
} else if pr.HeadRepo == nil { } else if pr.HeadRepo == nil {
return return
} }


pr := issue.PullRequest
if err = issue.LoadPullRequest(); err != nil {
ctx.ServerError("LoadPullRequest", err)
return
}


pr := issue.PullRequest
if err = pr.GetBaseRepo(); err != nil { if err = pr.GetBaseRepo(); err != nil {
ctx.ServerError("GetBaseRepo", err) ctx.ServerError("GetBaseRepo", err)
return return
return return
} }


pr := issue.PullRequest
if err = issue.LoadPullRequest(); err != nil {
ctx.ServerError("LoadPullRequest", err)
return
}


pr := issue.PullRequest
if err = pr.GetHeadRepo(); err != nil { if err = pr.GetHeadRepo(); err != nil {
ctx.ServerError("GetHeadRepo", err) ctx.ServerError("GetHeadRepo", err)
return return

+ 1
- 0
routers/user/home.go View File

OnlyPerformedBy: false, OnlyPerformedBy: false,
IncludeDeleted: false, IncludeDeleted: false,
}) })

if ctx.Written() { if ctx.Written() {
return return
} }

Loading…
Cancel
Save