mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-26 20:11:02 +00:00 
			
		
		
		
	Unfortunately, when a system setting hasn't been stored in the database, it cannot be cached. Meanwhile, this PR also uses context cache for push email avatar display which should avoid to read user table via email address again and again. According to my local test, this should reduce dashboard elapsed time from 150ms -> 80ms .
		
			
				
	
	
		
			173 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			173 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2019 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package repository
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"net/url"
 | |
| 	"time"
 | |
| 
 | |
| 	"code.gitea.io/gitea/models/avatars"
 | |
| 	user_model "code.gitea.io/gitea/models/user"
 | |
| 	"code.gitea.io/gitea/modules/cache"
 | |
| 	"code.gitea.io/gitea/modules/git"
 | |
| 	"code.gitea.io/gitea/modules/log"
 | |
| 	"code.gitea.io/gitea/modules/setting"
 | |
| 	api "code.gitea.io/gitea/modules/structs"
 | |
| )
 | |
| 
 | |
| // PushCommit represents a commit in a push operation.
 | |
| type PushCommit struct {
 | |
| 	Sha1           string
 | |
| 	Message        string
 | |
| 	AuthorEmail    string
 | |
| 	AuthorName     string
 | |
| 	CommitterEmail string
 | |
| 	CommitterName  string
 | |
| 	Timestamp      time.Time
 | |
| }
 | |
| 
 | |
| // PushCommits represents list of commits in a push operation.
 | |
| type PushCommits struct {
 | |
| 	Commits    []*PushCommit
 | |
| 	HeadCommit *PushCommit
 | |
| 	CompareURL string
 | |
| 	Len        int
 | |
| }
 | |
| 
 | |
| // NewPushCommits creates a new PushCommits object.
 | |
| func NewPushCommits() *PushCommits {
 | |
| 	return &PushCommits{}
 | |
| }
 | |
| 
 | |
| // toAPIPayloadCommit converts a single PushCommit to an api.PayloadCommit object.
 | |
| func (pc *PushCommits) toAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.User, repoPath, repoLink string, commit *PushCommit) (*api.PayloadCommit, error) {
 | |
| 	var err error
 | |
| 	authorUsername := ""
 | |
| 	author, ok := emailUsers[commit.AuthorEmail]
 | |
| 	if !ok {
 | |
| 		author, err = user_model.GetUserByEmail(ctx, commit.AuthorEmail)
 | |
| 		if err == nil {
 | |
| 			authorUsername = author.Name
 | |
| 			emailUsers[commit.AuthorEmail] = author
 | |
| 		}
 | |
| 	} else {
 | |
| 		authorUsername = author.Name
 | |
| 	}
 | |
| 
 | |
| 	committerUsername := ""
 | |
| 	committer, ok := emailUsers[commit.CommitterEmail]
 | |
| 	if !ok {
 | |
| 		committer, err = user_model.GetUserByEmail(ctx, commit.CommitterEmail)
 | |
| 		if err == nil {
 | |
| 			// TODO: check errors other than email not found.
 | |
| 			committerUsername = committer.Name
 | |
| 			emailUsers[commit.CommitterEmail] = committer
 | |
| 		}
 | |
| 	} else {
 | |
| 		committerUsername = committer.Name
 | |
| 	}
 | |
| 
 | |
| 	fileStatus, err := git.GetCommitFileStatus(ctx, repoPath, commit.Sha1)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %w", commit.Sha1, err)
 | |
| 	}
 | |
| 
 | |
| 	return &api.PayloadCommit{
 | |
| 		ID:      commit.Sha1,
 | |
| 		Message: commit.Message,
 | |
| 		URL:     fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(commit.Sha1)),
 | |
| 		Author: &api.PayloadUser{
 | |
| 			Name:     commit.AuthorName,
 | |
| 			Email:    commit.AuthorEmail,
 | |
| 			UserName: authorUsername,
 | |
| 		},
 | |
