···
14
+
"github.com/dgraph-io/ristretto"
15
+
"github.com/go-git/go-git/v5/plumbing"
16
+
"github.com/go-git/go-git/v5/plumbing/object"
20
+
commitCache *ristretto.Cache
24
+
cache, _ := ristretto.NewCache(&ristretto.Config{
28
+
TtlTickerDurationInSec: 120,
33
+
func (g *GitRepo) streamingGitLog(ctx context.Context, extraArgs ...string) (io.Reader, error) {
35
+
args = append(args, "log")
36
+
args = append(args, g.h.String())
37
+
args = append(args, extraArgs...)
39
+
cmd := exec.CommandContext(ctx, "git", args...)
42
+
stdout, err := cmd.StdoutPipe()
47
+
if err := cmd.Start(); err != nil {
54
+
type commit struct {
61
+
func cacheKey(g *GitRepo, path string) string {
63
+
hash := sha256.Sum256(fmt.Append([]byte{}, g.path, sep, g.h.String(), sep, path))
64
+
return fmt.Sprintf("%x", hash)
67
+
func (g *GitRepo) calculateCommitTimeIn(ctx context.Context, subtree *object.Tree, parent string, timeout time.Duration) (map[string]commit, error) {
68
+
ctx, cancel := context.WithTimeout(ctx, timeout)
70
+
return g.calculateCommitTime(ctx, subtree, parent)
73
+
func (g *GitRepo) calculateCommitTime(ctx context.Context, subtree *object.Tree, parent string) (map[string]commit, error) {
74
+
filesToDo := make(map[string]struct{})
75
+
filesDone := make(map[string]commit)
76
+
for _, e := range subtree.Entries {
77
+
fpath := path.Clean(path.Join(parent, e.Name))
78
+
filesToDo[fpath] = struct{}{}
81
+
for _, e := range subtree.Entries {
82
+
f := path.Clean(path.Join(parent, e.Name))
83
+
cacheKey := cacheKey(g, f)
84
+
if cached, ok := commitCache.Get(cacheKey); ok {
85
+
filesDone[f] = cached.(commit)
86
+
delete(filesToDo, f)
88
+
filesToDo[f] = struct{}{}
92
+
if len(filesToDo) == 0 {
93
+
return filesDone, nil
96
+
ctx, cancel := context.WithCancel(ctx)
103
+
output, err := g.streamingGitLog(ctx, "--pretty=format:%H,%ad,%s", "--date=iso", "--name-only", "--", pathSpec)
108
+
reader := bufio.NewReader(output)
111
+
line, err := reader.ReadString('\n')
112
+
if err != nil && err != io.EOF {
115
+
line = strings.TrimSpace(line)
118
+
if !current.hash.IsZero() {
119
+
// we have a fully parsed commit
120
+
for _, f := range current.files {
121
+
if _, ok := filesToDo[f]; ok {
122
+
filesDone[f] = current
123
+
delete(filesToDo, f)
124
+
commitCache.Set(cacheKey(g, f), current, 0)
128
+
if len(filesToDo) == 0 {
134
+
} else if current.hash.IsZero() {
135
+
parts := strings.SplitN(line, ",", 3)
136
+
if len(parts) == 3 {
137
+
current.hash = plumbing.NewHash(parts[0])
138
+
current.when, _ = time.Parse("2006-01-02 15:04:05 -0700", parts[1])
139
+
current.message = parts[2]
142
+
// all ancestors along this path should also be included
143
+
file := path.Clean(line)
144
+
ancestors := ancestors(file)
145
+
current.files = append(current.files, file)
146
+
current.files = append(current.files, ancestors...)
154
+
return filesDone, nil
157
+
func ancestors(p string) []string {
158
+
var ancestors []string
162
+
if p == "." || p == "/" {
165
+
ancestors = append(ancestors, p)