Skip to content

Commit 4a5af4e

Browse files
lunnywxiaoguang
andauthored
Cache GPG keys, emails and users when list commits (#34086)
When list commits, some of the commits authors are the same at many situations. But current logic will always fetch the same GPG keys from database. This PR will cache the GPG keys, emails and users for the context so that reducing the database queries. --------- Co-authored-by: wxiaoguang <[email protected]>
1 parent f8edc29 commit 4a5af4e

File tree

10 files changed

+65
-48
lines changed

10 files changed

+65
-48
lines changed

models/asymkey/gpg_key.go

+7
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,10 @@ func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err err
240240

241241
return committer.Commit()
242242
}
243+
244+
func FindGPGKeyWithSubKeys(ctx context.Context, keyID string) ([]*GPGKey, error) {
245+
return db.Find[GPGKey](ctx, FindGPGKeyOptions{
246+
KeyID: keyID,
247+
IncludeSubKeys: true,
248+
})
249+
}

models/user/user.go

+13-14
Original file line numberDiff line numberDiff line change
@@ -1187,29 +1187,28 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
11871187
for _, email := range emailAddresses {
11881188
userIDs.Add(email.UID)
11891189
}
1190-
users, err := GetUsersMapByIDs(ctx, userIDs.Values())
1191-
if err != nil {
1192-
return nil, err
1193-
}
1194-
11951190
results := make(map[string]*User, len(emails))
1196-
for _, email := range emailAddresses {
1197-
user := users[email.UID]
1198-
if user != nil {
1199-
if user.KeepEmailPrivate {
1200-
results[user.LowerName+"@"+setting.Service.NoReplyAddress] = user
1201-
} else {
1202-
results[email.Email] = user
1191+
1192+
if len(userIDs) > 0 {
1193+
users, err := GetUsersMapByIDs(ctx, userIDs.Values())
1194+
if err != nil {
1195+
return nil, err
1196+
}
1197+
1198+
for _, email := range emailAddresses {
1199+
user := users[email.UID]
1200+
if user != nil {
1201+
results[user.GetEmail()] = user
12031202
}
12041203
}
12051204
}
12061205

1207-
users = make(map[int64]*User, len(needCheckUserNames))
1206+
users := make(map[int64]*User, len(needCheckUserNames))
12081207
if err := db.GetEngine(ctx).In("lower_name", needCheckUserNames.Values()).Find(&users); err != nil {
12091208
return nil, err
12101209
}
12111210
for _, user := range users {
1212-
results[user.LowerName+"@"+setting.Service.NoReplyAddress] = user
1211+
results[user.GetPlaceholderEmail()] = user
12131212
}
12141213
return results, nil
12151214
}

modules/cache/context.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,15 @@ func RemoveContextData(ctx context.Context, tp, key any) {
166166
}
167167

168168
// GetWithContextCache returns the cache value of the given key in the given context.
169-
func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) {
170-
v := GetContextData(ctx, cacheGroupKey, cacheTargetID)
169+
func GetWithContextCache[T, K any](ctx context.Context, groupKey string, targetKey K, f func(context.Context, K) (T, error)) (T, error) {
170+
v := GetContextData(ctx, groupKey, targetKey)
171171
if vv, ok := v.(T); ok {
172172
return vv, nil
173173
}
174-
t, err := f()
174+
t, err := f(ctx, targetKey)
175175
if err != nil {
176176
return t, err
177177
}
178-
SetContextData(ctx, cacheGroupKey, cacheTargetID, t)
178+
SetContextData(ctx, groupKey, targetKey, t)
179179
return t, nil
180180
}

modules/cache/context_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package cache
55

66
import (
7+
"context"
78
"testing"
89
"time"
910

@@ -30,7 +31,7 @@ func TestWithCacheContext(t *testing.T) {
3031
v = GetContextData(ctx, field, "my_config1")
3132
assert.Nil(t, v)
3233

33-
vInt, err := GetWithContextCache(ctx, field, "my_config1", func() (int, error) {
34+
vInt, err := GetWithContextCache(ctx, field, "my_config1", func(context.Context, string) (int, error) {
3435
return 1, nil
3536
})
3637
assert.NoError(t, err)

modules/cachegroup/cachegroup.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package cachegroup
5+
6+
const (
7+
User = "user"
8+
EmailAvatarLink = "email_avatar_link"
9+
UserEmailAddresses = "user_email_addresses"
10+
GPGKeyWithSubKeys = "gpg_key_with_subkeys"
11+
RepoUserPermission = "repo_user_permission"
12+
)

modules/repository/commits.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
repo_model "code.gitea.io/gitea/models/repo"
1414
user_model "code.gitea.io/gitea/models/user"
1515
"code.gitea.io/gitea/modules/cache"
16+
"code.gitea.io/gitea/modules/cachegroup"
1617
"code.gitea.io/gitea/modules/git"
1718
"code.gitea.io/gitea/modules/log"
1819
"code.gitea.io/gitea/modules/setting"
@@ -131,7 +132,7 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repo *repo_model
131132
func (pc *PushCommits) AvatarLink(ctx context.Context, email string) string {
132133
size := avatars.DefaultAvatarPixelSize * setting.Avatar.RenderedSizeFactor
133134

134-
v, _ := cache.GetWithContextCache(ctx, "push_commits", email, func() (string, error) {
135+
v, _ := cache.GetWithContextCache(ctx, cachegroup.EmailAvatarLink, email, func(ctx context.Context, email string) (string, error) {
135136
u, err := user_model.GetUserByEmail(ctx, email)
136137
if err != nil {
137138
if !user_model.IsErrUserNotExist(err) {

routers/private/hook_post_receive.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
repo_model "code.gitea.io/gitea/models/repo"
1515
user_model "code.gitea.io/gitea/models/user"
1616
"code.gitea.io/gitea/modules/cache"
17+
"code.gitea.io/gitea/modules/cachegroup"
1718
"code.gitea.io/gitea/modules/git"
1819
"code.gitea.io/gitea/modules/gitrepo"
1920
"code.gitea.io/gitea/modules/log"
@@ -326,9 +327,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
326327
}
327328

328329
func loadContextCacheUser(ctx context.Context, id int64) (*user_model.User, error) {
329-
return cache.GetWithContextCache(ctx, "hook_post_receive_user", id, func() (*user_model.User, error) {
330-
return user_model.GetUserByID(ctx, id)
331-
})
330+
return cache.GetWithContextCache(ctx, cachegroup.User, id, user_model.GetUserByID)
332331
}
333332

334333
// handlePullRequestMerging handle pull request merging, a pull request action should push at least 1 commit

services/asymkey/commit.go

+10-15
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
asymkey_model "code.gitea.io/gitea/models/asymkey"
1212
"code.gitea.io/gitea/models/db"
1313
user_model "code.gitea.io/gitea/models/user"
14+
"code.gitea.io/gitea/modules/cache"
15+
"code.gitea.io/gitea/modules/cachegroup"
1416
"code.gitea.io/gitea/modules/git"
1517
"code.gitea.io/gitea/modules/log"
1618
"code.gitea.io/gitea/modules/setting"
@@ -115,7 +117,7 @@ func ParseCommitWithSignatureCommitter(ctx context.Context, c *git.Commit, commi
115117
}
116118
}
117119

118-
committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
120+
committerEmailAddresses, _ := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses)
119121
activated := false
120122
for _, e := range committerEmailAddresses {
121123
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
@@ -209,10 +211,9 @@ func checkKeyEmails(ctx context.Context, email string, keys ...*asymkey_model.GP
209211
}
210212
if key.Verified && key.OwnerID != 0 {
211213
if uid != key.OwnerID {
212-
userEmails, _ = user_model.GetEmailAddresses(ctx, key.OwnerID)
214+
userEmails, _ = cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, key.OwnerID, user_model.GetEmailAddresses)
213215
uid = key.OwnerID
214-
user = &user_model.User{ID: uid}
215-
_, _ = user_model.GetUser(ctx, user)
216+
user, _ = cache.GetWithContextCache(ctx, cachegroup.User, uid, user_model.GetUserByID)
216217
}
217218
for _, e := range userEmails {
218219
if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) {
@@ -231,10 +232,7 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s
231232
if keyID == "" {
232233
return nil
233234
}
234-
keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
235-
KeyID: keyID,
236-
IncludeSubKeys: true,
237-
})
235+
keys, err := cache.GetWithContextCache(ctx, cachegroup.GPGKeyWithSubKeys, keyID, asymkey_model.FindGPGKeyWithSubKeys)
238236
if err != nil {
239237
log.Error("GetGPGKeysByKeyID: %v", err)
240238
return &asymkey_model.CommitVerification{
@@ -249,10 +247,7 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s
249247
for _, key := range keys {
250248
var primaryKeys []*asymkey_model.GPGKey
251249
if key.PrimaryKeyID != "" {
252-
primaryKeys, err = db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
253-
KeyID: key.PrimaryKeyID,
254-
IncludeSubKeys: true,
255-
})
250+
primaryKeys, err = cache.GetWithContextCache(ctx, cachegroup.GPGKeyWithSubKeys, key.PrimaryKeyID, asymkey_model.FindGPGKeyWithSubKeys)
256251
if err != nil {
257252
log.Error("GetGPGKeysByKeyID: %v", err)
258253
return &asymkey_model.CommitVerification{
@@ -272,8 +267,8 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s
272267
Name: name,
273268
Email: email,
274269
}
275-
if key.OwnerID != 0 {
276-
owner, err := user_model.GetUserByID(ctx, key.OwnerID)
270+
if key.OwnerID > 0 {
271+
owner, err := cache.GetWithContextCache(ctx, cachegroup.User, key.OwnerID, user_model.GetUserByID)
277272
if err == nil {
278273
signer = owner
279274
} else if !user_model.IsErrUserNotExist(err) {
@@ -381,7 +376,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
381376
}
382377
}
383378

384-
committerEmailAddresses, err := user_model.GetEmailAddresses(ctx, committer.ID)
379+
committerEmailAddresses, err := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses)
385380
if err != nil {
386381
log.Error("GetEmailAddresses: %v", err)
387382
}

services/convert/pull.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
repo_model "code.gitea.io/gitea/models/repo"
1515
user_model "code.gitea.io/gitea/models/user"
1616
"code.gitea.io/gitea/modules/cache"
17+
"code.gitea.io/gitea/modules/cachegroup"
1718
"code.gitea.io/gitea/modules/git"
1819
"code.gitea.io/gitea/modules/gitrepo"
1920
"code.gitea.io/gitea/modules/log"
@@ -60,14 +61,14 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
6061
doerID = doer.ID
6162
}
6263

63-
const repoDoerPermCacheKey = "repo_doer_perm_cache"
64-
p, err := cache.GetWithContextCache(ctx, repoDoerPermCacheKey, fmt.Sprintf("%d_%d", pr.BaseRepoID, doerID),
65-
func() (access_model.Permission, error) {
64+
repoUserPerm, err := cache.GetWithContextCache(ctx, cachegroup.RepoUserPermission, fmt.Sprintf("%d-%d", pr.BaseRepoID, doerID),
65+
func(ctx context.Context, _ string) (access_model.Permission, error) {
6666
return access_model.GetUserRepoPermission(ctx, pr.BaseRepo, doer)
67-
})
67+
},
68+
)
6869
if err != nil {
6970
log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err)
70-
p.AccessMode = perm.AccessModeNone
71+
repoUserPerm.AccessMode = perm.AccessModeNone
7172
}
7273

7374
apiPullRequest := &api.PullRequest{
@@ -107,7 +108,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
107108
Name: pr.BaseBranch,
108109
Ref: pr.BaseBranch,
109110
RepoID: pr.BaseRepoID,
110-
Repository: ToRepo(ctx, pr.BaseRepo, p),
111+
Repository: ToRepo(ctx, pr.BaseRepo, repoUserPerm),
111112
},
112113
Head: &api.PRBranchInfo{
113114
Name: pr.HeadBranch,

services/git/commit.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
)
1818

1919
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
20-
func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) ([]*asymkey_model.SignCommit, error) {
20+
func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType) ([]*asymkey_model.SignCommit, error) {
2121
newCommits := make([]*asymkey_model.SignCommit, 0, len(oldCommits))
2222
keyMap := map[string]bool{}
2323

@@ -47,6 +47,10 @@ func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.Use
4747
Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.Commit, committer),
4848
}
4949

50+
isOwnerMemberCollaborator := func(user *user_model.User) (bool, error) {
51+
return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID)
52+
}
53+
5054
_ = asymkey_model.CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
5155

5256
newCommits = append(newCommits, signCommit)
@@ -62,11 +66,9 @@ func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo
6266
}
6367
signedCommits, err := ParseCommitsWithSignature(
6468
ctx,
69+
repo,
6570
validatedCommits,
6671
repo.GetTrustModel(),
67-
func(user *user_model.User) (bool, error) {
68-
return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID)
69-
},
7072
)
7173
if err != nil {
7274
return nil, err

0 commit comments

Comments
 (0)