use built-in cache package to wrap external go-chi cache packagetags/v1.22.0-rc1
@@ -86,6 +86,8 @@ linters-settings: | |||
desc: do not use the internal package, use AddXxx function instead | |||
- pkg: gopkg.in/ini.v1 | |||
desc: do not use the ini package, use gitea's config system instead | |||
- pkg: gitea.com/go-chi/cache | |||
desc: do not use the go-chi cache package, use gitea's cache system | |||
issues: | |||
max-issues-per-linter: 0 |
@@ -4,149 +4,75 @@ | |||
package cache | |||
import ( | |||
"fmt" | |||
"strconv" | |||
"time" | |||
"code.gitea.io/gitea/modules/setting" | |||
mc "gitea.com/go-chi/cache" | |||
_ "gitea.com/go-chi/cache/memcache" // memcache plugin for cache | |||
) | |||
var conn mc.Cache | |||
func newCache(cacheConfig setting.Cache) (mc.Cache, error) { | |||
return mc.NewCacher(mc.Options{ | |||
Adapter: cacheConfig.Adapter, | |||
AdapterConfig: cacheConfig.Conn, | |||
Interval: cacheConfig.Interval, | |||
}) | |||
} | |||
var defaultCache StringCache | |||
// Init start cache service | |||
func Init() error { | |||
var err error | |||
if conn == nil { | |||
if conn, err = newCache(setting.CacheService.Cache); err != nil { | |||
if defaultCache == nil { | |||
c, err := NewStringCache(setting.CacheService.Cache) | |||
if err != nil { | |||
return err | |||
} | |||
if err = conn.Ping(); err != nil { | |||
for i := 0; i < 10; i++ { | |||
if err = c.Ping(); err == nil { | |||
break | |||
} | |||
time.Sleep(time.Second) | |||
} | |||
if err != nil { | |||
return err | |||
} | |||
defaultCache = c | |||
} | |||
return err | |||
return nil | |||
} | |||
// GetCache returns the currently configured cache | |||
func GetCache() mc.Cache { | |||
return conn | |||
func GetCache() StringCache { | |||
return defaultCache | |||
} | |||
// GetString returns the key value from cache with callback when no key exists in cache | |||
func GetString(key string, getFunc func() (string, error)) (string, error) { | |||
if conn == nil || setting.CacheService.TTL == 0 { | |||
if defaultCache == nil || setting.CacheService.TTL == 0 { | |||
return getFunc() | |||
} | |||
cached := conn.Get(key) | |||
if cached == nil { | |||
cached, exist := defaultCache.Get(key) | |||
if !exist { | |||
value, err := getFunc() | |||
if err != nil { | |||
return value, err | |||
} | |||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds()) | |||
} | |||
if value, ok := cached.(string); ok { | |||
return value, nil | |||
} | |||
if stringer, ok := cached.(fmt.Stringer); ok { | |||
return stringer.String(), nil | |||
} | |||
return fmt.Sprintf("%s", cached), nil | |||
} | |||
// GetInt returns key value from cache with callback when no key exists in cache | |||
func GetInt(key string, getFunc func() (int, error)) (int, error) { | |||
if conn == nil || setting.CacheService.TTL == 0 { | |||
return getFunc() | |||
} | |||
cached := conn.Get(key) | |||
if cached == nil { | |||
value, err := getFunc() | |||
if err != nil { | |||
return value, err | |||
} | |||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds()) | |||
} | |||
switch v := cached.(type) { | |||
case int: | |||
return v, nil | |||
case string: | |||
value, err := strconv.Atoi(v) | |||
if err != nil { | |||
return 0, err | |||
} | |||
return value, nil | |||
default: | |||
value, err := getFunc() | |||
if err != nil { | |||
return value, err | |||
} | |||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds()) | |||
return value, defaultCache.Put(key, value, setting.CacheService.TTLSeconds()) | |||
} | |||
return cached, nil | |||
} | |||
// GetInt64 returns key value from cache with callback when no key exists in cache | |||
func GetInt64(key string, getFunc func() (int64, error)) (int64, error) { | |||
if conn == nil || setting.CacheService.TTL == 0 { | |||
return getFunc() | |||
} | |||
cached := conn.Get(key) | |||
if cached == nil { | |||
value, err := getFunc() | |||
if err != nil { | |||
return value, err | |||
} | |||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds()) | |||
s, err := GetString(key, func() (string, error) { | |||
v, err := getFunc() | |||
return strconv.FormatInt(v, 10), err | |||
}) | |||
if err != nil { | |||
return 0, err | |||
} | |||
switch v := conn.Get(key).(type) { | |||
case int64: | |||
return v, nil | |||
case string: | |||
value, err := strconv.ParseInt(v, 10, 64) | |||
if err != nil { | |||
return 0, err | |||
} | |||
return value, nil | |||
default: | |||
value, err := getFunc() | |||
if err != nil { | |||
return value, err | |||
} | |||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds()) | |||
if s == "" { | |||
return 0, nil | |||
} | |||
return strconv.ParseInt(s, 10, 64) | |||
} | |||
// Remove key from cache | |||
func Remove(key string) { | |||
if conn == nil { | |||
if defaultCache == nil { | |||
return | |||
} | |||
_ = conn.Delete(key) | |||
_ = defaultCache.Delete(key) | |||
} |
@@ -11,7 +11,7 @@ import ( | |||
"code.gitea.io/gitea/modules/graceful" | |||
"code.gitea.io/gitea/modules/nosql" | |||
"gitea.com/go-chi/cache" | |||
"gitea.com/go-chi/cache" //nolint:depguard | |||
"github.com/redis/go-redis/v9" | |||
) | |||
@@ -14,7 +14,7 @@ import ( | |||
) | |||
func createTestCache() { | |||
conn, _ = newCache(setting.Cache{ | |||
defaultCache, _ = NewStringCache(setting.Cache{ | |||
Adapter: "memory", | |||
TTL: time.Minute, | |||
}) | |||
@@ -25,7 +25,7 @@ func TestNewContext(t *testing.T) { | |||
assert.NoError(t, Init()) | |||
setting.CacheService.Cache = setting.Cache{Adapter: "redis", Conn: "some random string"} | |||
con, err := newCache(setting.Cache{ | |||
con, err := NewStringCache(setting.Cache{ | |||
Adapter: "rand", | |||
Conn: "false conf", | |||
Interval: 100, | |||
@@ -76,42 +76,6 @@ func TestGetString(t *testing.T) { | |||
Remove("key") | |||
} | |||
func TestGetInt(t *testing.T) { | |||
createTestCache() | |||
data, err := GetInt("key", func() (int, error) { | |||
return 0, fmt.Errorf("some error") | |||
}) | |||
assert.Error(t, err) | |||
assert.Equal(t, 0, data) | |||
data, err = GetInt("key", func() (int, error) { | |||
return 0, nil | |||
}) | |||
assert.NoError(t, err) | |||
assert.Equal(t, 0, data) | |||
data, err = GetInt("key", func() (int, error) { | |||
return 100, nil | |||
}) | |||
assert.NoError(t, err) | |||
assert.Equal(t, 0, data) | |||
Remove("key") | |||
data, err = GetInt("key", func() (int, error) { | |||
return 100, nil | |||
}) | |||
assert.NoError(t, err) | |||
assert.Equal(t, 100, data) | |||
data, err = GetInt("key", func() (int, error) { | |||
return 0, fmt.Errorf("some error") | |||
}) | |||
assert.NoError(t, err) | |||
assert.Equal(t, 100, data) | |||
Remove("key") | |||
} | |||
func TestGetInt64(t *testing.T) { | |||
createTestCache() | |||
@@ -10,7 +10,7 @@ import ( | |||
"code.gitea.io/gitea/modules/json" | |||
mc "gitea.com/go-chi/cache" | |||
mc "gitea.com/go-chi/cache" //nolint:depguard | |||
lru "github.com/hashicorp/golang-lru/v2" | |||
) | |||
@@ -0,0 +1,120 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package cache | |||
import ( | |||
"errors" | |||
"strings" | |||
"code.gitea.io/gitea/modules/json" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/util" | |||
chi_cache "gitea.com/go-chi/cache" //nolint:depguard | |||
) | |||
type GetJSONError struct { | |||
err error | |||
cachedError string // Golang error can't be stored in cache, only the string message could be stored | |||
} | |||
func (e *GetJSONError) ToError() error { | |||
if e.err != nil { | |||
return e.err | |||
} | |||
return errors.New("cached error: " + e.cachedError) | |||
} | |||
type StringCache interface { | |||
Ping() error | |||
Get(key string) (string, bool) | |||
Put(key, value string, ttl int64) error | |||
Delete(key string) error | |||
IsExist(key string) bool | |||
PutJSON(key string, v any, ttl int64) error | |||
GetJSON(key string, ptr any) (exist bool, err *GetJSONError) | |||
ChiCache() chi_cache.Cache | |||
} | |||
type stringCache struct { | |||
chiCache chi_cache.Cache | |||
} | |||
func NewStringCache(cacheConfig setting.Cache) (StringCache, error) { | |||
adapter := util.IfZero(cacheConfig.Adapter, "memory") | |||
interval := util.IfZero(cacheConfig.Interval, 60) | |||
cc, err := chi_cache.NewCacher(chi_cache.Options{ | |||
Adapter: adapter, | |||
AdapterConfig: cacheConfig.Conn, | |||
Interval: interval, | |||
}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &stringCache{chiCache: cc}, nil | |||
} | |||
func (sc *stringCache) Ping() error { | |||
return sc.chiCache.Ping() | |||
} | |||
func (sc *stringCache) Get(key string) (string, bool) { | |||
v := sc.chiCache.Get(key) | |||
if v == nil { | |||
return "", false | |||
} | |||
s, ok := v.(string) | |||
return s, ok | |||
} | |||
func (sc *stringCache) Put(key, value string, ttl int64) error { | |||
return sc.chiCache.Put(key, value, ttl) | |||
} | |||
func (sc *stringCache) Delete(key string) error { | |||
return sc.chiCache.Delete(key) | |||
} | |||
func (sc *stringCache) IsExist(key string) bool { | |||
return sc.chiCache.IsExist(key) | |||
} | |||
const cachedErrorPrefix = "<CACHED-ERROR>:" | |||
func (sc *stringCache) PutJSON(key string, v any, ttl int64) error { | |||
var s string | |||
switch v := v.(type) { | |||
case error: | |||
s = cachedErrorPrefix + v.Error() | |||
default: | |||
b, err := json.Marshal(v) | |||
if err != nil { | |||
return err | |||
} | |||
s = util.UnsafeBytesToString(b) | |||
} | |||
return sc.chiCache.Put(key, s, ttl) | |||
} | |||
func (sc *stringCache) GetJSON(key string, ptr any) (exist bool, getErr *GetJSONError) { | |||
s, ok := sc.Get(key) | |||
if !ok || s == "" { | |||
return false, nil | |||
} | |||
s, isCachedError := strings.CutPrefix(s, cachedErrorPrefix) | |||
if isCachedError { | |||
return true, &GetJSONError{cachedError: s} | |||
} | |||
if err := json.Unmarshal(util.UnsafeStringToBytes(s), ptr); err != nil { | |||
return false, &GetJSONError{err: err} | |||
} | |||
return true, nil | |||
} | |||
func (sc *stringCache) ChiCache() chi_cache.Cache { | |||
return sc.chiCache | |||
} |
@@ -7,18 +7,11 @@ import ( | |||
"crypto/sha256" | |||
"fmt" | |||
"code.gitea.io/gitea/modules/cache" | |||
"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) | |||
@@ -30,11 +23,11 @@ type LastCommitCache struct { | |||
ttl func() int64 | |||
repo *Repository | |||
commitCache map[string]*Commit | |||
cache Cache | |||
cache cache.StringCache | |||
} | |||
// NewLastCommitCache creates a new last commit cache for repo | |||
func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache Cache) *LastCommitCache { | |||
func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache cache.StringCache) *LastCommitCache { | |||
if cache == nil { | |||
return nil | |||
} | |||
@@ -65,7 +58,7 @@ func (c *LastCommitCache) Get(ref, entryPath string) (*Commit, error) { | |||
return nil, nil | |||
} | |||
commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath)).(string) | |||
commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath)) | |||
if !ok || commitID == "" { | |||
return nil, nil | |||
} |
@@ -29,9 +29,7 @@ func NodeInfo(ctx *context.APIContext) { | |||
nodeInfoUsage := structs.NodeInfoUsage{} | |||
if setting.Federation.ShareUserStatistics { | |||
var cached bool | |||
nodeInfoUsage, cached = ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage) | |||
cached, _ := ctx.Cache.GetJSON(cacheKeyNodeInfoUsage, &nodeInfoUsage) | |||
if !cached { | |||
usersTotal := int(user_model.CountUsers(ctx, nil)) | |||
now := time.Now() | |||
@@ -53,7 +51,7 @@ func NodeInfo(ctx *context.APIContext) { | |||
LocalComments: int(allComments), | |||
} | |||
if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil { | |||
if err := ctx.Cache.PutJSON(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil { | |||
ctx.InternalServerError(err) | |||
return | |||
} |
@@ -13,7 +13,7 @@ import ( | |||
"code.gitea.io/gitea/models/unit" | |||
user_model "code.gitea.io/gitea/models/user" | |||
mc "code.gitea.io/gitea/modules/cache" | |||
"code.gitea.io/gitea/modules/cache" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/gitrepo" | |||
"code.gitea.io/gitea/modules/httpcache" | |||
@@ -21,15 +21,13 @@ import ( | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/web" | |||
web_types "code.gitea.io/gitea/modules/web/types" | |||
"gitea.com/go-chi/cache" | |||
) | |||
// APIContext is a specific context for API service | |||
type APIContext struct { | |||
*Base | |||
Cache cache.Cache | |||
Cache cache.StringCache | |||
Doer *user_model.User // current signed-in user | |||
IsSigned bool | |||
@@ -217,7 +215,7 @@ func APIContexter() func(http.Handler) http.Handler { | |||
base, baseCleanUp := NewBaseContext(w, req) | |||
ctx := &APIContext{ | |||
Base: base, | |||
Cache: mc.GetCache(), | |||
Cache: cache.GetCache(), | |||
Repo: &Repository{PullRequest: &PullRequest{}}, | |||
Org: &APIOrganization{}, | |||
} |
@@ -30,7 +30,7 @@ func GetImageCaptcha() *captcha.Captcha { | |||
cpt = captcha.NewCaptcha(captcha.Options{ | |||
SubURL: setting.AppSubURL, | |||
}) | |||
cpt.Store = cache.GetCache() | |||
cpt.Store = cache.GetCache().ChiCache() | |||
}) | |||
return cpt | |||
} |
@@ -17,7 +17,7 @@ import ( | |||
"code.gitea.io/gitea/models/unit" | |||
user_model "code.gitea.io/gitea/models/user" | |||
mc "code.gitea.io/gitea/modules/cache" | |||
"code.gitea.io/gitea/modules/cache" | |||
"code.gitea.io/gitea/modules/gitrepo" | |||
"code.gitea.io/gitea/modules/httpcache" | |||
"code.gitea.io/gitea/modules/setting" | |||
@@ -27,7 +27,6 @@ import ( | |||
"code.gitea.io/gitea/modules/web/middleware" | |||
web_types "code.gitea.io/gitea/modules/web/types" | |||
"gitea.com/go-chi/cache" | |||
"gitea.com/go-chi/session" | |||
) | |||
@@ -46,7 +45,7 @@ type Context struct { | |||
Render Render | |||
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData` | |||
Cache cache.Cache | |||
Cache cache.StringCache | |||
Csrf CSRFProtector | |||
Flash *middleware.Flash | |||
Session session.Store | |||
@@ -111,7 +110,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context { | |||
Render: render, | |||
Session: session, | |||
Cache: mc.GetCache(), | |||
Cache: cache.GetCache(), | |||
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"), | |||
Repo: &Repository{PullRequest: &PullRequest{}}, | |||
Org: &Organization{}, |
@@ -26,6 +26,7 @@ import ( | |||
"code.gitea.io/gitea/modules/queue" | |||
repo_module "code.gitea.io/gitea/modules/repository" | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"code.gitea.io/gitea/modules/util" | |||
webhook_module "code.gitea.io/gitea/modules/webhook" | |||
notify_service "code.gitea.io/gitea/services/notify" | |||
files_service "code.gitea.io/gitea/services/repository/files" | |||
@@ -119,17 +120,15 @@ func getDivergenceCacheKey(repoID int64, branchName string) string { | |||
// getDivergenceFromCache gets the divergence from cache | |||
func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject, bool) { | |||
data := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName)) | |||
data, ok := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName)) | |||
res := git.DivergeObject{ | |||
Ahead: -1, | |||
Behind: -1, | |||
} | |||
s, ok := data.([]byte) | |||
if !ok || len(s) == 0 { | |||
if !ok || data == "" { | |||
return &res, false | |||
} | |||
if err := json.Unmarshal(s, &res); err != nil { | |||
if err := json.Unmarshal(util.UnsafeStringToBytes(data), &res); err != nil { | |||
log.Error("json.UnMarshal failed: %v", err) | |||
return &res, false | |||
} | |||
@@ -141,7 +140,7 @@ func putDivergenceFromCache(repoID int64, branchName string, divergence *git.Div | |||
if err != nil { | |||
return err | |||
} | |||
return cache.GetCache().Put(getDivergenceCacheKey(repoID, branchName), bs, 30*24*60*60) | |||
return cache.GetCache().Put(getDivergenceCacheKey(repoID, branchName), util.UnsafeBytesToString(bs), 30*24*60*60) | |||
} | |||
func DelDivergenceFromCache(repoID int64, branchName string) error { |
@@ -34,7 +34,7 @@ type commitStatusCacheValue struct { | |||
func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheValue { | |||
c := cache.GetCache() | |||
statusStr, ok := c.Get(getCacheKey(repoID, branchName)).(string) | |||
statusStr, ok := c.Get(getCacheKey(repoID, branchName)) | |||
if ok && statusStr != "" { | |||
var cv commitStatusCacheValue | |||
err := json.Unmarshal([]byte(statusStr), &cv) |
@@ -17,13 +17,12 @@ import ( | |||
"code.gitea.io/gitea/models/avatars" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
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/gitrepo" | |||
"code.gitea.io/gitea/modules/graceful" | |||
"code.gitea.io/gitea/modules/log" | |||
api "code.gitea.io/gitea/modules/structs" | |||
"gitea.com/go-chi/cache" | |||
) | |||
const ( | |||
@@ -79,13 +78,13 @@ func findLastSundayBeforeDate(dateStr string) (string, error) { | |||
} | |||
// GetContributorStats returns contributors stats for git commits for given revision or default branch | |||
func GetContributorStats(ctx context.Context, cache cache.Cache, repo *repo_model.Repository, revision string) (map[string]*ContributorData, error) { | |||
func GetContributorStats(ctx context.Context, cache cache.StringCache, repo *repo_model.Repository, revision string) (map[string]*ContributorData, error) { | |||
// as GetContributorStats is resource intensive we cache the result | |||
cacheKey := fmt.Sprintf(contributorStatsCacheKey, repo.FullName(), revision) | |||
if !cache.IsExist(cacheKey) { | |||
genReady := make(chan struct{}) | |||
// dont start multible async generations | |||
// dont start multiple async generations | |||
_, run := generateLock.Load(cacheKey) | |||
if run { | |||
return nil, ErrAwaitGeneration | |||
@@ -104,15 +103,11 @@ func GetContributorStats(ctx context.Context, cache cache.Cache, repo *repo_mode | |||
} | |||
} | |||
// TODO: renew timeout of cache cache.UpdateTimeout(cacheKey, contributorStatsCacheTimeout) | |||
switch v := cache.Get(cacheKey).(type) { | |||
case error: | |||
return nil, v | |||
case map[string]*ContributorData: | |||
return v, nil | |||
default: | |||
return nil, fmt.Errorf("unexpected type in cache detected") | |||
var res map[string]*ContributorData | |||
if _, cacheErr := cache.GetJSON(cacheKey, &res); cacheErr != nil { | |||
return nil, fmt.Errorf("cached error: %w", cacheErr.ToError()) | |||
} | |||
return res, nil | |||
} | |||
// getExtendedCommitStats return the list of *ExtendedCommitStats for the given revision | |||
@@ -205,13 +200,12 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int | |||
return extendedCommitStats, nil | |||
} | |||
func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey string, repo *repo_model.Repository, revision string) { | |||
func generateContributorStats(genDone chan struct{}, cache cache.StringCache, cacheKey string, repo *repo_model.Repository, revision string) { | |||
ctx := graceful.GetManager().HammerContext() | |||
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo) | |||
if err != nil { | |||
err := fmt.Errorf("OpenRepository: %w", err) | |||
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout) | |||
_ = cache.PutJSON(cacheKey, fmt.Errorf("OpenRepository: %w", err), contributorStatsCacheTimeout) | |||
return | |||
} | |||
defer closer.Close() | |||
@@ -221,13 +215,11 @@ func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey | |||
} | |||
extendedCommitStats, err := getExtendedCommitStats(gitRepo, revision) | |||
if err != nil { | |||
err := fmt.Errorf("ExtendedCommitStats: %w", err) | |||
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout) | |||
_ = cache.PutJSON(cacheKey, fmt.Errorf("ExtendedCommitStats: %w", err), contributorStatsCacheTimeout) | |||
return | |||
} | |||
if len(extendedCommitStats) == 0 { | |||
err := fmt.Errorf("no commit stats returned for revision '%s'", revision) | |||
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout) | |||
_ = cache.PutJSON(cacheKey, fmt.Errorf("no commit stats returned for revision '%s'", revision), contributorStatsCacheTimeout) | |||
return | |||
} | |||
@@ -309,7 +301,7 @@ func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey | |||
total.TotalCommits++ | |||
} | |||
_ = cache.Put(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout) | |||
_ = cache.PutJSON(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout) | |||
generateLock.Delete(cacheKey) | |||
if genDone != nil { | |||
genDone <- struct{}{} |
@@ -10,9 +10,9 @@ import ( | |||
"code.gitea.io/gitea/models/db" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/models/unittest" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/cache" | |||
"code.gitea.io/gitea/modules/setting" | |||
"gitea.com/go-chi/cache" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
@@ -20,20 +20,18 @@ func TestRepository_ContributorsGraph(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) | |||
assert.NoError(t, repo.LoadOwner(db.DefaultContext)) | |||
mockCache, err := cache.NewCacher(cache.Options{ | |||
Adapter: "memory", | |||
Interval: 24 * 60, | |||
}) | |||
mockCache, err := cache.NewStringCache(setting.Cache{}) | |||
assert.NoError(t, err) | |||
generateContributorStats(nil, mockCache, "key", repo, "404ref") | |||
err, isErr := mockCache.Get("key").(error) | |||
assert.True(t, isErr) | |||
assert.ErrorAs(t, err, &git.ErrNotExist{}) | |||
var data map[string]*ContributorData | |||
_, getErr := mockCache.GetJSON("key", &data) | |||
assert.NotNil(t, getErr) | |||
assert.ErrorContains(t, getErr.ToError(), "object does not exist") | |||
generateContributorStats(nil, mockCache, "key2", repo, "master") | |||
data, isData := mockCache.Get("key2").(map[string]*ContributorData) | |||
assert.True(t, isData) | |||
exist, _ := mockCache.GetJSON("key2", &data) | |||
assert.True(t, exist) | |||
var keys []string | |||
for k := range data { | |||
keys = append(keys, k) |