drone/gitrpc/internal/gitea/last_commit_cache.go

161 lines
3.9 KiB
Go

// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package gitea
import (
"context"
"crypto/sha256"
"encoding/gob"
"encoding/hex"
"fmt"
"strings"
"time"
"github.com/harness/gitness/cache"
"github.com/harness/gitness/gitrpc/internal/types"
gitea "code.gitea.io/gitea/modules/git"
gogitplumbing "github.com/go-git/go-git/v5/plumbing"
"github.com/go-redis/redis/v8"
)
func NewInMemoryLastCommitCache(
cacheDuration time.Duration,
repoProvider *GoGitRepoProvider,
) cache.Cache[CommitEntryKey, *types.Commit] {
return cache.New[CommitEntryKey, *types.Commit](
commitEntryGetter{
repoProvider: repoProvider,
},
cacheDuration)
}
func NewRedisLastCommitCache(
redisClient redis.UniversalClient,
cacheDuration time.Duration,
repoProvider *GoGitRepoProvider,
) cache.Cache[CommitEntryKey, *types.Commit] {
return cache.NewRedis[CommitEntryKey, *types.Commit](
redisClient,
commitEntryGetter{
repoProvider: repoProvider,
},
func(key CommitEntryKey) string {
h := sha256.New()
h.Write([]byte(key))
return "gitrpc:last_commit:" + hex.EncodeToString(h.Sum(nil))
},
commitValueCodec{},
cacheDuration)
}
func NoLastCommitCache(
repoProvider *GoGitRepoProvider,
) cache.Cache[CommitEntryKey, *types.Commit] {
return cache.NewNoCache[CommitEntryKey, *types.Commit](commitEntryGetter{repoProvider: repoProvider})
}
type CommitEntryKey string
const commitEntryKeySeparator = "\x00"
func makeCommitEntryKey(repoPath, commitSHA, path string) CommitEntryKey {
return CommitEntryKey(repoPath + commitEntryKeySeparator + commitSHA + commitEntryKeySeparator + path)
}
func (c CommitEntryKey) Split() (repoPath, commitSHA, path string) {
parts := strings.Split(string(c), commitEntryKeySeparator)
if len(parts) != 3 {
return
}
repoPath = parts[0]
commitSHA = parts[1]
path = parts[2]
return
}
type commitValueCodec struct{}
func (c commitValueCodec) Encode(v *types.Commit) string {
buffer := &strings.Builder{}
_ = gob.NewEncoder(buffer).Encode(v)
return buffer.String()
}
func (c commitValueCodec) Decode(s string) (*types.Commit, error) {
commit := &types.Commit{}
if err := gob.NewDecoder(strings.NewReader(s)).Decode(commit); err != nil {
return nil, fmt.Errorf("failed to unpack commit entry value: %w", err)
}
return commit, nil
}
type commitEntryGetter struct {
repoProvider *GoGitRepoProvider
}
// Find implements the cache.Getter interface.
func (c commitEntryGetter) Find(ctx context.Context, key CommitEntryKey) (*types.Commit, error) {
repoPath, rev, path := key.Split()
if path == "" {
path = "."
}
args := []string{"log", "--max-count=1", "--format=%H", rev, "--", path}
commitSHA, _, runErr := gitea.NewCommand(ctx, args...).RunStdString(&gitea.RunOpts{Dir: repoPath})
if runErr != nil {
return nil, fmt.Errorf("failed to run git: %w", runErr)
}
commitSHA = strings.TrimSpace(commitSHA)
if commitSHA == "" {
return nil, types.ErrNotFound
}
repo, err := c.repoProvider.Get(ctx, repoPath)
if err != nil {
return nil, fmt.Errorf("failed to get repository %s from cache: %w", repoPath, err)
}
commit, err := repo.CommitObject(gogitplumbing.NewHash(commitSHA))
if err != nil {
return nil, fmt.Errorf("failed to load commit data: %w", err)
}
var title string
var message string
title = commit.Message
if idx := strings.IndexRune(commit.Message, '\n'); idx >= 0 {
title = commit.Message[:idx]
message = commit.Message[idx+1:]
}
return &types.Commit{
SHA: commitSHA,
Title: title,
Message: message,
Author: types.Signature{
Identity: types.Identity{
Name: commit.Author.Name,
Email: commit.Author.Email,
},
When: commit.Author.When,
},
Committer: types.Signature{
Identity: types.Identity{
Name: commit.Committer.Name,
Email: commit.Committer.Email,
},
When: commit.Committer.When,
},
}, nil
}