mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-12 22:11:07 +00:00
fix: git cache (#38763)
1. always use "last commit cache" 2. correctly build the cache key for any input (SafeCacheKey) 3. fix the git note "last commit cache FIXME" and avoid OOM
This commit is contained in:
@@ -1973,9 +1973,6 @@ LEVEL = Info
|
|||||||
;; Time to keep items in cache if not used, default is 8760 hours.
|
;; Time to keep items in cache if not used, default is 8760 hours.
|
||||||
;; Setting it to -1 disables caching
|
;; Setting it to -1 disables caching
|
||||||
;ITEM_TTL = 8760h
|
;ITEM_TTL = 8760h
|
||||||
;;
|
|
||||||
;; Only enable the cache when repository's commits count great than
|
|
||||||
;COMMITS_COUNT = 1000
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|||||||
@@ -365,17 +365,6 @@ func (repo *Repository) APIURL(ctxOpt ...context.Context) string {
|
|||||||
return httplib.MakeAbsoluteURL(ctx, setting.AppSubURL+"/api/v1/repos/"+url.PathEscape(repo.OwnerName)+"/"+url.PathEscape(repo.Name))
|
return httplib.MakeAbsoluteURL(ctx, setting.AppSubURL+"/api/v1/repos/"+url.PathEscape(repo.OwnerName)+"/"+url.PathEscape(repo.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommitsCountCacheKey returns cache key used for commits count caching.
|
|
||||||
func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) string {
|
|
||||||
var prefix string
|
|
||||||
if isRef {
|
|
||||||
prefix = "ref"
|
|
||||||
} else {
|
|
||||||
prefix = "commit"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("commits-count-%d-%s-%s", repo.ID, prefix, contextName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadUnits loads repo units into repo.Units
|
// LoadUnits loads repo units into repo.Units
|
||||||
func (repo *Repository) LoadUnits(ctx context.Context) (err error) {
|
func (repo *Repository) LoadUnits(ctx context.Context) (err error) {
|
||||||
if repo.Units != nil {
|
if repo.Units != nil {
|
||||||
|
|||||||
Vendored
+29
@@ -4,6 +4,7 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
|
"gitea.dev/modules/util"
|
||||||
|
|
||||||
_ "gitea.com/go-chi/cache/memcache" //nolint:depguard // memcache plugin for cache, it is required for config "ADAPTER=memcache"
|
_ "gitea.com/go-chi/cache/memcache" //nolint:depguard // memcache plugin for cache, it is required for config "ADAPTER=memcache"
|
||||||
)
|
)
|
||||||
@@ -117,3 +119,30 @@ func Remove(key string) {
|
|||||||
}
|
}
|
||||||
_ = defaultCache.Delete(key)
|
_ = defaultCache.Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SafeCacheKey returns a cache-safe key for the input string
|
||||||
|
// Some caches like memcached have char & length limits.
|
||||||
|
// Caller must make sure the prefix is valid and well-designed.
|
||||||
|
// If prefix is already too long, the returned key will still exceed the limit, then just let the cache report an error.
|
||||||
|
func SafeCacheKey(prefix, input string) string {
|
||||||
|
// memcached has a limit 250 for key length, so we use 230 to leave some room for other prefixes and separators
|
||||||
|
return safeCacheKey(prefix, input, 230)
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeCacheKey(prefix, input string, limit int) string {
|
||||||
|
safeAsKey := len(prefix)+len(input)+3 <= limit
|
||||||
|
if safeAsKey {
|
||||||
|
for i := 0; i < len(input); i++ {
|
||||||
|
if c := input[i]; c <= ' ' || c >= 127 {
|
||||||
|
safeAsKey = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sep, key := ":s-", input
|
||||||
|
if !safeAsKey {
|
||||||
|
hashBytes := sha256.Sum256(util.UnsafeStringToBytes(input))
|
||||||
|
sep, key = ":h-", hex.EncodeToString(hashBytes[:])
|
||||||
|
}
|
||||||
|
return prefix + sep + key
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+8
@@ -124,3 +124,11 @@ func TestGetInt64(t *testing.T) {
|
|||||||
assert.EqualValues(t, 100, data)
|
assert.EqualValues(t, 100, data)
|
||||||
Remove("key")
|
Remove("key")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSafeCacheKey(t *testing.T) {
|
||||||
|
assert.Equal(t, "prefix:s-0~", safeCacheKey("prefix", "0~", 100))
|
||||||
|
assert.Equal(t, "prefix:h-36a9e7f1c95b82ffb99743e0c5c4ce95d83c9a430aac59f84ef3cbfab6145068", safeCacheKey("prefix", " ", 100))
|
||||||
|
|
||||||
|
assert.Equal(t, "prefix:s-a", safeCacheKey("prefix", "a", 10))
|
||||||
|
assert.Equal(t, "prefix:h-961b6dd3ede3cb8ecbaacbd68de040cd78eb2ed5889130cceb4c49268ea4d506", safeCacheKey("prefix", "aa", 10))
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,10 +66,7 @@ func (c *Commit) ParentCount() int {
|
|||||||
|
|
||||||
// GetCommitByPath return the commit of relative path object.
|
// GetCommitByPath return the commit of relative path object.
|
||||||
func (c *Commit) GetCommitByPath(ctx context.Context, gitRepo *Repository, relpath string) (*Commit, error) {
|
func (c *Commit) GetCommitByPath(ctx context.Context, gitRepo *Repository, relpath string) (*Commit, error) {
|
||||||
if gitRepo.LastCommitCache != nil {
|
return gitRepo.LastCommitCache.GetCommitByPath(ctx, c.ID, relpath)
|
||||||
return gitRepo.LastCommitCache.GetCommitByPath(ctx, c.ID.String(), relpath)
|
|
||||||
}
|
|
||||||
return gitRepo.getCommitByPathWithID(ctx, c.ID, relpath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commit) Tree() *Tree {
|
func (c *Commit) Tree() *Tree {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.dev/modules/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeCommitsCountCacheKey(repo RepositoryFacade, ref RefName) string {
|
||||||
|
return cache.SafeCacheKey("git-commits-count:"+repo.GitRepoManagedID(), ref.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveCommitsCountCache(repo RepositoryFacade, ref RefName) {
|
||||||
|
cache.Remove(makeCommitsCountCacheKey(repo, ref))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCommitsCountCache(ctx context.Context, repo RepositoryFacade, ref RefName, commit *Commit) (int64, error) {
|
||||||
|
if commit == nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return cache.GetInt64(makeCommitsCountCacheKey(repo, ref), func() (int64, error) {
|
||||||
|
return CommitsCountOfCommit(ctx, repo, commit.ID.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -60,15 +60,9 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, timeout time.Duration, re
|
|||||||
entryNames = append(entryNames, entry.Name())
|
entryNames = append(entryNames, entry.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
var revs map[string]*Commit
|
revs, remainingEntryNames, err := getLastCommitForPathsByCache(ctx, commit.ID.String(), treePath, entryNames, gitRepo.LastCommitCache)
|
||||||
var remainingEntryNames []string
|
if err != nil {
|
||||||
if gitRepo.LastCommitCache != nil {
|
return nil, nil, err
|
||||||
revs, remainingEntryNames, err = getLastCommitForPathsByCache(ctx, commit.ID.String(), treePath, entryNames, gitRepo.LastCommitCache)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
revs, remainingEntryNames = map[string]*Commit{}, entryNames
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(remainingEntryNames) > 0 {
|
if len(remainingEntryNames) > 0 {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.dev/modules/git/gitrepo"
|
||||||
"gitea.dev/modules/test"
|
"gitea.dev/modules/test"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
|
|
||||||
@@ -19,7 +20,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) {
|
func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) {
|
||||||
repo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
|
repoPath, _ := filepath.Abs(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
|
repo, err := OpenRepository(t.Context(), gitrepo.RepositoryManaged("dummy", repoPath))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.dev/modules/git/gitrepo"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -126,9 +128,9 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEntries_GetCommitsInfo(t *testing.T) {
|
func TestEntries_GetCommitsInfo(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
bareRepo1Path, _ := filepath.Abs(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path)
|
bareRepo1, err := OpenRepository(t.Context(), gitrepo.RepositoryManaged("repo1_bare", bareRepo1Path))
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer bareRepo1.Close()
|
defer bareRepo1.Close()
|
||||||
|
|
||||||
testGetCommitsInfo(t, bareRepo1)
|
testGetCommitsInfo(t, bareRepo1)
|
||||||
@@ -137,7 +139,7 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
clonedRepo1, err := OpenRepositoryLocal(t.Context(), clonedPath)
|
clonedRepo1, err := OpenRepository(t.Context(), gitrepo.RepositoryManaged("repo1_bare-clone", clonedPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gitea.dev/modules/cache"
|
||||||
"gitea.dev/modules/git/gitcmd"
|
"gitea.dev/modules/git/gitcmd"
|
||||||
"gitea.dev/modules/globallock"
|
"gitea.dev/modules/globallock"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
@@ -191,6 +192,7 @@ func RunGitTests(m interface{ Run() int }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runGitTests(m interface{ Run() int }) int {
|
func runGitTests(m interface{ Run() int }) int {
|
||||||
|
_ = cache.Init()
|
||||||
gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home")
|
gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return testlogger.MainErrorf("unable to create temp dir: %v", err)
|
return testlogger.MainErrorf("unable to create temp dir: %v", err)
|
||||||
|
|||||||
@@ -5,103 +5,69 @@ package git
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gitea.dev/modules/cache"
|
"gitea.dev/modules/cache"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/setting"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCacheKey(repoPath, commitID, entryPath string) string {
|
func getCacheKey(repo RepositoryFacade, commitID, entryPath string) string {
|
||||||
hashBytes := sha256.Sum256(fmt.Appendf(nil, "%s:%s:%s", repoPath, commitID, entryPath))
|
return cache.SafeCacheKey(fmt.Sprintf("git-last-commit:%s:%s", repo.GitRepoManagedID(), commitID), entryPath)
|
||||||
return fmt.Sprintf("last_commit:%x", hashBytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastCommitCache represents a cache to store last commit
|
// LastCommitCache represents a cache to store last commit
|
||||||
type LastCommitCache struct {
|
type LastCommitCache struct {
|
||||||
repoPath string
|
ttlFn func() int64
|
||||||
ttl func() int64
|
|
||||||
repo *Repository
|
repo *Repository
|
||||||
commitCache map[string]*Commit
|
commitCache map[string]*Commit
|
||||||
cache cache.StringCache
|
cache cache.StringCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLastCommitCache creates a new last commit cache for repo
|
// Put puts the last commit id with commit and entry path
|
||||||
func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache cache.StringCache) *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 {
|
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)
|
log.Debug("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
|
||||||
return c.cache.Put(getCacheKey(c.repoPath, ref, entryPath), commitID, c.ttl())
|
return c.cache.Put(getCacheKey(c.repo, ref, entryPath), commitID, c.ttlFn())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets the last commit information by commit id and entry path
|
// Get gets the last commit information by commit id and entry path
|
||||||
func (c *LastCommitCache) Get(ctx context.Context, ref, entryPath string) (*Commit, error) {
|
func (c *LastCommitCache) Get(ctx context.Context, ref, entryPath string) (*Commit, error) {
|
||||||
if c == nil || c.cache == nil {
|
lastCommitID, ok := c.cache.Get(getCacheKey(c.repo, ref, entryPath))
|
||||||
return nil, nil //nolint:nilnil // return nil when cache is not available
|
if !ok || lastCommitID == "" {
|
||||||
}
|
|
||||||
|
|
||||||
commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath))
|
|
||||||
if !ok || commitID == "" {
|
|
||||||
return nil, nil //nolint:nilnil // return nil when cache miss
|
return nil, nil //nolint:nilnil // return nil when cache miss
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, commitID)
|
log.Debug("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, lastCommitID)
|
||||||
if c.commitCache != nil {
|
if lastCommit, ok := c.commitCache[lastCommitID]; ok {
|
||||||
if commit, ok := c.commitCache[commitID]; ok {
|
log.Debug("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, lastCommitID)
|
||||||
log.Debug("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, commitID)
|
return lastCommit, nil
|
||||||
return commit, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commit, err := c.repo.GetCommit(ctx, commitID)
|
lastCommit, err := c.repo.GetCommit(ctx, lastCommitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if c.commitCache == nil {
|
if c.commitCache == nil {
|
||||||
c.commitCache = make(map[string]*Commit)
|
c.commitCache = make(map[string]*Commit)
|
||||||
}
|
}
|
||||||
c.commitCache[commitID] = commit
|
c.commitCache[lastCommitID] = lastCommit
|
||||||
return commit, nil
|
return lastCommit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommitByPath gets the last commit for the entry in the provided commit
|
// GetCommitByPath gets the last commit for the entry in the provided commit
|
||||||
func (c *LastCommitCache) GetCommitByPath(ctx context.Context, commitID, entryPath string) (*Commit, error) {
|
func (c *LastCommitCache) GetCommitByPath(ctx context.Context, entryCommitID ObjectID, entryPath string) (*Commit, error) {
|
||||||
sha, err := NewIDFromString(commitID)
|
entryCommitIDStr := entryCommitID.String()
|
||||||
if err != nil {
|
lastCommit, err := c.Get(ctx, entryCommitIDStr, entryPath)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lastCommit, err := c.Get(ctx, sha.String(), entryPath)
|
|
||||||
if err != nil || lastCommit != nil {
|
if err != nil || lastCommit != nil {
|
||||||
return lastCommit, err
|
return lastCommit, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lastCommit, err = c.repo.getCommitByPathWithID(ctx, sha, entryPath)
|
lastCommit, err = c.repo.getCommitByPathWithID(ctx, entryCommitID, entryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Put(commitID, entryPath, lastCommit.ID.String()); err != nil {
|
if err := c.Put(entryCommitIDStr, 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)
|
log.Error("Unable to cache %s as the last commit for %q in %s %s. Error %v", lastCommit.ID.String(), entryPath, entryCommitID, c.repo.LogString(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return lastCommit, nil
|
return lastCommit, nil
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ import (
|
|||||||
|
|
||||||
// CacheCommit will cache the commit from the gitRepository
|
// CacheCommit will cache the commit from the gitRepository
|
||||||
func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error {
|
func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error {
|
||||||
if gitRepo.LastCommitCache == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
commitNodeIndex, closer := gitRepo.CommitNodeIndex()
|
commitNodeIndex, closer := gitRepo.CommitNodeIndex()
|
||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ import (
|
|||||||
|
|
||||||
// CacheCommit will cache the commit from the gitRepository
|
// CacheCommit will cache the commit from the gitRepository
|
||||||
func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error {
|
func (c *Commit) CacheCommit(ctx context.Context, gitRepo *Repository) error {
|
||||||
if gitRepo.LastCommitCache == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.recursiveCache(ctx, gitRepo, c.Tree(), "", 1)
|
return c.recursiveCache(ctx, gitRepo, c.Tree(), "", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+55
-65
@@ -5,10 +5,10 @@ package git
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NotesRef is the git ref where Gitea will look for git-notes data.
|
// NotesRef is the git ref where Gitea will look for git-notes data.
|
||||||
@@ -17,83 +17,73 @@ const NotesRef = "refs/notes/commits"
|
|||||||
|
|
||||||
// Note stores information about a note created using git-notes.
|
// Note stores information about a note created using git-notes.
|
||||||
type Note struct {
|
type Note struct {
|
||||||
Message []byte
|
refCommit *Commit
|
||||||
Commit *Commit
|
|
||||||
|
BlobMessage CommitMessage // if the blob is too large, the message will be truncated
|
||||||
|
BlobSize int64
|
||||||
|
TreePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNote retrieves the git-notes data for a given commit.
|
// GetNote retrieves the git-notes data for a given commit.
|
||||||
// FIXME: Add LastCommitCache support
|
func GetNote(ctx context.Context, repo *Repository, commitID string) (*Note, error) {
|
||||||
func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error {
|
noteCommit, err := repo.GetCommit(ctx, NotesRef)
|
||||||
log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.LogString())
|
|
||||||
notes, err := repo.GetCommit(ctx, NotesRef)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if IsErrNotExist(err) {
|
return nil, err
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
path := ""
|
// A note for a commit is stored in a blob in the notes commit tree, with the path being the commit ID.
|
||||||
|
// The path can be "FullCommitID" or a fanout path like "ab/cdef...." or "ab/cd/ef.....".
|
||||||
tree := notes.Tree()
|
tree := noteCommit.Tree()
|
||||||
log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", tree.ID, commitID)
|
entryName := commitID
|
||||||
|
|
||||||
var entry *TreeEntry
|
var entry *TreeEntry
|
||||||
originalCommitID := commitID
|
var treePathBuf strings.Builder
|
||||||
for len(commitID) > 2 {
|
for len(entryName) > 2 {
|
||||||
entry, err = tree.GetTreeEntryByPath(ctx, repo, commitID)
|
entry, err = tree.GetTreeEntryByPath(ctx, repo, entryName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
path += commitID
|
treePathBuf.WriteString(entryName)
|
||||||
break
|
break
|
||||||
}
|
} else if IsErrNotExist(err) {
|
||||||
if IsErrNotExist(err) {
|
fanoutDir, fanoutName := entryName[0:2], entryName[2:]
|
||||||
tree, err = tree.SubTree(ctx, repo, commitID[0:2])
|
tree, err = tree.SubTree(ctx, repo, fanoutDir)
|
||||||
path += commitID[0:2] + "/"
|
if err != nil {
|
||||||
commitID = commitID[2:]
|
return nil, err
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
// Err may have been updated by the SubTree we need to recheck if it's again an ErrNotExist
|
|
||||||
if !IsErrNotExist(err) {
|
|
||||||
log.Error("Unable to find git note corresponding to the commit %q. Error: %v", originalCommitID, err)
|
|
||||||
}
|
}
|
||||||
return err
|
treePathBuf.WriteString(fanoutDir)
|
||||||
|
treePathBuf.WriteByte('/')
|
||||||
|
entryName = fanoutName
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if entry == nil {
|
||||||
|
return nil, ErrNotExist{ID: commitID}
|
||||||
|
}
|
||||||
|
|
||||||
|
treePath := treePathBuf.String()
|
||||||
blob := entry.Blob(repo)
|
blob := entry.Blob(repo)
|
||||||
dataRc, err := blob.DataAsync(ctx)
|
note := &Note{TreePath: treePath, refCommit: noteCommit}
|
||||||
|
note.BlobMessage.MessageRaw, err = blob.GetBlobContent(ctx, setting.UI.MaxDisplayFileSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
|
return nil, err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
closed := false
|
note.BlobSize = blob.Size(ctx) // it should be called after the get blob content, then the "size" is cached
|
||||||
defer func() {
|
return note, nil
|
||||||
if !closed {
|
}
|
||||||
_ = dataRc.Close()
|
|
||||||
}
|
func GetNoteWithLastCommit(ctx context.Context, repo *Repository, commitID string) (*Note, *Commit, error) {
|
||||||
}()
|
note, err := GetNote(ctx, repo, commitID)
|
||||||
d, err := io.ReadAll(dataRc)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, nil, err
|
||||||
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
|
}
|
||||||
return err
|
parentPath, entryName := path.Split(note.TreePath)
|
||||||
}
|
parentPath = strings.Trim(parentPath, "/")
|
||||||
_ = dataRc.Close()
|
lastCommits, err := GetLastCommitForPaths(ctx, repo, note.refCommit, parentPath, []string{entryName})
|
||||||
closed = true
|
if err != nil {
|
||||||
note.Message = d
|
return nil, nil, err
|
||||||
|
}
|
||||||
treePath := ""
|
lastCommit := lastCommits[entryName]
|
||||||
if idx := strings.LastIndex(path, "/"); idx > -1 {
|
if lastCommit == nil {
|
||||||
treePath = path[:idx]
|
return nil, nil, ErrNotExist{ID: commitID}
|
||||||
path = path[idx+1:]
|
}
|
||||||
}
|
return note, lastCommit, nil
|
||||||
|
|
||||||
lastCommits, err := GetLastCommitForPaths(ctx, repo, notes, treePath, []string{path})
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
note.Commit = lastCommits[path]
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-32
@@ -7,45 +7,40 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gitea.dev/modules/git/gitrepo"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetNotes(t *testing.T) {
|
func TestGetNote(t *testing.T) {
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
repo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
|
||||||
bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer bareRepo1.Close()
|
|
||||||
|
|
||||||
note := Note{}
|
|
||||||
err = GetNote(t.Context(), bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", ¬e)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, []byte("Note contents\n"), note.Message)
|
|
||||||
assert.Equal(t, "Vladimir Panteleev", note.Commit.Author.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetNestedNotes(t *testing.T) {
|
|
||||||
repoPath := filepath.Join(testReposDir, "repo3_notes")
|
|
||||||
repo, err := OpenRepositoryLocal(t.Context(), repoPath)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer repo.Close()
|
defer repo.Close()
|
||||||
|
|
||||||
note := Note{}
|
note, err := GetNote(t.Context(), repo, "95bb4d39648ee7e325106df01a621c530863a653")
|
||||||
err = GetNote(t.Context(), repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4", ¬e)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []byte("Note 2"), note.Message)
|
assert.Equal(t, "Note contents\n", note.BlobMessage.MessageUTF8())
|
||||||
err = GetNote(t.Context(), repo, "ba0a96fa63532d6c5087ecef070b0250ed72fa47", ¬e)
|
assert.EqualValues(t, len(note.BlobMessage.MessageRaw), note.BlobSize)
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, []byte("Note 1"), note.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetNonExistentNotes(t *testing.T) {
|
_, err = GetNote(t.Context(), repo, "non_existent_sha")
|
||||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
|
||||||
bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer bareRepo1.Close()
|
|
||||||
|
|
||||||
note := Note{}
|
|
||||||
err = GetNote(t.Context(), bareRepo1, "non_existent_sha", ¬e)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.ErrorAs(t, err, &ErrNotExist{})
|
assert.ErrorAs(t, err, &ErrNotExist{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetNoteNestedWithCache(t *testing.T) {
|
||||||
|
repoPath, _ := filepath.Abs(filepath.Join(testReposDir, "repo3_notes"))
|
||||||
|
repo, err := OpenRepository(t.Context(), gitrepo.RepositoryManaged("repo3_notes", repoPath))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer repo.Close()
|
||||||
|
|
||||||
|
note, lastCommit, err := GetNoteWithLastCommit(t.Context(), repo, "ba0a96fa63532d6c5087ecef070b0250ed72fa47")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "Note 1", note.BlobMessage.MessageUTF8())
|
||||||
|
assert.Equal(t, "ba0a96fa63532d6c5087ecef070b0250ed72fa47", note.TreePath)
|
||||||
|
assert.Equal(t, "Filip Navara", lastCommit.Author.Name)
|
||||||
|
|
||||||
|
note, lastCommit, err = GetNoteWithLastCommit(t.Context(), repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "Note 2", note.BlobMessage.MessageUTF8())
|
||||||
|
assert.Equal(t, "3e/66/8dbfac39cbc80a9ff9c61eb565d944453ba4", note.TreePath)
|
||||||
|
assert.Equal(t, "Filip Navara", lastCommit.Author.Name)
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.dev/modules/cache"
|
||||||
"gitea.dev/modules/git/gitcmd"
|
"gitea.dev/modules/git/gitcmd"
|
||||||
"gitea.dev/modules/git/gitrepo"
|
"gitea.dev/modules/git/gitrepo"
|
||||||
"gitea.dev/modules/proxy"
|
"gitea.dev/modules/proxy"
|
||||||
@@ -71,6 +72,11 @@ func OpenRepository(catFileBatchCtx context.Context, repo RepositoryFacade) (*Re
|
|||||||
gitRepo := &Repository{
|
gitRepo := &Repository{
|
||||||
RepositoryBase: RepositoryBase{tagCache: newObjectCache[*Tag](), repoFacade: repo, catFileBatchCtx: catFileBatchCtx},
|
RepositoryBase: RepositoryBase{tagCache: newObjectCache[*Tag](), repoFacade: repo, catFileBatchCtx: catFileBatchCtx},
|
||||||
}
|
}
|
||||||
|
gitRepo.RepositoryBase.LastCommitCache = &LastCommitCache{
|
||||||
|
repo: gitRepo,
|
||||||
|
ttlFn: setting.LastCommitCacheTTLSeconds,
|
||||||
|
cache: cache.GetCache(),
|
||||||
|
}
|
||||||
if err = openRepositoryInternal(gitRepo); err != nil {
|
if err = openRepositoryInternal(gitRepo); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
// Cache represents cache settings
|
// Cache represents cache settings
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
Adapter string
|
Adapter string
|
||||||
Interval int
|
Interval int // GC
|
||||||
Conn string `ini:"-"`
|
Conn string `ini:"-"`
|
||||||
TTL time.Duration `ini:"ITEM_TTL"`
|
TTL time.Duration `ini:"ITEM_TTL"`
|
||||||
}
|
}
|
||||||
@@ -22,8 +22,7 @@ var CacheService = struct {
|
|||||||
Cache `ini:"cache"`
|
Cache `ini:"cache"`
|
||||||
|
|
||||||
LastCommit struct {
|
LastCommit struct {
|
||||||
TTL time.Duration `ini:"ITEM_TTL"`
|
TTL time.Duration `ini:"ITEM_TTL"`
|
||||||
CommitsCount int64
|
|
||||||
} `ini:"cache.last_commit"`
|
} `ini:"cache.last_commit"`
|
||||||
}{
|
}{
|
||||||
Cache: Cache{
|
Cache: Cache{
|
||||||
@@ -32,11 +31,9 @@ var CacheService = struct {
|
|||||||
TTL: 16 * time.Hour,
|
TTL: 16 * time.Hour,
|
||||||
},
|
},
|
||||||
LastCommit: struct {
|
LastCommit: struct {
|
||||||
TTL time.Duration `ini:"ITEM_TTL"`
|
TTL time.Duration `ini:"ITEM_TTL"`
|
||||||
CommitsCount int64
|
|
||||||
}{
|
}{
|
||||||
TTL: 8760 * time.Hour,
|
TTL: 8760 * time.Hour,
|
||||||
CommitsCount: 1000,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,9 +58,6 @@ func loadCacheFrom(rootCfg ConfigProvider) {
|
|||||||
default:
|
default:
|
||||||
log.Fatal("Unknown cache adapter: %s", CacheService.Adapter)
|
log.Fatal("Unknown cache adapter: %s", CacheService.Adapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
sec = rootCfg.Section("cache.last_commit")
|
|
||||||
CacheService.LastCommit.CommitsCount = sec.Key("COMMITS_COUNT").MustInt64(1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TTLSeconds returns the TTLSeconds or unix timestamp for memcache
|
// TTLSeconds returns the TTLSeconds or unix timestamp for memcache
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
@@ -60,32 +59,23 @@ func GetNote(ctx *context.APIContext) {
|
|||||||
getNote(ctx, sha)
|
getNote(ctx, sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNote(ctx *context.APIContext, identifier string) {
|
func getNote(ctx *context.APIContext, ref string) {
|
||||||
if ctx.Repo.GitRepo == nil {
|
commit, err := ctx.Repo.GitRepo.GetCommit(ctx, ref)
|
||||||
ctx.APIErrorInternal(errors.New("no open git repo"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
commitID, err := ctx.Repo.GitRepo.ConvertToGitID(ctx, identifier)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorAuto(err)
|
ctx.APIErrorAuto(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var note git.Note
|
note, lastCommit, err := git.GetNoteWithLastCommit(ctx, ctx.Repo.GitRepo, commit.ID.String())
|
||||||
if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitID.String(), ¬e); err != nil {
|
if err != nil {
|
||||||
if git.IsErrNotExist(err) {
|
ctx.APIErrorAuto(err)
|
||||||
ctx.APIErrorNotFound("commit doesn't exist: " + identifier)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.APIErrorInternal(err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
|
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
|
||||||
files := ctx.FormString("files") == "" || ctx.FormBool("files")
|
files := ctx.FormString("files") == "" || ctx.FormBool("files")
|
||||||
|
|
||||||
cmt, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, note.Commit, nil,
|
cmt, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, lastCommit, nil,
|
||||||
convert.ToCommitOptions{
|
convert.ToCommitOptions{
|
||||||
Stat: true,
|
Stat: true,
|
||||||
Verification: verification,
|
Verification: verification,
|
||||||
@@ -95,6 +85,6 @@ func getNote(ctx *context.APIContext, identifier string) {
|
|||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
apiNote := api.Note{Message: string(note.Message), Commit: cmt}
|
apiNote := api.Note{Message: note.BlobMessage.MessageUTF8(), Commit: cmt}
|
||||||
ctx.JSON(http.StatusOK, apiNote)
|
ctx.JSON(http.StatusOK, apiNote)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ package repo
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -21,9 +20,9 @@ import (
|
|||||||
unit_model "gitea.dev/models/unit"
|
unit_model "gitea.dev/models/unit"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
"gitea.dev/modules/base"
|
"gitea.dev/modules/base"
|
||||||
"gitea.dev/modules/charset"
|
|
||||||
"gitea.dev/modules/fileicon"
|
"gitea.dev/modules/fileicon"
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
|
"gitea.dev/modules/htmlutil"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/markup"
|
"gitea.dev/modules/markup"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
@@ -406,13 +405,12 @@ func Diff(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
note := &git.Note{}
|
note, noteLastCommit, err := git.GetNoteWithLastCommit(ctx, gitRepo, commitID)
|
||||||
err = git.GetNote(ctx, gitRepo, commitID, note)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ctx.Data["NoteCommit"] = note.Commit
|
ctx.Data["NoteCommit"] = noteLastCommit
|
||||||
ctx.Data["NoteAuthor"] = user_model.GetUserByGitAuthor(ctx, note.Commit)
|
ctx.Data["NoteAuthor"] = user_model.GetUserByGitAuthor(ctx, noteLastCommit)
|
||||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{CurrentRefSubURL: "commit/" + util.PathEscapeSegments(commitID)})
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{CurrentRefSubURL: "commit/" + util.PathEscapeSegments(commitID)})
|
||||||
htmlMessage := template.HTML(template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
|
htmlMessage := htmlutil.EscapeString(note.BlobMessage.MessageUTF8())
|
||||||
ctx.Data["NoteRendered"] = markup.PostProcessCommitMessage(rctx, htmlMessage)
|
ctx.Data["NoteRendered"] = markup.PostProcessCommitMessage(rctx, htmlMessage)
|
||||||
} else if !git.IsErrNotExist(err) {
|
} else if !git.IsErrNotExist(err) {
|
||||||
log.Error("GetNote: %v", err)
|
log.Error("GetNote: %v", err)
|
||||||
|
|||||||
@@ -251,18 +251,6 @@ func (r *Repository) CanCreateIssueDependencies(ctx context.Context, user *user_
|
|||||||
return r.Repository.IsDependenciesEnabled(ctx) && r.Permission.CanWriteIssuesOrPulls(isPull)
|
return r.Repository.IsDependenciesEnabled(ctx) && r.Permission.CanWriteIssuesOrPulls(isPull)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommitsCount returns cached commit count for current view
|
|
||||||
func (r *Repository) GetCommitsCount(ctx context.Context) (int64, error) {
|
|
||||||
if r.Commit == nil {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
contextName := r.RefFullName.ShortName()
|
|
||||||
isRef := r.RefFullName.IsBranch() || r.RefFullName.IsTag()
|
|
||||||
return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, isRef), func() (int64, error) {
|
|
||||||
return git.CommitsCountOfCommit(ctx, r.Repository, r.Commit.ID.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCommitGraphsCount returns cached commit count for current view
|
// GetCommitGraphsCount returns cached commit count for current view
|
||||||
func (r *Repository) GetCommitGraphsCount(ctx context.Context, hidePRRefs bool, branches, files []string) (int64, error) {
|
func (r *Repository) GetCommitGraphsCount(ctx context.Context, hidePRRefs bool, branches, files []string) (int64, error) {
|
||||||
cacheKey := fmt.Sprintf("commits-count-%d-graph-%t-%s-%s", r.Repository.ID, hidePRRefs, branches, files)
|
cacheKey := fmt.Sprintf("commits-count-%d-graph-%t-%s-%s", r.Repository.ID, hidePRRefs, branches, files)
|
||||||
@@ -906,7 +894,7 @@ func RepoRefByDefaultBranch() func(*Context) {
|
|||||||
ctx.Repo.RefFullName = git.RefNameFromBranch(ctx.Repo.Repository.DefaultBranch)
|
ctx.Repo.RefFullName = git.RefNameFromBranch(ctx.Repo.Repository.DefaultBranch)
|
||||||
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
|
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
|
||||||
ctx.Repo.Commit, _ = ctx.Repo.GitRepo.GetBranchCommit(ctx, ctx.Repo.BranchName)
|
ctx.Repo.Commit, _ = ctx.Repo.GitRepo.GetBranchCommit(ctx, ctx.Repo.BranchName)
|
||||||
ctx.Repo.CommitsCount, _ = ctx.Repo.GetCommitsCount(ctx)
|
ctx.Repo.CommitsCount, _ = git.GetCommitsCountCache(ctx, ctx.Repo.Repository, ctx.Repo.RefFullName, ctx.Repo.Commit)
|
||||||
ctx.Data["RefFullName"] = ctx.Repo.RefFullName
|
ctx.Data["RefFullName"] = ctx.Repo.RefFullName
|
||||||
ctx.Data["BranchName"] = ctx.Repo.BranchName
|
ctx.Data["BranchName"] = ctx.Repo.BranchName
|
||||||
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
||||||
@@ -1053,7 +1041,7 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) {
|
|||||||
|
|
||||||
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() // only used by the branch selector dropdown: AllowCreateNewRef
|
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() // only used by the branch selector dropdown: AllowCreateNewRef
|
||||||
|
|
||||||
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount(ctx)
|
ctx.Repo.CommitsCount, err = git.GetCommitsCountCache(ctx, ctx.Repo.Repository, ctx.Repo.RefFullName, ctx.Repo.Commit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetCommitsCount", err)
|
ctx.ServerError("GetCommitsCount", err)
|
||||||
return
|
return
|
||||||
@@ -1068,7 +1056,6 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
||||||
ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
system_model "gitea.dev/models/system"
|
system_model "gitea.dev/models/system"
|
||||||
"gitea.dev/modules/cache"
|
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
"gitea.dev/modules/git/gitcmd"
|
"gitea.dev/modules/git/gitcmd"
|
||||||
giturl "gitea.dev/modules/git/url"
|
giturl "gitea.dev/modules/git/url"
|
||||||
@@ -266,7 +265,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*repo_module.SyncResu
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, branch := range branches {
|
for _, branch := range branches {
|
||||||
cache.Remove(m.Repo.GetCommitsCountCacheKey(branch, true))
|
git.RemoveCommitsCountCache(m.Repo, git.RefNameFromBranch(branch))
|
||||||
}
|
}
|
||||||
|
|
||||||
m.UpdatedUnix = timeutil.TimeStampNow()
|
m.UpdatedUnix = timeutil.TimeStampNow()
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import (
|
|||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
"gitea.dev/models/unit"
|
"gitea.dev/models/unit"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
"gitea.dev/modules/cache"
|
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
"gitea.dev/modules/git/gitcmd"
|
"gitea.dev/modules/git/gitcmd"
|
||||||
"gitea.dev/modules/globallock"
|
"gitea.dev/modules/globallock"
|
||||||
@@ -294,8 +293,7 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset cached commit count
|
// Reset cached commit count
|
||||||
cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))
|
git.RemoveCommitsCountCache(pr.Issue.Repo, git.RefNameFromBranch(pr.BaseBranch))
|
||||||
|
|
||||||
return handleCloseCrossReferences(ctx, pr, doer)
|
return handleCloseCrossReferences(ctx, pr, doer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,27 +6,14 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
repo_model "gitea.dev/models/repo"
|
|
||||||
"gitea.dev/modules/cache"
|
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CacheRef cachhe last commit information of the branch or the tag
|
// CacheRef caches last commit information of the branch or the tag
|
||||||
func CacheRef(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, fullRefName git.RefName) error {
|
func CacheRef(ctx context.Context, gitRepo *git.Repository, fullRefName git.RefName) error {
|
||||||
commit, err := gitRepo.GetCommit(ctx, fullRefName.String())
|
commit, err := gitRepo.GetCommit(ctx, fullRefName.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if gitRepo.LastCommitCache == nil {
|
|
||||||
commitsCount, err := cache.GetInt64(repo.GetCommitsCountCacheKey(fullRefName.ShortName(), true), func() (int64, error) {
|
|
||||||
return git.CommitsCountOfCommit(ctx, repo, commit.ID.String())
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gitRepo.LastCommitCache = git.NewLastCommitCache(commitsCount, repo.FullName(), gitRepo, cache.GetCache())
|
|
||||||
}
|
|
||||||
|
|
||||||
return commit.CacheCommit(ctx, gitRepo)
|
return commit.CacheCommit(ctx, gitRepo)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
"gitea.dev/modules/cache"
|
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
"gitea.dev/modules/lfs"
|
"gitea.dev/modules/lfs"
|
||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
@@ -121,21 +120,7 @@ func GetFileContents(ctx context.Context, repo *repo_model.Repository, gitRepo *
|
|||||||
return getFileContentsByEntryInternal(ctx, repo, gitRepo, refCommit, entry, opts)
|
return getFileContentsByEntryInternal(ctx, repo, gitRepo, refCommit, entry, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addLastCommitCache(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, cacheKey, fullName, sha string) error {
|
|
||||||
if gitRepo.LastCommitCache == nil {
|
|
||||||
commitsCount, err := cache.GetInt64(cacheKey, func() (int64, error) {
|
|
||||||
return git.CommitsCountOfCommit(ctx, repo, sha)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gitRepo.LastCommitCache = git.NewLastCommitCache(commitsCount, fullName, gitRepo, cache.GetCache())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFileContentsByEntryInternal(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, entry *git.TreeEntry, opts GetContentsOrListOptions) (*api.ContentsResponse, error) {
|
func getFileContentsByEntryInternal(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, entry *git.TreeEntry, opts GetContentsOrListOptions) (*api.ContentsResponse, error) {
|
||||||
refType := refCommit.RefName.RefType()
|
|
||||||
commit := refCommit.Commit
|
commit := refCommit.Commit
|
||||||
selfURL, err := url.Parse(repo.APIURL() + "/contents/" + util.PathEscapeSegments(opts.TreePath) + "?ref=" + url.QueryEscape(refCommit.InputRef))
|
selfURL, err := url.Parse(repo.APIURL() + "/contents/" + util.PathEscapeSegments(opts.TreePath) + "?ref=" + url.QueryEscape(refCommit.InputRef))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -157,11 +142,6 @@ func getFileContentsByEntryInternal(ctx context.Context, repo *repo_model.Reposi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.IncludeCommitMetadata || opts.IncludeCommitMessage {
|
if opts.IncludeCommitMetadata || opts.IncludeCommitMessage {
|
||||||
err = addLastCommitCache(ctx, repo, gitRepo, repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lastCommit, err := refCommit.Commit.GetCommitByPath(ctx, gitRepo, opts.TreePath)
|
lastCommit, err := refCommit.Commit.GetCommitByPath(ctx, gitRepo, opts.TreePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
"gitea.dev/modules/cache"
|
|
||||||
"gitea.dev/modules/git"
|
"gitea.dev/modules/git"
|
||||||
"gitea.dev/modules/graceful"
|
"gitea.dev/modules/graceful"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
@@ -205,7 +204,7 @@ func pushQueueHandleUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
notify_service.PushCommits(ctx, pusher, repo, opts, commits)
|
notify_service.PushCommits(ctx, pusher, repo, opts, commits)
|
||||||
|
|
||||||
// Cache for big repository
|
// Cache for big repository
|
||||||
if err := CacheRef(graceful.GetManager().HammerContext(), repo, gitRepo, opts.RefFullName); err != nil {
|
if err := CacheRef(graceful.GetManager().HammerContext(), gitRepo, opts.RefFullName); err != nil {
|
||||||
log.Error("repo_module.CacheRef %s/%s failed: %v", repo.ID, branch, err)
|
log.Error("repo_module.CacheRef %s/%s failed: %v", repo.ID, branch, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -309,16 +308,7 @@ func pushUpdateBranch(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||||||
OldCommitID: opts.OldCommitID,
|
OldCommitID: opts.OldCommitID,
|
||||||
NewCommitID: opts.NewCommitID,
|
NewCommitID: opts.NewCommitID,
|
||||||
})
|
})
|
||||||
|
git.RemoveCommitsCountCache(repo, opts.RefFullName)
|
||||||
if isForcePush {
|
|
||||||
log.Trace("Push %s is a force push", opts.NewCommitID)
|
|
||||||
|
|
||||||
cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))
|
|
||||||
} else {
|
|
||||||
// TODO: increment update the commit count cache but not remove
|
|
||||||
cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))
|
|
||||||
}
|
|
||||||
|
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,14 +132,8 @@ func testViewRepoWithCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: these test don't seem quite right, no enough assert
|
// FIXME: these test don't seem quite right, no enough assert
|
||||||
// no last commit cache
|
testView(t) // first view will not hit the cache, need execute git operations
|
||||||
testView(t)
|
testView(t) // second view will hit the cache
|
||||||
// enable last commit cache for all repositories
|
|
||||||
defer test.MockVariableValue(&setting.CacheService.LastCommit.CommitsCount, 0)()
|
|
||||||
// first view will not hit the cache
|
|
||||||
testView(t)
|
|
||||||
// second view will hit the cache
|
|
||||||
testView(t)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testViewRepoPrivate(t *testing.T) {
|
func testViewRepoPrivate(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user