Skip to content

Commit 3fd15ae

Browse files
authored
Add cache for dashbaord commit status (#29932)
backport #29444
1 parent 0873088 commit 3fd15ae

File tree

4 files changed

+148
-66
lines changed

4 files changed

+148
-66
lines changed

routers/api/v1/repo/status.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"code.gitea.io/gitea/modules/web"
1414
"code.gitea.io/gitea/routers/api/v1/utils"
1515
"code.gitea.io/gitea/services/convert"
16-
files_service "code.gitea.io/gitea/services/repository/files"
16+
commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
1717
)
1818

1919
// NewCommitStatus creates a new CommitStatus
@@ -63,7 +63,7 @@ func NewCommitStatus(ctx *context.APIContext) {
6363
Description: form.Description,
6464
Context: form.Context,
6565
}
66-
if err := files_service.CreateCommitStatus(ctx, ctx.Repo.Repository, ctx.Doer, sha, status); err != nil {
66+
if err := commitstatus_service.CreateCommitStatus(ctx, ctx.Repo.Repository, ctx.Doer, sha, status); err != nil {
6767
ctx.Error(http.StatusInternalServerError, "CreateCommitStatus", err)
6868
return
6969
}

routers/web/repo/repo.go

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"code.gitea.io/gitea/services/forms"
3535
repo_service "code.gitea.io/gitea/services/repository"
3636
archiver_service "code.gitea.io/gitea/services/repository/archiver"
37+
commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
3738
)
3839

3940
const (
@@ -596,30 +597,14 @@ func SearchRepo(ctx *context.Context) {
596597

597598
ctx.SetTotalCountHeader(count)
598599

599-
// collect the latest commit of each repo
600-
// at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment
601-
repoBranchNames := make(map[int64]string, len(repos))
602-
for _, repo := range repos {
603-
repoBranchNames[repo.ID] = repo.DefaultBranch
604-
}
605-
606-
repoIDsToLatestCommitSHAs, err := git_model.FindBranchesByRepoAndBranchName(ctx, repoBranchNames)
600+
latestCommitStatuses, err := commitstatus_service.FindReposLastestCommitStatuses(ctx, repos)
607601
if err != nil {
608-
log.Error("FindBranchesByRepoAndBranchName: %v", err)
609-
return
610-
}
611-
612-
// call the database O(1) times to get the commit statuses for all repos
613-
repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptions{})
614-
if err != nil {
615-
log.Error("GetLatestCommitStatusForPairs: %v", err)
602+
log.Error("FindReposLastestCommitStatuses: %v", err)
616603
return
617604
}
618605

619606
results := make([]*repo_service.WebSearchRepository, len(repos))
620607
for i, repo := range repos {
621-
latestCommitStatus := git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
622-
623608
results[i] = &repo_service.WebSearchRepository{
624609
Repository: &api.Repository{
625610
ID: repo.ID,
@@ -633,8 +618,11 @@ func SearchRepo(ctx *context.Context) {
633618
Link: repo.Link(),
634619
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
635620
},
636-
LatestCommitStatus: latestCommitStatus,
637-
LocaleLatestCommitStatus: latestCommitStatus.LocaleString(ctx.Locale),
621+
}
622+
623+
if latestCommitStatuses[i] != nil {
624+
results[i].LatestCommitStatus = latestCommitStatuses[i]
625+
results[i].LocaleLatestCommitStatus = latestCommitStatuses[i].LocaleString(ctx.Locale)
638626
}
639627
}
640628

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package commitstatus
5+
6+
import (
7+
"context"
8+
"crypto/sha256"
9+
"fmt"
10+
11+
"code.gitea.io/gitea/models/db"
12+
git_model "code.gitea.io/gitea/models/git"
13+
repo_model "code.gitea.io/gitea/models/repo"
14+
user_model "code.gitea.io/gitea/models/user"
15+
"code.gitea.io/gitea/modules/cache"
16+
"code.gitea.io/gitea/modules/git"
17+
"code.gitea.io/gitea/modules/log"
18+
api "code.gitea.io/gitea/modules/structs"
19+
"code.gitea.io/gitea/services/automerge"
20+
)
21+
22+
func getCacheKey(repoID int64, brancheName string) string {
23+
hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%d:%s", repoID, brancheName)))
24+
return fmt.Sprintf("commit_status:%x", hashBytes)
25+
}
26+
27+
func updateCommitStatusCache(ctx context.Context, repoID int64, branchName string, status api.CommitStatusState) error {
28+
c := cache.GetCache()
29+
if c == nil {
30+
return nil
31+
}
32+
return c.Put(getCacheKey(repoID, branchName), string(status), 3*24*60)
33+
}
34+
35+
func deleteCommitStatusCache(ctx context.Context, repoID int64, branchName string) error {
36+
c := cache.GetCache()
37+
if c == nil {
38+
return nil
39+
}
40+
return c.Delete(getCacheKey(repoID, branchName))
41+
}
42+
43+
// CreateCommitStatus creates a new CommitStatus given a bunch of parameters
44+
// NOTE: All text-values will be trimmed from whitespaces.
45+
// Requires: Repo, Creator, SHA
46+
func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creator *user_model.User, sha string, status *git_model.CommitStatus) error {
47+
repoPath := repo.RepoPath()
48+
49+
// confirm that commit is exist
50+
gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repoPath)
51+
if err != nil {
52+
return fmt.Errorf("OpenRepository[%s]: %w", repoPath, err)
53+
}
54+
defer closer.Close()
55+
56+
commit, err := gitRepo.GetCommit(sha)
57+
if err != nil {
58+
return fmt.Errorf("GetCommit[%s]: %w", sha, err)
59+
}
60+
61+
// use complete commit sha
62+
sha = commit.ID.String()
63+
64+
if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
65+
Repo: repo,
66+
Creator: creator,
67+
SHA: sha,
68+
CommitStatus: status,
69+
}); err != nil {
70+
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
71+
}
72+
73+
defaultBranchCommit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
74+
if err != nil {
75+
return fmt.Errorf("GetBranchCommit[%s]: %w", repo.DefaultBranch, err)
76+
}
77+
78+
if commit.ID.String() == defaultBranchCommit.ID.String() { // since one commit status updated, the combined commit status should be invalid
79+
if err := deleteCommitStatusCache(ctx, repo.ID, repo.DefaultBranch); err != nil {
80+
log.Error("deleteCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
81+
}
82+
}
83+
84+
if status.State.IsSuccess() {
85+
if err := automerge.MergeScheduledPullRequest(ctx, sha, repo); err != nil {
86+
return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
87+
}
88+
}
89+
90+
return nil
91+
}
92+
93+
// FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache
94+
func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
95+
results := make([]*git_model.CommitStatus, len(repos))
96+
c := cache.GetCache()
97+
if c != nil {
98+
for i, repo := range repos {
99+
status, ok := c.Get(getCacheKey(repo.ID, repo.DefaultBranch)).(string)
100+
if ok && status != "" {
101+
results[i] = &git_model.CommitStatus{State: api.CommitStatusState(status)}
102+
}
103+
}
104+
}
105+
106+
// collect the latest commit of each repo
107+
// at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment
108+
repoBranchNames := make(map[int64]string, len(repos))
109+
for i, repo := range repos {
110+
if results[i] == nil {
111+
repoBranchNames[repo.ID] = repo.DefaultBranch
112+
}
113+
}
114+
115+
repoIDsToLatestCommitSHAs, err := git_model.FindBranchesByRepoAndBranchName(ctx, repoBranchNames)
116+
if err != nil {
117+
return nil, fmt.Errorf("FindBranchesByRepoAndBranchName: %v", err)
118+
}
119+
120+
// call the database O(1) times to get the commit statuses for all repos
121+
repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptions{ListAll: true})
122+
if err != nil {
123+
return nil, fmt.Errorf("GetLatestCommitStatusForPairs: %v", err)
124+
}
125+
126+
for i, repo := range repos {
127+
if results[i] == nil {
128+
results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
129+
if results[i].State != "" {
130+
if err := updateCommitStatusCache(ctx, repo.ID, repo.DefaultBranch, results[i].State); err != nil {
131+
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
132+
}
133+
}
134+
}
135+
}
136+
137+
return results, nil
138+
}

