···
+
"github.com/dgraph-io/ristretto"
+
"github.com/go-git/go-git/v5/plumbing"
+
"github.com/go-git/go-git/v5/plumbing/object"
+
commitCache *ristretto.Cache
+
cache, _ := ristretto.NewCache(&ristretto.Config{
+
TtlTickerDurationInSec: 120,
+
func (g *GitRepo) streamingGitLog(ctx context.Context, extraArgs ...string) (io.Reader, error) {
+
args = append(args, "log")
+
args = append(args, g.h.String())
+
args = append(args, extraArgs...)
+
cmd := exec.CommandContext(ctx, "git", args...)
+
stdout, err := cmd.StdoutPipe()
+
if err := cmd.Start(); err != nil {
+
func cacheKey(g *GitRepo, path string) string {
+
hash := sha256.Sum256(fmt.Append([]byte{}, g.path, sep, g.h.String(), sep, path))
+
return fmt.Sprintf("%x", hash)
+
func (g *GitRepo) calculateCommitTimeIn(ctx context.Context, subtree *object.Tree, parent string, timeout time.Duration) (map[string]commit, error) {
+
ctx, cancel := context.WithTimeout(ctx, timeout)
+
return g.calculateCommitTime(ctx, subtree, parent)
+
func (g *GitRepo) calculateCommitTime(ctx context.Context, subtree *object.Tree, parent string) (map[string]commit, error) {
+
filesToDo := make(map[string]struct{})
+
filesDone := make(map[string]commit)
+
for _, e := range subtree.Entries {
+
fpath := path.Clean(path.Join(parent, e.Name))
+
filesToDo[fpath] = struct{}{}
+
for _, e := range subtree.Entries {
+
f := path.Clean(path.Join(parent, e.Name))
+
cacheKey := cacheKey(g, f)
+
if cached, ok := commitCache.Get(cacheKey); ok {
+
filesDone[f] = cached.(commit)
+
filesToDo[f] = struct{}{}
+
if len(filesToDo) == 0 {
+
ctx, cancel := context.WithCancel(ctx)
+
output, err := g.streamingGitLog(ctx, "--pretty=format:%H,%ad,%s", "--date=iso", "--name-only", "--", pathSpec)
+
reader := bufio.NewReader(output)
+
line, err := reader.ReadString('\n')
+
if err != nil && err != io.EOF {
+
line = strings.TrimSpace(line)
+
if !current.hash.IsZero() {
+
// we have a fully parsed commit
+
for _, f := range current.files {
+
if _, ok := filesToDo[f]; ok {
+
commitCache.Set(cacheKey(g, f), current, 0)
+
if len(filesToDo) == 0 {
+
} else if current.hash.IsZero() {
+
parts := strings.SplitN(line, ",", 3)
+
current.hash = plumbing.NewHash(parts[0])
+
current.when, _ = time.Parse("2006-01-02 15:04:05 -0700", parts[1])
+
current.message = parts[2]
+
// all ancestors along this path should also be included
+
file := path.Clean(line)
+
ancestors := ancestors(file)
+
current.files = append(current.files, file)
+
current.files = append(current.files, ancestors...)
+
func ancestors(p string) []string {
+
if p == "." || p == "/" {
+
ancestors = append(ancestors, p)