summaryrefslogtreecommitdiffstats
path: root/modules/git/last_commit_cache.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/git/last_commit_cache.go')
-rw-r--r--modules/git/last_commit_cache.go159
1 files changed, 159 insertions, 0 deletions
diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go
new file mode 100644
index 0000000..8c7ee5a
--- /dev/null
+++ b/modules/git/last_commit_cache.go
@@ -0,0 +1,159 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+ "context"
+ "crypto/sha256"
+ "fmt"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// Cache represents a caching interface
+type Cache interface {
+ // Put puts value into cache with key and expire time.
+ Put(key string, val any, timeout int64) error
+ // Get gets cached value by given key.
+ Get(key string) any
+}
+
+func getCacheKey(repoPath, commitID, entryPath string) string {
+ hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, commitID, entryPath)))
+ return fmt.Sprintf("last_commit:%x", hashBytes)
+}
+
+// LastCommitCache represents a cache to store last commit
+type LastCommitCache struct {
+ repoPath string
+ ttl func() int64
+ repo *Repository
+ commitCache map[string]*Commit
+ cache Cache
+}
+
+// NewLastCommitCache creates a new last commit cache for repo
+func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache Cache) *LastCommitCache {
+ if cache == nil {
+ return nil
+ }
+ if count < setting.CacheService.LastCommit.CommitsCount {
+ return nil
+ }
+
+ return &LastCommitCache{
+ repoPath: repoPath,
+ repo: gitRepo,
+ ttl: setting.LastCommitCacheTTLSeconds,
+ cache: cache,
+ }
+}
+
+// Put put the last commit id with commit and entry path
+func (c *LastCommitCache) Put(ref, entryPath, commitID string) error {
+ if c == nil || c.cache == nil {
+ return nil
+ }
+ log.Debug("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
+ return c.cache.Put(getCacheKey(c.repoPath, ref, entryPath), commitID, c.ttl())
+}
+
+// Get gets the last commit information by commit id and entry path
+func (c *LastCommitCache) Get(ref, entryPath string) (*Commit, error) {
+ if c == nil || c.cache == nil {
+ return nil, nil
+ }
+
+ commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath)).(string)
+ if !ok || commitID == "" {
+ return nil, nil
+ }
+
+ log.Debug("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, commitID)
+ if c.commitCache != nil {
+ if commit, ok := c.commitCache[commitID]; ok {
+ log.Debug("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, commitID)
+ return commit, nil
+ }
+ }
+
+ commit, err := c.repo.GetCommit(commitID)
+ if err != nil {
+ return nil, err
+ }
+ if c.commitCache == nil {
+ c.commitCache = make(map[string]*Commit)
+ }
+ c.commitCache[commitID] = commit
+ return commit, nil
+}
+
+// GetCommitByPath gets the last commit for the entry in the provided commit
+func (c *LastCommitCache) GetCommitByPath(commitID, entryPath string) (*Commit, error) {
+ sha, err := NewIDFromString(commitID)
+ if err != nil {
+ return nil, err
+ }
+
+ lastCommit, err := c.Get(sha.String(), entryPath)
+ if err != nil || lastCommit != nil {
+ return lastCommit, err
+ }
+
+ lastCommit, err = c.repo.getCommitByPathWithID(sha, entryPath)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := c.Put(commitID, entryPath, lastCommit.ID.String()); err != nil {
+ log.Error("Unable to cache %s as the last commit for %q in %s %s. Error %v", lastCommit.ID.String(), entryPath, commitID, c.repoPath, err)
+ }
+
+ return lastCommit, nil
+}
+
+// CacheCommit will cache the commit from the gitRepository
+func (c *Commit) CacheCommit(ctx context.Context) error {
+ if c.repo.LastCommitCache == nil {
+ return nil
+ }
+ return c.recursiveCache(ctx, &c.Tree, "", 1)
+}
+
+func (c *Commit) recursiveCache(ctx context.Context, tree *Tree, treePath string, level int) error {
+ if level == 0 {
+ return nil
+ }
+
+ entries, err := tree.ListEntries()
+ if err != nil {
+ return err
+ }
+
+ entryPaths := make([]string, len(entries))
+ for i, entry := range entries {
+ entryPaths[i] = entry.Name()
+ }
+
+ _, err = WalkGitLog(ctx, c.repo, c, treePath, entryPaths...)
+ if err != nil {
+ return err
+ }
+
+ for _, treeEntry := range entries {
+ // entryMap won't contain "" therefore skip this.
+ if treeEntry.IsDir() {
+ subTree, err := tree.SubTree(treeEntry.Name())
+ if err != nil {
+ return err
+ }
+ if err := c.recursiveCache(ctx, subTree, treeEntry.Name(), level-1); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}