services/repository/files/commit.go

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,13 @@ package files
55

66
import (
77
"context"
8-
"fmt"
98

109
asymkey_model "code.gitea.io/gitea/models/asymkey"
11-
git_model "code.gitea.io/gitea/models/git"
1210
repo_model "code.gitea.io/gitea/models/repo"
13-
user_model "code.gitea.io/gitea/models/user"
1411
"code.gitea.io/gitea/modules/git"
1512
"code.gitea.io/gitea/modules/structs"
16-
"code.gitea.io/gitea/services/automerge"
1713
)
1814

19-
// CreateCommitStatus creates a new CommitStatus given a bunch of parameters
20-
// NOTE: All text-values will be trimmed from whitespaces.
21-
// Requires: Repo, Creator, SHA
22-
func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creator *user_model.User, sha string, status *git_model.CommitStatus) error {
23-
repoPath := repo.RepoPath()
24-
25-
// confirm that commit is exist
26-
gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.RepoPath())
27-
if err != nil {
28-
return fmt.Errorf("OpenRepository[%s]: %w", repoPath, err)
29-
}
30-
defer closer.Close()
31-
32-
if commit, err := gitRepo.GetCommit(sha); err != nil {
33-
gitRepo.Close()
34-
return fmt.Errorf("GetCommit[%s]: %w", sha, err)
35-
} else if len(sha) != git.SHAFullLength {
36-
// use complete commit sha
37-
sha = commit.ID.String()
38-
}
39-
gitRepo.Close()
40-
41-
if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
42-
Repo: repo,
43-
Creator: creator,
44-
SHA: sha,
45-
CommitStatus: status,
46-
}); err != nil {
47-
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
48-
}
49-
50-
if status.State.IsSuccess() {
51-
if err := automerge.MergeScheduledPullRequest(ctx, sha, repo); err != nil {
52-
return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
53-
}
54-
}
55-
56-
return nil
57-
}
58-
5915
// CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch
6016
func CountDivergingCommits(ctx context.Context, repo *repo_model.Repository, branch string) (*git.DivergeObject, error) {
6117
divergence, err := git.GetDivergingCommits(ctx, repo.RepoPath(), repo.DefaultBranch, branch)

0 commit comments

Comments
 (0)