| 		Committer: &api.PayloadUser{
 | |
| 			Name:     commit.CommitterName,
 | |
| 			Email:    commit.CommitterEmail,
 | |
| 			UserName: committerUsername,
 | |
| 		},
 | |
| 		Added:     fileStatus.Added,
 | |
| 		Removed:   fileStatus.Removed,
 | |
| 		Modified:  fileStatus.Modified,
 | |
| 		Timestamp: commit.Timestamp,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // ToAPIPayloadCommits converts a PushCommits object to api.PayloadCommit format.
 | |
| // It returns all converted commits and, if provided, the head commit or an error otherwise.
 | |
| func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLink string) ([]*api.PayloadCommit, *api.PayloadCommit, error) {
 | |
| 	commits := make([]*api.PayloadCommit, len(pc.Commits))
 | |
| 	var headCommit *api.PayloadCommit
 | |
| 
 | |
| 	emailUsers := make(map[string]*user_model.User)
 | |
| 
 | |
| 	for i, commit := range pc.Commits {
 | |
| 		apiCommit, err := pc.toAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, commit)
 | |
| 		if err != nil {
 | |
| 			return nil, nil, err
 | |
| 		}
 | |
| 
 | |
| 		commits[i] = apiCommit
 | |
| 		if pc.HeadCommit != nil && pc.HeadCommit.Sha1 == commits[i].ID {
 | |
| 			headCommit = apiCommit
 | |
| 		}
 | |
| 	}
 | |
| 	if pc.HeadCommit != nil && headCommit == nil {
 | |
| 		var err error
 | |
| 		headCommit, err = pc.toAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, pc.HeadCommit)
 | |
| 		if err != nil {
 | |
| 			return nil, nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	return commits, headCommit, nil
 | |
| }
 | |
| 
 | |
| // AvatarLink tries to match user in database with e-mail
 | |
| // in order to show custom avatar, and falls back to general avatar link.
 | |
| func (pc *PushCommits) AvatarLink(ctx context.Context, email string) string {
 | |
| 	size := avatars.DefaultAvatarPixelSize * setting.Avatar.RenderedSizeFactor
 | |
| 
 | |
| 	v, _ := cache.GetWithContextCache(ctx, "push_commits", email, func() (string, error) {
 | |
| 		u, err := user_model.GetUserByEmail(ctx, email)
 | |
| 		if err != nil {
 | |
| 			if !user_model.IsErrUserNotExist(err) {
 | |
| 				log.Error("GetUserByEmail: %v", err)
 | |
| 				return "", err
 | |
| 			}
 | |
| 			return avatars.GenerateEmailAvatarFastLink(ctx, email, size), nil
 | |
| 		}
 | |
| 		return u.AvatarLinkWithSize(ctx, size), nil
 | |
| 	})
 | |
| 
 | |
| 	return v
 | |
| }
 | |
| 
 | |
| // CommitToPushCommit transforms a git.Commit to PushCommit type.
 | |
| func CommitToPushCommit(commit *git.Commit) *PushCommit {
 | |
| 	return &PushCommit{
 | |
| 		Sha1:           commit.ID.String(),
 | |
| 		Message:        commit.Message(),
 | |
| 		AuthorEmail:    commit.Author.Email,
 | |
| 		AuthorName:     commit.Author.Name,
 | |
| 		CommitterEmail: commit.Committer.Email,
 | |
| 		CommitterName:  commit.Committer.Name,
 | |
| 		Timestamp:      commit.Author.When,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // GitToPushCommits transforms a list of git.Commits to PushCommits type.
 | |
| func GitToPushCommits(gitCommits []*git.Commit) *PushCommits {
 | |
| 	commits := make([]*PushCommit, 0, len(gitCommits))
 | |
| 	for _, commit := range gitCommits {
 | |
| 		commits = append(commits, CommitToPushCommit(commit))
 | |
| 	}
 | |
| 	return &PushCommits{
 | |
| 		Commits:    commits,
 | |
| 		HeadCommit: nil,
 | |
| 		CompareURL: "",
 | |
| 		Len:        len(commits),
 | |
| 	}
 | |
| }
 |