Skip to content

Commit 6ef986d

Browse files
authored
Performance improvements for pull request list page (#29900) (#29972)
This PR will avoid load pullrequest.Issue twice in pull request list page. It will reduce x times database queries for those WIP pull requests. Partially fix #29585 Backport #29900
1 parent c03b1e2 commit 6ef986d

File tree

14 files changed

+85
-49
lines changed

14 files changed

+85
-49
lines changed

models/activities/notification.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"code.gitea.io/gitea/modules/log"
2121
"code.gitea.io/gitea/modules/setting"
2222
"code.gitea.io/gitea/modules/timeutil"
23+
"code.gitea.io/gitea/modules/util"
2324

2425
"xorm.io/builder"
2526
"xorm.io/xorm"
@@ -821,3 +822,31 @@ func UpdateNotificationStatuses(ctx context.Context, user *user_model.User, curr
821822
Update(n)
822823
return err
823824
}
825+
826+
// LoadIssuePullRequests loads all issues' pull requests if possible
827+
func (nl NotificationList) LoadIssuePullRequests(ctx context.Context) error {
828+
issues := make(map[int64]*issues_model.Issue, len(nl))
829+
for _, notification := range nl {
830+
if notification.Issue != nil && notification.Issue.IsPull && notification.Issue.PullRequest == nil {
831+
issues[notification.Issue.ID] = notification.Issue
832+
}
833+
}
834+
835+
if len(issues) == 0 {
836+
return nil
837+
}
838+
839+
pulls, err := issues_model.GetPullRequestByIssueIDs(ctx, util.KeysOfMap(issues))
840+
if err != nil {
841+
return err
842+
}
843+
844+
for _, pull := range pulls {
845+
if issue := issues[pull.IssueID]; issue != nil {
846+
issue.PullRequest = pull
847+
issue.PullRequest.Issue = issue
848+
}
849+
}
850+
851+
return nil
852+
}

models/issues/issue.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -200,20 +200,6 @@ func (issue *Issue) IsTimetrackerEnabled(ctx context.Context) bool {
200200
return issue.Repo.IsTimetrackerEnabled(ctx)
201201
}
202202

203-
// GetPullRequest returns the issue pull request
204-
func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) {
205-
if !issue.IsPull {
206-
return nil, fmt.Errorf("Issue is not a pull request")
207-
}
208-
209-
pr, err = GetPullRequestByIssueID(db.DefaultContext, issue.ID)
210-
if err != nil {
211-
return nil, err
212-
}
213-
pr.Issue = issue
214-
return pr, err
215-
}
216-
217203
// LoadPoster loads poster
218204
func (issue *Issue) LoadPoster(ctx context.Context) (err error) {
219205
if issue.Poster == nil && issue.PosterID != 0 {

models/issues/issue_list.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,9 @@ func (issues IssueList) LoadPullRequests(ctx context.Context) error {
370370

371371
for _, issue := range issues {
372372
issue.PullRequest = pullRequestMaps[issue.ID]
373+
if issue.PullRequest != nil {
374+
issue.PullRequest.Issue = issue
375+
}
373376
}
374377
return nil
375378
}

models/issues/pull_list.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,12 @@ func HasMergedPullRequestInRepo(ctx context.Context, repoID, posterID int64) (bo
212212
Limit(1).
213213
Get(new(Issue))
214214
}
215+
216+
// GetPullRequestByIssueIDs returns all pull requests by issue ids
217+
func GetPullRequestByIssueIDs(ctx context.Context, issueIDs []int64) (PullRequestList, error) {
218+
prs := make([]*PullRequest, 0, len(issueIDs))
219+
return prs, db.GetEngine(ctx).
220+
Where("issue_id > 0").
221+
In("issue_id", issueIDs).
222+
Find(&prs)
223+
}

models/issues/review.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,11 +240,11 @@ type CreateReviewOptions struct {
240240

241241
// IsOfficialReviewer check if at least one of the provided reviewers can make official reviews in issue (counts towards required approvals)
242242
func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewer *user_model.User) (bool, error) {
243-
pr, err := GetPullRequestByIssueID(ctx, issue.ID)
244-
if err != nil {
243+
if err := issue.LoadPullRequest(ctx); err != nil {
245244
return false, err
246245
}
247246

247+
pr := issue.PullRequest
248248
rule, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
249249
if err != nil {
250250
return false, err
@@ -272,11 +272,10 @@ func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewer *user_model.
272272

273273
// IsOfficialReviewerTeam check if reviewer in this team can make official reviews in issue (counts towards required approvals)
274274
func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organization.Team) (bool, error) {
275-
pr, err := GetPullRequestByIssueID(ctx, issue.ID)
276-
if err != nil {
275+
if err := issue.LoadPullRequest(ctx); err != nil {
277276
return false, err
278277
}
279-
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
278+
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, issue.PullRequest.BaseRepoID, issue.PullRequest.BaseBranch)
280279
if err != nil {
281280
return false, err
282281
}

modules/util/slice.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,12 @@ func SliceSortedEqual[T comparable](s1, s2 []T) bool {
4545
func SliceRemoveAll[T comparable](slice []T, target T) []T {
4646
return slices.DeleteFunc(slice, func(t T) bool { return t == target })
4747
}
48+
49+
// TODO: Replace with "maps.Keys" once available, current it only in golang.org/x/exp/maps but not in standard library
50+
func KeysOfMap[K comparable, V any](m map[K]V) []K {
51+
keys := make([]K, 0, len(m))
52+
for k := range m {
53+
keys = append(keys, k)
54+
}
55+
return keys
56+
}

routers/api/v1/repo/issue.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -864,10 +864,11 @@ func EditIssue(ctx *context.APIContext) {
864864
}
865865
if form.State != nil {
866866
if issue.IsPull {
867-
if pr, err := issue.GetPullRequest(); err != nil {
867+
if err := issue.LoadPullRequest(ctx); err != nil {
868868
ctx.Error(http.StatusInternalServerError, "GetPullRequest", err)
869869
return
870-
} else if pr.HasMerged {
870+
}
871+
if issue.PullRequest.HasMerged {
871872
ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
872873
return
873874
}

routers/api/v1/repo/issue_pin.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -240,18 +240,12 @@ func ListPinnedPullRequests(ctx *context.APIContext) {
240240
}
241241

242242
apiPrs := make([]*api.PullRequest, len(issues))
243+
if err := issues.LoadPullRequests(ctx); err != nil {
244+
ctx.Error(http.StatusInternalServerError, "LoadPullRequests", err)
245+
return
246+
}
243247
for i, currentIssue := range issues {
244-
pr, err := currentIssue.GetPullRequest()
245-
if err != nil {
246-
ctx.Error(http.StatusInternalServerError, "GetPullRequest", err)
247-
return
248-
}
249-
250-
if err = pr.LoadIssue(ctx); err != nil {
251-
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
252-
return
253-
}
254-
248+
pr := currentIssue.PullRequest
255249
if err = pr.LoadAttributes(ctx); err != nil {
256250
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
257251
return

routers/web/user/notification.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ func getNotifications(ctx *context.Context) {
128128
ctx.ServerError("LoadIssues", err)
129129
return
130130
}
131+
132+
if err = notifications.LoadIssuePullRequests(ctx); err != nil {
133+
ctx.ServerError("LoadIssuePullRequests", err)
134+
return
135+
}
136+
131137
notifications = notifications.Without(failures)
132138
failCount += len(failures)
133139

services/convert/notification.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ func ToNotificationThread(ctx context.Context, n *activities_model.Notification)
6161
result.Subject.LatestCommentHTMLURL = comment.HTMLURL(ctx)
6262
}
6363

64-
pr, _ := n.Issue.GetPullRequest()
65-
if pr != nil && pr.HasMerged {
64+
if err := n.Issue.LoadPullRequest(ctx); err == nil &&
65+
n.Issue.PullRequest != nil &&
66+
n.Issue.PullRequest.HasMerged {
6667
result.Subject.State = "merged"
6768
}
6869
}

services/pull/review.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,11 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo
264264

265265
// SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
266266
func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue, reviewType issues_model.ReviewType, content, commitID string, attachmentUUIDs []string) (*issues_model.Review, *issues_model.Comment, error) {
267-
pr, err := issue.GetPullRequest()
268-
if err != nil {
267+
if err := issue.LoadPullRequest(ctx); err != nil {
269268
return nil, nil, err
270269
}
271270

271+
pr := issue.PullRequest
272272
var stale bool
273273
if reviewType != issues_model.ReviewTypeApprove && reviewType != issues_model.ReviewTypeReject {
274274
stale = false

templates/shared/issueicon.tmpl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{{if .IsPull}}
2-
{{if and .PullRequest .PullRequest.HasMerged}}
3-
{{svg "octicon-git-merge" 16 "text purple"}}
4-
{{else if and .GetPullRequest .GetPullRequest.HasMerged}}
5-
{{svg "octicon-git-merge" 16 "text purple"}}
2+
{{if not .PullRequest}}
3+
No PullRequest
64
{{else}}
75
{{if .IsClosed}}
8-
{{svg "octicon-git-pull-request" 16 "text red"}}
6+
{{if .PullRequest.HasMerged}}
7+
{{svg "octicon-git-merge" 16 "text purple"}}
8+
{{else}}
9+
{{svg "octicon-git-pull-request" 16 "text red"}}
10+
{{end}}
911
{{else}}
10-
{{if and .PullRequest (.PullRequest.IsWorkInProgress ctx)}}
11-
{{svg "octicon-git-pull-request-draft" 16 "text grey"}}
12-
{{else if and .GetPullRequest (.GetPullRequest.IsWorkInProgress ctx)}}
12+
{{if .PullRequest.IsWorkInProgress ctx}}
1313
{{svg "octicon-git-pull-request-draft" 16 "text grey"}}
1414
{{else}}
1515
{{svg "octicon-git-pull-request" 16 "text green"}}

tests/integration/pull_merge_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,8 @@ func TestConflictChecking(t *testing.T) {
424424
assert.NoError(t, err)
425425

426426
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: "PR with conflict!"})
427-
conflictingPR, err := issues_model.GetPullRequestByIssueID(db.DefaultContext, issue.ID)
428-
assert.NoError(t, err)
427+
assert.NoError(t, issue.LoadPullRequest(db.DefaultContext))
428+
conflictingPR := issue.PullRequest
429429

430430
// Ensure conflictedFiles is populated.
431431
assert.Len(t, conflictingPR.ConflictedFiles, 1)

tests/integration/pull_update_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,7 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_mod
175175
assert.NoError(t, err)
176176

177177
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: "Test Pull -to-update-"})
178-
pr, err := issues_model.GetPullRequestByIssueID(db.DefaultContext, issue.ID)
179-
assert.NoError(t, err)
178+
assert.NoError(t, issue.LoadPullRequest(db.DefaultContext))
180179

181-
return pr
180+
return issue.PullRequest
182181
}

0 commit comments

Comments
 (0)