drone/git/adapter/last_commit_cache.go
2023-12-08 22:50:16 +00:00

180 lines
4.5 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package adapter
import (
"context"
"crypto/sha256"
"encoding/gob"
"encoding/hex"
"fmt"
"strconv"
"strings"
"time"
"github.com/harness/gitness/cache"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/git/types"
gitea "code.gitea.io/gitea/modules/git"
"github.com/go-redis/redis/v8"
)
func NewInMemoryLastCommitCache(
cacheDuration time.Duration,
) cache.Cache[CommitEntryKey, *types.Commit] {
return cache.New[CommitEntryKey, *types.Commit](
commitEntryGetter{},
cacheDuration)
}
func NewRedisLastCommitCache(
redisClient redis.UniversalClient,
cacheDuration time.Duration,
) (cache.Cache[CommitEntryKey, *types.Commit], error) {
if redisClient == nil {
return nil, errors.New("unable to create redis based LastCommitCache as redis client is nil")
}
return cache.NewRedis[CommitEntryKey, *types.Commit](
redisClient,
commitEntryGetter{},
func(key CommitEntryKey) string {
h := sha256.New()
h.Write([]byte(key))
return "last_commit:" + hex.EncodeToString(h.Sum(nil))
},
commitValueCodec{},
cacheDuration), nil
}
func NoLastCommitCache() cache.Cache[CommitEntryKey, *types.Commit] {
return cache.NewNoCache[CommitEntryKey, *types.Commit](commitEntryGetter{})
}
type CommitEntryKey string
const separatorZero = "\x00"
func makeCommitEntryKey(
repoPath string,
commitSHA string,
path string,
) CommitEntryKey {
return CommitEntryKey(repoPath + separatorZero + commitSHA + separatorZero + path)
}
func (c CommitEntryKey) Split() (
repoPath string,
commitSHA string,
path string,
) {
parts := strings.Split(string(c), separatorZero)
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{}
// Find implements the cache.Getter interface.
func (c commitEntryGetter) Find(
ctx context.Context,
key CommitEntryKey,
) (*types.Commit, error) {
repoPath, commitSHA, path := key.Split()
if path == "" {
path = "."
}
const format = "" +
fmtCommitHash + fmtZero + // 0
fmtAuthorName + fmtZero + // 1
fmtAuthorEmail + fmtZero + // 2
fmtAuthorUnix + fmtZero + // 3
fmtCommitterName + fmtZero + // 4
fmtCommitterEmail + fmtZero + // 5
fmtCommitterUnix + fmtZero + // 6
fmtSubject + fmtZero + // 7
fmtBody // 8
args := []string{"log", "--max-count=1", "--format=" + format, commitSHA, "--", path}
commitLine, _, err := gitea.NewCommand(ctx, args...).RunStdString(&gitea.RunOpts{Dir: repoPath})
if err != nil {
return nil, fmt.Errorf("failed to run git to get the last commit for path: %w", err)
}
const columnCount = 9
commitData := strings.Split(strings.TrimSpace(commitLine), separatorZero)
if len(commitData) != columnCount {
return nil, errors.InvalidArgument("path %q not found in commit %s", path, commitSHA)
}
sha := commitData[0]
authorName := commitData[1]
authorEmail := commitData[2]
authorTime, _ := strconv.ParseInt(commitData[3], 10, 64) // parse failure produces 01-01-1970
committerName := commitData[4]
committerEmail := commitData[5]
committerTime, _ := strconv.ParseInt(commitData[6], 10, 64) // parse failure produces 01-01-1970
subject := commitData[7]
body := commitData[8]
return &types.Commit{
SHA: sha,
Title: subject,
Message: body,
Author: types.Signature{
Identity: types.Identity{
Name: authorName,
Email: authorEmail,
},
When: time.Unix(authorTime, 0),
},
Committer: types.Signature{
Identity: types.Identity{
Name: committerName,
Email: committerEmail,
},
When: time.Unix(committerTime, 0),
},
}, nil
}