mirror of
https://github.com/go-gitea/gitea.git
synced 2025-04-23 01:48:47 +03:00
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 <wxiaoguang@gmail.com>
This commit is contained in:
@ -240,3 +240,10 @@ func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err err
|
|||||||
|
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FindGPGKeyWithSubKeys(ctx context.Context, keyID string) ([]*GPGKey, error) {
|
||||||
|
return db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
||||||
|
KeyID: keyID,
|
||||||
|
IncludeSubKeys: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -1187,29 +1187,28 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
|
|||||||
for _, email := range emailAddresses {
|
for _, email := range emailAddresses {
|
||||||
userIDs.Add(email.UID)
|
userIDs.Add(email.UID)
|
||||||
}
|
}
|
||||||
users, err := GetUsersMapByIDs(ctx, userIDs.Values())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
results := make(map[string]*User, len(emails))
|
results := make(map[string]*User, len(emails))
|
||||||
for _, email := range emailAddresses {
|
|
||||||
user := users[email.UID]
|
if len(userIDs) > 0 {
|
||||||
if user != nil {
|
users, err := GetUsersMapByIDs(ctx, userIDs.Values())
|
||||||
if user.KeepEmailPrivate {
|
if err != nil {
|
||||||
results[user.LowerName+"@"+setting.Service.NoReplyAddress] = user
|
return nil, err
|
||||||
} else {
|
}
|
||||||
results[email.Email] = user
|
|
||||||
|
for _, email := range emailAddresses {
|
||||||
|
user := users[email.UID]
|
||||||
|
if user != nil {
|
||||||
|
results[user.GetEmail()] = user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
users = make(map[int64]*User, len(needCheckUserNames))
|
users := make(map[int64]*User, len(needCheckUserNames))
|
||||||
if err := db.GetEngine(ctx).In("lower_name", needCheckUserNames.Values()).Find(&users); err != nil {
|
if err := db.GetEngine(ctx).In("lower_name", needCheckUserNames.Values()).Find(&users); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
results[user.LowerName+"@"+setting.Service.NoReplyAddress] = user
|
results[user.GetPlaceholderEmail()] = user
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
8
modules/cache/context.go
vendored
8
modules/cache/context.go
vendored
@ -166,15 +166,15 @@ func RemoveContextData(ctx context.Context, tp, key any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetWithContextCache returns the cache value of the given key in the given context.
|
// GetWithContextCache returns the cache value of the given key in the given context.
|
||||||
func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) {
|
func GetWithContextCache[T, K any](ctx context.Context, groupKey string, targetKey K, f func(context.Context, K) (T, error)) (T, error) {
|
||||||
v := GetContextData(ctx, cacheGroupKey, cacheTargetID)
|
v := GetContextData(ctx, groupKey, targetKey)
|
||||||
if vv, ok := v.(T); ok {
|
if vv, ok := v.(T); ok {
|
||||||
return vv, nil
|
return vv, nil
|
||||||
}
|
}
|
||||||
t, err := f()
|
t, err := f(ctx, targetKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return t, err
|
return t, err
|
||||||
}
|
}
|
||||||
SetContextData(ctx, cacheGroupKey, cacheTargetID, t)
|
SetContextData(ctx, groupKey, targetKey, t)
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
3
modules/cache/context_test.go
vendored
3
modules/cache/context_test.go
vendored
@ -4,6 +4,7 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ func TestWithCacheContext(t *testing.T) {
|
|||||||
v = GetContextData(ctx, field, "my_config1")
|
v = GetContextData(ctx, field, "my_config1")
|
||||||
assert.Nil(t, v)
|
assert.Nil(t, v)
|
||||||
|
|
||||||
vInt, err := GetWithContextCache(ctx, field, "my_config1", func() (int, error) {
|
vInt, err := GetWithContextCache(ctx, field, "my_config1", func(context.Context, string) (int, error) {
|
||||||
return 1, nil
|
return 1, nil
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
12
modules/cachegroup/cachegroup.go
Normal file
12
modules/cachegroup/cachegroup.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cachegroup
|
||||||
|
|
||||||
|
const (
|
||||||
|
User = "user"
|
||||||
|
EmailAvatarLink = "email_avatar_link"
|
||||||
|
UserEmailAddresses = "user_email_addresses"
|
||||||
|
GPGKeyWithSubKeys = "gpg_key_with_subkeys"
|
||||||
|
RepoUserPermission = "repo_user_permission"
|
||||||
|
)
|
@ -13,6 +13,7 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
|
"code.gitea.io/gitea/modules/cachegroup"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -131,7 +132,7 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repo *repo_model
|
|||||||
func (pc *PushCommits) AvatarLink(ctx context.Context, email string) string {
|
func (pc *PushCommits) AvatarLink(ctx context.Context, email string) string {
|
||||||
size := avatars.DefaultAvatarPixelSize * setting.Avatar.RenderedSizeFactor
|
size := avatars.DefaultAvatarPixelSize * setting.Avatar.RenderedSizeFactor
|
||||||
|
|
||||||
v, _ := cache.GetWithContextCache(ctx, "push_commits", email, func() (string, error) {
|
v, _ := cache.GetWithContextCache(ctx, cachegroup.EmailAvatarLink, email, func(ctx context.Context, email string) (string, error) {
|
||||||
u, err := user_model.GetUserByEmail(ctx, email)
|
u, err := user_model.GetUserByEmail(ctx, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !user_model.IsErrUserNotExist(err) {
|
if !user_model.IsErrUserNotExist(err) {
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
|
"code.gitea.io/gitea/modules/cachegroup"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@ -326,9 +327,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadContextCacheUser(ctx context.Context, id int64) (*user_model.User, error) {
|
func loadContextCacheUser(ctx context.Context, id int64) (*user_model.User, error) {
|
||||||
return cache.GetWithContextCache(ctx, "hook_post_receive_user", id, func() (*user_model.User, error) {
|
return cache.GetWithContextCache(ctx, cachegroup.User, id, user_model.GetUserByID)
|
||||||
return user_model.GetUserByID(ctx, id)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePullRequestMerging handle pull request merging, a pull request action should push at least 1 commit
|
// handlePullRequestMerging handle pull request merging, a pull request action should push at least 1 commit
|
||||||
|
@ -11,6 +11,8 @@ import (
|
|||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/cache"
|
||||||
|
"code.gitea.io/gitea/modules/cachegroup"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -115,7 +117,7 @@ func ParseCommitWithSignatureCommitter(ctx context.Context, c *git.Commit, commi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
|
committerEmailAddresses, _ := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses)
|
||||||
activated := false
|
activated := false
|
||||||
for _, e := range committerEmailAddresses {
|
for _, e := range committerEmailAddresses {
|
||||||
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
|
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
|
|||||||
}
|
}
|
||||||
if key.Verified && key.OwnerID != 0 {
|
if key.Verified && key.OwnerID != 0 {
|
||||||
if uid != key.OwnerID {
|
if uid != key.OwnerID {
|
||||||
userEmails, _ = user_model.GetEmailAddresses(ctx, key.OwnerID)
|
userEmails, _ = cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, key.OwnerID, user_model.GetEmailAddresses)
|
||||||
uid = key.OwnerID
|
uid = key.OwnerID
|
||||||
user = &user_model.User{ID: uid}
|
user, _ = cache.GetWithContextCache(ctx, cachegroup.User, uid, user_model.GetUserByID)
|
||||||
_, _ = user_model.GetUser(ctx, user)
|
|
||||||
}
|
}
|
||||||
for _, e := range userEmails {
|
for _, e := range userEmails {
|
||||||
if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) {
|
if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) {
|
||||||
@ -231,10 +232,7 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s
|
|||||||
if keyID == "" {
|
if keyID == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
|
keys, err := cache.GetWithContextCache(ctx, cachegroup.GPGKeyWithSubKeys, keyID, asymkey_model.FindGPGKeyWithSubKeys)
|
||||||
KeyID: keyID,
|
|
||||||
IncludeSubKeys: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetGPGKeysByKeyID: %v", err)
|
log.Error("GetGPGKeysByKeyID: %v", err)
|
||||||
return &asymkey_model.CommitVerification{
|
return &asymkey_model.CommitVerification{
|
||||||
@ -249,10 +247,7 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s
|
|||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
var primaryKeys []*asymkey_model.GPGKey
|
var primaryKeys []*asymkey_model.GPGKey
|
||||||
if key.PrimaryKeyID != "" {
|
if key.PrimaryKeyID != "" {
|
||||||
primaryKeys, err = db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
|
primaryKeys, err = cache.GetWithContextCache(ctx, cachegroup.GPGKeyWithSubKeys, key.PrimaryKeyID, asymkey_model.FindGPGKeyWithSubKeys)
|
||||||
KeyID: key.PrimaryKeyID,
|
|
||||||
IncludeSubKeys: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetGPGKeysByKeyID: %v", err)
|
log.Error("GetGPGKeysByKeyID: %v", err)
|
||||||
return &asymkey_model.CommitVerification{
|
return &asymkey_model.CommitVerification{
|
||||||
@ -272,8 +267,8 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s
|
|||||||
Name: name,
|
Name: name,
|
||||||
Email: email,
|
Email: email,
|
||||||
}
|
}
|
||||||
if key.OwnerID != 0 {
|
if key.OwnerID > 0 {
|
||||||
owner, err := user_model.GetUserByID(ctx, key.OwnerID)
|
owner, err := cache.GetWithContextCache(ctx, cachegroup.User, key.OwnerID, user_model.GetUserByID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
signer = owner
|
signer = owner
|
||||||
} else if !user_model.IsErrUserNotExist(err) {
|
} else if !user_model.IsErrUserNotExist(err) {
|
||||||
@ -381,7 +376,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
committerEmailAddresses, err := user_model.GetEmailAddresses(ctx, committer.ID)
|
committerEmailAddresses, err := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetEmailAddresses: %v", err)
|
log.Error("GetEmailAddresses: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
|
"code.gitea.io/gitea/modules/cachegroup"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@ -60,14 +61,14 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
|||||||
doerID = doer.ID
|
doerID = doer.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
const repoDoerPermCacheKey = "repo_doer_perm_cache"
|
repoUserPerm, err := cache.GetWithContextCache(ctx, cachegroup.RepoUserPermission, fmt.Sprintf("%d-%d", pr.BaseRepoID, doerID),
|
||||||
p, err := cache.GetWithContextCache(ctx, repoDoerPermCacheKey, fmt.Sprintf("%d_%d", pr.BaseRepoID, doerID),
|
func(ctx context.Context, _ string) (access_model.Permission, error) {
|
||||||
func() (access_model.Permission, error) {
|
|
||||||
return access_model.GetUserRepoPermission(ctx, pr.BaseRepo, doer)
|
return access_model.GetUserRepoPermission(ctx, pr.BaseRepo, doer)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err)
|
log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err)
|
||||||
p.AccessMode = perm.AccessModeNone
|
repoUserPerm.AccessMode = perm.AccessModeNone
|
||||||
}
|
}
|
||||||
|
|
||||||
apiPullRequest := &api.PullRequest{
|
apiPullRequest := &api.PullRequest{
|
||||||
@ -107,7 +108,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
|||||||
Name: pr.BaseBranch,
|
Name: pr.BaseBranch,
|
||||||
Ref: pr.BaseBranch,
|
Ref: pr.BaseBranch,
|
||||||
RepoID: pr.BaseRepoID,
|
RepoID: pr.BaseRepoID,
|
||||||
Repository: ToRepo(ctx, pr.BaseRepo, p),
|
Repository: ToRepo(ctx, pr.BaseRepo, repoUserPerm),
|
||||||
},
|
},
|
||||||
Head: &api.PRBranchInfo{
|
Head: &api.PRBranchInfo{
|
||||||
Name: pr.HeadBranch,
|
Name: pr.HeadBranch,
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
|
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
|
||||||
func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) ([]*asymkey_model.SignCommit, error) {
|
func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType) ([]*asymkey_model.SignCommit, error) {
|
||||||
newCommits := make([]*asymkey_model.SignCommit, 0, len(oldCommits))
|
newCommits := make([]*asymkey_model.SignCommit, 0, len(oldCommits))
|
||||||
keyMap := map[string]bool{}
|
keyMap := map[string]bool{}
|
||||||
|
|
||||||
@ -47,6 +47,10 @@ func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.Use
|
|||||||
Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.Commit, committer),
|
Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.Commit, committer),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isOwnerMemberCollaborator := func(user *user_model.User) (bool, error) {
|
||||||
|
return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
_ = asymkey_model.CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
|
_ = asymkey_model.CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
|
||||||
|
|
||||||
newCommits = append(newCommits, signCommit)
|
newCommits = append(newCommits, signCommit)
|
||||||
@ -62,11 +66,9 @@ func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo
|
|||||||
}
|
}
|
||||||
signedCommits, err := ParseCommitsWithSignature(
|
signedCommits, err := ParseCommitsWithSignature(
|
||||||
ctx,
|
ctx,
|
||||||
|
repo,
|
||||||
validatedCommits,
|
validatedCommits,
|
||||||
repo.GetTrustModel(),
|
repo.GetTrustModel(),
|
||||||
func(user *user_model.User) (bool, error) {
|
|
||||||
return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
Reference in New Issue
Block a user