mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Improve performance of dashboard (#4977)
This commit is contained in:
		
				
					committed by
					
						 techknowlogick
						techknowlogick
					
				
			
			
				
	
			
			
			
						parent
						
							49ea6e0deb
						
					
				
				
					commit
					b3b7598ec6
				
			| @@ -194,6 +194,10 @@ func (a *Action) GetRepoLink() string { | |||||||
|  |  | ||||||
| // 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 "#" | ||||||
| 	} | 	} | ||||||
| @@ -213,11 +217,15 @@ func (a *Action) GetCommentLink() string { | |||||||
| 		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() | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -331,12 +339,14 @@ type PushCommits struct { | |||||||
| 	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), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -344,17 +354,35 @@ func NewPushCommits() *PushCommits { | |||||||
| // 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) | 		author, ok := pc.emailUsers[commit.AuthorEmail] | ||||||
|  | 		if !ok { | ||||||
|  | 			author, err = GetUserByEmail(commit.AuthorEmail) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				authorUsername = author.Name | 				authorUsername = author.Name | ||||||
|  | 				pc.emailUsers[commit.AuthorEmail] = author | ||||||
| 			} | 			} | ||||||
|  | 		} else { | ||||||
|  | 			authorUsername = author.Name | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		committerUsername := "" | 		committerUsername := "" | ||||||
| 		committer, err := GetUserByEmail(commit.CommitterEmail) | 		committer, ok := pc.emailUsers[commit.CommitterEmail] | ||||||
|  | 		if !ok { | ||||||
|  | 			committer, err = GetUserByEmail(commit.CommitterEmail) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				// TODO: check errors other than email not found. | 				// TODO: check errors other than email not found. | ||||||
| 				committerUsername = committer.Name | 				committerUsername = committer.Name | ||||||
|  | 				pc.emailUsers[commit.CommitterEmail] = committer | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			committerUsername = committer.Name | ||||||
| 		} | 		} | ||||||
| 		commits[i] = &api.PayloadCommit{ | 		commits[i] = &api.PayloadCommit{ | ||||||
| 			ID:      commit.Sha1, | 			ID:      commit.Sha1, | ||||||
| @@ -379,18 +407,28 @@ func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*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] | ||||||
| } | } | ||||||
| @@ -479,7 +517,8 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err | |||||||
| 				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 | ||||||
| @@ -504,7 +543,8 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err | |||||||
| 				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 | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -86,6 +86,11 @@ func (issue *Issue) IsOverdue() bool { | |||||||
| 	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) | ||||||
| @@ -129,6 +134,11 @@ func (issue *Issue) loadLabels(e Engine) (err error) { | |||||||
| 	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) | ||||||
| @@ -154,10 +164,16 @@ func (issue *Issue) loadPullRequest(e Engine) (err error) { | |||||||
| 			} | 			} | ||||||
| 			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 | ||||||
| @@ -310,11 +326,18 @@ func (issue *Issue) State() api.StateType { | |||||||
| // 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(), | ||||||
| @@ -336,6 +359,8 @@ func (issue *Issue) APIFormat() *api.Issue { | |||||||
| 	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()) | ||||||
| @@ -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` | 		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, | ||||||
| 		} | 		} | ||||||
| @@ -656,7 +682,7 @@ func UpdateIssueCols(issue *Issue, cols ...string) error { | |||||||
| 	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 | ||||||
| @@ -707,7 +733,7 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// 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 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -715,14 +741,21 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository, | |||||||
| } | } | ||||||
|  |  | ||||||
| // 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 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -733,12 +766,14 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e | |||||||
|  |  | ||||||
| 	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 { | ||||||
| @@ -746,12 +781,12 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e | |||||||
| 		} 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 { | ||||||
| @@ -759,12 +794,12 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e | |||||||
| 		} 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 | ||||||
| @@ -785,6 +820,10 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { | |||||||
| 		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) | ||||||
| 	} | 	} | ||||||
| @@ -795,6 +834,9 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { | |||||||
|  |  | ||||||
| 	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, | ||||||
| @@ -1099,8 +1141,8 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetRawIssueByIndex returns raw issue without loading attributes by index in a repository. | // GetIssueByIndex returns raw issue without loading attributes by index in a repository. | ||||||
| func GetRawIssueByIndex(repoID, index int64) (*Issue, error) { | func GetIssueByIndex(repoID, index int64) (*Issue, error) { | ||||||
| 	issue := &Issue{ | 	issue := &Issue{ | ||||||
| 		RepoID: repoID, | 		RepoID: repoID, | ||||||
| 		Index:  index, | 		Index:  index, | ||||||
| @@ -1114,9 +1156,9 @@ func GetRawIssueByIndex(repoID, index int64) (*Issue, error) { | |||||||
| 	return issue, nil | 	return issue, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetIssueByIndex returns issue by index in a repository. | // GetIssueWithAttrsByIndex returns issue by index in a repository. | ||||||
| func GetIssueByIndex(repoID, index int64) (*Issue, error) { | func GetIssueWithAttrsByIndex(repoID, index int64) (*Issue, error) { | ||||||
| 	issue, err := GetRawIssueByIndex(repoID, index) | 	issue, err := GetIssueByIndex(repoID, index) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -1131,7 +1173,16 @@ func getIssueByID(e Engine, id int64) (*Issue, error) { | |||||||
| 	} 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. | ||||||
|   | |||||||
| @@ -174,7 +174,7 @@ func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID in | |||||||
| 		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 { | ||||||
| @@ -191,8 +191,8 @@ func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID in | |||||||
|  |  | ||||||
| 		apiIssue := &api.IssuePayload{ | 		apiIssue := &api.IssuePayload{ | ||||||
| 			Index:      issue.Index, | 			Index:      issue.Index, | ||||||
| 			Issue:      issue.APIFormat(), | 			Issue:      issue.apiFormat(sess), | ||||||
| 			Repository: issue.Repo.APIFormat(mode), | 			Repository: issue.Repo.innerAPIFormat(sess, mode, false), | ||||||
| 			Sender:     doer.APIFormat(), | 			Sender:     doer.APIFormat(), | ||||||
| 		} | 		} | ||||||
| 		if removed { | 		if removed { | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ func TestUpdateAssignee(t *testing.T) { | |||||||
| 	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 | ||||||
|   | |||||||
| @@ -150,25 +150,6 @@ func (c *Comment) LoadIssue() (err error) { | |||||||
| 	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 { | ||||||
| @@ -189,6 +170,11 @@ func (c *Comment) HTMLURL() string { | |||||||
| 		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()) | ||||||
| @@ -217,6 +203,12 @@ func (c *Comment) IssueURL() string { | |||||||
| 	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() | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -228,6 +220,12 @@ func (c *Comment) PRURL() string { | |||||||
| 		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 "" | ||||||
| 	} | 	} | ||||||
| @@ -303,6 +301,39 @@ func (c *Comment) LoadMilestone() error { | |||||||
| 	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 | ||||||
| @@ -375,9 +406,11 @@ func (c *Comment) LoadReactions() error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Comment) loadReview(e Engine) (err error) { | func (c *Comment) loadReview(e Engine) (err error) { | ||||||
|  | 	if c.Review == nil { | ||||||
| 		if c.Review, err = getReviewByID(e, c.ReviewID); err != nil { | 		if c.Review, err = getReviewByID(e, c.ReviewID); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -454,6 +487,11 @@ func (c *Comment) CodeCommentURL() string { | |||||||
| 		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()) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -601,7 +639,7 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen | |||||||
| 	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 | ||||||
| @@ -609,7 +647,7 @@ func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *I | |||||||
| 	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, | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| @@ -983,6 +1021,9 @@ func UpdateComment(doer *User, c *Comment, oldContent string) error { | |||||||
| 		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 | ||||||
| 	} | 	} | ||||||
| @@ -1040,6 +1081,9 @@ func DeleteComment(doer *User, comment *Comment) error { | |||||||
| 		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 | ||||||
| 	} | 	} | ||||||
| @@ -1095,6 +1139,10 @@ func fetchCodeCommentsByReview(e Engine, issue *Issue, currentUser *User, review | |||||||
| 		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
									
								
								models/issue_comment_list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								models/issue_comment_list.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | 	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) | ||||||
| @@ -44,7 +47,7 @@ func TestCreateIssueDependency(t *testing.T) { | |||||||
| 	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) | ||||||
|   | |||||||
| @@ -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, | 	// 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) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -49,6 +49,10 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error { | |||||||
| 	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) | ||||||
| @@ -112,6 +116,10 @@ func CancelStopwatch(user *User, issue *Issue) error { | |||||||
| 			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, | ||||||
|   | |||||||
| @@ -90,6 +90,9 @@ func AddTime(user *User, issue *Issue, time int64) (*TrackedTime, error) { | |||||||
| 	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, | ||||||
|   | |||||||
| @@ -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 { | 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) | ||||||
|   | |||||||
| @@ -148,6 +148,10 @@ func (pr *PullRequest) GetGitRefName() string { | |||||||
| // 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 | ||||||
| @@ -155,16 +159,20 @@ func (pr *PullRequest) APIFormat() *api.PullRequest { | |||||||
| 		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 | ||||||
| @@ -187,15 +195,18 @@ func (pr *PullRequest) APIFormat() *api.PullRequest { | |||||||
| 		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, | ||||||
| @@ -542,7 +553,7 @@ func (pr *PullRequest) setMerged() (err error) { | |||||||
| 		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 { | ||||||
|   | |||||||
| @@ -51,7 +51,7 @@ func ListToPushCommits(l *list.List) *PushCommits { | |||||||
| 		} | 		} | ||||||
| 		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 | ||||||
|   | |||||||
| @@ -175,6 +175,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) { | |||||||
|  |  | ||||||
| 	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, | ||||||
| @@ -212,7 +213,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) { | |||||||
| 	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 | ||||||
| @@ -273,6 +274,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||||||
| 		} | 		} | ||||||
| 		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) | ||||||
| @@ -333,7 +335,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||||||
| 		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 | ||||||
|   | |||||||
| @@ -51,7 +51,7 @@ func ListIssueComments(ctx *context.APIContext) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// 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 | ||||||
| @@ -68,6 +68,10 @@ func ListIssueComments(ctx *context.APIContext) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	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() | ||||||
| 	} | 	} | ||||||
| @@ -114,6 +118,11 @@ func ListRepoIssueComments(ctx *context.APIContext) { | |||||||
| 		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() | ||||||
|   | |||||||
| @@ -350,6 +350,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||||||
|  |  | ||||||
| 	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) | ||||||
| @@ -383,7 +384,6 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||||||
| 	// 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) { | ||||||
| @@ -422,7 +422,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||||||
| 		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 | ||||||
|   | |||||||
| @@ -576,6 +576,12 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 	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 | ||||||
| @@ -677,6 +683,10 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 						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() | ||||||
| 				} | 				} | ||||||
| @@ -697,7 +707,17 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 	// 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())) | ||||||
|  |  | ||||||
| @@ -868,6 +888,7 @@ func GetActionIssue(ctx *context.Context) *models.Issue { | |||||||
| 		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 | ||||||
| @@ -1049,7 +1070,7 @@ func UpdateIssueStatus(ctx *context.Context) { | |||||||
| 	} | 	} | ||||||
| 	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", | ||||||
| @@ -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)) | 				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) { | ||||||
|   | |||||||
| @@ -223,6 +223,10 @@ func checkPullInfo(ctx *context.Context) *models.Issue { | |||||||
| 		} | 		} | ||||||
| 		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 | ||||||
|  |  | ||||||
| @@ -231,6 +235,11 @@ func checkPullInfo(ctx *context.Context) *models.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 | ||||||
| @@ -519,16 +528,7 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pr, err := models.GetPullRequestByIssueID(issue.ID) | 	pr := issue.PullRequest | ||||||
| 	if err != nil { |  | ||||||
| 		if models.IsErrPullRequestNotExist(err) { |  | ||||||
| 			ctx.NotFound("GetPullRequestByIssueID", nil) |  | ||||||
| 		} else { |  | ||||||
| 			ctx.ServerError("GetPullRequestByIssueID", err) |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	pr.Issue = issue |  | ||||||
|  |  | ||||||
| 	if !pr.CanAutoMerge() || pr.HasMerged { | 	if !pr.CanAutoMerge() || pr.HasMerged { | ||||||
| 		ctx.NotFound("MergePullRequest", nil) | 		ctx.NotFound("MergePullRequest", nil) | ||||||
| @@ -949,15 +949,7 @@ func CleanUpPullRequest(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pr, err := models.GetPullRequestByIssueID(issue.ID) | 	pr := issue.PullRequest | ||||||
| 	if err != nil { |  | ||||||
| 		if models.IsErrPullRequestNotExist(err) { |  | ||||||
| 			ctx.NotFound("GetPullRequestByIssueID", nil) |  | ||||||
| 		} else { |  | ||||||
| 			ctx.ServerError("GetPullRequestByIssueID", err) |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Allow cleanup only for merged PR | 	// Allow cleanup only for merged PR | ||||||
| 	if !pr.HasMerged { | 	if !pr.HasMerged { | ||||||
| @@ -965,7 +957,7 @@ func CleanUpPullRequest(ctx *context.Context) { | |||||||
| 		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 { | ||||||
| @@ -1077,8 +1069,12 @@ func DownloadPullDiff(ctx *context.Context) { | |||||||
| 		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 | ||||||
| @@ -1111,8 +1107,12 @@ func DownloadPullPatch(ctx *context.Context) { | |||||||
| 		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 | ||||||
|   | |||||||
| @@ -138,6 +138,7 @@ func Dashboard(ctx *context.Context) { | |||||||
| 		OnlyPerformedBy: false, | 		OnlyPerformedBy: false, | ||||||
| 		IncludeDeleted:  false, | 		IncludeDeleted:  false, | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	if ctx.Written() { | 	if ctx.Written() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user