1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Improve commits list performance to reduce unnecessary database queries (#33528)

When listing commits, Gitea attempts to retrieve the actual user based
on the commit email. Querying users one by one from the database is
inefficient. This PR optimizes the process by batch querying users by
email, reducing the number of database queries.
This commit is contained in:
Lunny Xiao
2025-02-14 00:05:55 -08:00
committed by GitHub
parent f232d8f530
commit 70327d6a92
10 changed files with 207 additions and 42 deletions

View File

@@ -1129,28 +1129,85 @@ func ValidateCommitWithEmail(ctx context.Context, c *git.Commit) *User {
}
// ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users.
func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) []*UserCommit {
func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) ([]*UserCommit, error) {
var (
emails = make(map[string]*User)
newCommits = make([]*UserCommit, 0, len(oldCommits))
emailSet = make(container.Set[string])
)
for _, c := range oldCommits {
var u *User
if c.Author != nil {
if v, ok := emails[c.Author.Email]; !ok {
u, _ = GetUserByEmail(ctx, c.Author.Email)
emails[c.Author.Email] = u
} else {
u = v
emailSet.Add(c.Author.Email)
}
}
emailUserMap, err := GetUsersByEmails(ctx, emailSet.Values())
if err != nil {
return nil, err
}
for _, c := range oldCommits {
user, ok := emailUserMap[c.Author.Email]
if !ok {
user = &User{
Name: c.Author.Name,
Email: c.Author.Email,
}
}
newCommits = append(newCommits, &UserCommit{
User: u,
User: user,
Commit: c,
})
}
return newCommits
return newCommits, nil
}
func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, error) {
if len(emails) == 0 {
return nil, nil
}
needCheckEmails := make(container.Set[string])
needCheckUserNames := make(container.Set[string])
for _, email := range emails {
if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) {
username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress))
needCheckUserNames.Add(username)
} else {
needCheckEmails.Add(strings.ToLower(email))
}
}
emailAddresses := make([]*EmailAddress, 0, len(needCheckEmails))
if err := db.GetEngine(ctx).In("lower_email", needCheckEmails.Values()).
And("is_activated=?", true).
Find(&emailAddresses); err != nil {
return nil, err
}
userIDs := make(container.Set[int64])
for _, email := range emailAddresses {
userIDs.Add(email.UID)
}
users, err := GetUsersByIDs(ctx, userIDs.Values())
if err != nil {
return nil, err
}
results := make(map[string]*User, len(emails))
for _, user := range users {
if user.KeepEmailPrivate {
results[user.LowerName+"@"+setting.Service.NoReplyAddress] = user
} else {
results[user.Email] = user
}
}
users = make([]*User, 0, len(needCheckUserNames))
if err := db.GetEngine(ctx).In("lower_name", needCheckUserNames.Values()).Find(&users); err != nil {
return nil, err
}
for _, user := range users {
results[user.LowerName+"@"+setting.Service.NoReplyAddress] = user
}
return results, nil
}
// GetUserByEmail returns the user object by given e-mail if exists.