forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

knotserver/git: cache just the commit hash and time

This should help alleviate the memory leakyness that was exhibited when
we stored the entire object.Commit in memory.

anirudh.fi 31c88431 f164a6f6

verified
Changed files
+40 -17
appview
pages
templates
knotserver
git
types
+2 -2
appview/pages/templates/repo/index.html
··· 79 </a> 80 81 <time class="text-xs text-gray-500" 82 - >{{ timeFmt .LastCommit.Author.When }}</time 83 > 84 </div> 85 </div> ··· 104 </a> 105 106 <time class="text-xs text-gray-500" 107 - >{{ timeFmt .LastCommit.Author.When }}</time 108 > 109 </div> 110 </div>
··· 79 </a> 80 81 <time class="text-xs text-gray-500" 82 + >{{ timeFmt .LastCommit.When }}</time 83 > 84 </div> 85 </div> ··· 104 </a> 105 106 <time class="text-xs text-gray-500" 107 + >{{ timeFmt .LastCommit.When }}</time 108 > 109 </div> 110 </div>
+2 -2
appview/pages/templates/repo/tree.html
··· 44 <i class="w-3 h-3 fill-current" data-lucide="folder"></i>{{ .Name }} 45 </div> 46 </a> 47 - <time class="text-xs text-gray-500">{{ timeFmt .LastCommit.Author.When }}</time> 48 </div> 49 </div> 50 {{ end }} ··· 59 <i class="w-3 h-3" data-lucide="file"></i>{{ .Name }} 60 </div> 61 </a> 62 - <time class="text-xs text-gray-500">{{ timeFmt .LastCommit.Author.When }}</time> 63 </div> 64 </div> 65 {{ end }}
··· 44 <i class="w-3 h-3 fill-current" data-lucide="folder"></i>{{ .Name }} 45 </div> 46 </a> 47 + <time class="text-xs text-gray-500">{{ timeFmt .LastCommit.When }}</time> 48 </div> 49 </div> 50 {{ end }} ··· 59 <i class="w-3 h-3" data-lucide="file"></i>{{ .Name }} 60 </div> 61 </a> 62 + <time class="text-xs text-gray-500">{{ timeFmt .LastCommit.When }}</time> 63 </div> 64 </div> 65 {{ end }}
+26 -11
knotserver/git/git.go
··· 9 "os/exec" 10 "path" 11 "sort" 12 "strings" 13 "sync" 14 "time" ··· 17 "github.com/go-git/go-git/v5" 18 "github.com/go-git/go-git/v5/plumbing" 19 "github.com/go-git/go-git/v5/plumbing/object" 20 ) 21 22 var ( ··· 297 return nil 298 } 299 300 - func (g *GitRepo) LastCommitForPath(path string) (*object.Commit, error) { 301 cacheKey := fmt.Sprintf("%s:%s", g.h.String(), path) 302 cacheMu.RLock() 303 - if commit, found := commitCache.Get(cacheKey); found { 304 cacheMu.RUnlock() 305 - return commit.(*object.Commit), nil 306 } 307 cacheMu.RUnlock() 308 309 - cmd := exec.Command("git", "-C", g.path, "log", "-1", "--format=%H", "--", path) 310 311 var out bytes.Buffer 312 cmd.Stdout = &out ··· 316 return nil, fmt.Errorf("failed to get commit hash: %w", err) 317 } 318 319 - commitHash := strings.TrimSpace(out.String()) 320 - if commitHash == "" { 321 return nil, fmt.Errorf("no commits found for path: %s", path) 322 } 323 324 hash := plumbing.NewHash(commitHash) 325 326 - commit, err := g.r.CommitObject(hash) 327 - if err != nil { 328 - return nil, err 329 } 330 331 cacheMu.Lock() 332 - commitCache.Set(cacheKey, commit, 1) 333 cacheMu.Unlock() 334 335 - return commit, nil 336 } 337 338 func newInfoWrapper(
··· 9 "os/exec" 10 "path" 11 "sort" 12 + "strconv" 13 "strings" 14 "sync" 15 "time" ··· 18 "github.com/go-git/go-git/v5" 19 "github.com/go-git/go-git/v5/plumbing" 20 "github.com/go-git/go-git/v5/plumbing/object" 21 + "github.com/sotangled/tangled/types" 22 ) 23 24 var ( ··· 299 return nil 300 } 301 302 + func (g *GitRepo) LastCommitForPath(path string) (*types.LastCommitInfo, error) { 303 cacheKey := fmt.Sprintf("%s:%s", g.h.String(), path) 304 cacheMu.RLock() 305 + if commitInfo, found := commitCache.Get(cacheKey); found { 306 cacheMu.RUnlock() 307 + return commitInfo.(*types.LastCommitInfo), nil 308 } 309 cacheMu.RUnlock() 310 311 + cmd := exec.Command("git", "-C", g.path, "log", "-1", "--format=%H %ct", "--", path) 312 313 var out bytes.Buffer 314 cmd.Stdout = &out ··· 318 return nil, fmt.Errorf("failed to get commit hash: %w", err) 319 } 320 321 + output := strings.TrimSpace(out.String()) 322 + if output == "" { 323 return nil, fmt.Errorf("no commits found for path: %s", path) 324 } 325 326 + parts := strings.SplitN(output, " ", 2) 327 + if len(parts) < 2 { 328 + return nil, fmt.Errorf("unexpected commit log format") 329 + } 330 + 331 + commitHash := parts[0] 332 + commitTimeUnix, err := strconv.ParseInt(parts[1], 10, 64) 333 + if err != nil { 334 + return nil, fmt.Errorf("parsing commit time: %w", err) 335 + } 336 + commitTime := time.Unix(commitTimeUnix, 0) 337 + 338 hash := plumbing.NewHash(commitHash) 339 340 + commitInfo := &types.LastCommitInfo{ 341 + Hash: hash, 342 + Message: "", 343 + When: commitTime, 344 } 345 346 cacheMu.Lock() 347 + commitCache.Set(cacheKey, commitInfo, 1) 348 cacheMu.Unlock() 349 350 + return commitInfo, nil 351 } 352 353 func newInfoWrapper(
+10 -2
types/tree.go
··· 1 package types 2 3 import ( 4 - "github.com/go-git/go-git/v5/plumbing/object" 5 ) 6 7 // A nicer git tree representation. ··· 12 IsFile bool `json:"is_file"` 13 IsSubtree bool `json:"is_subtree"` 14 15 - LastCommit *object.Commit `json:"last_commit,omitempty"` 16 }
··· 1 package types 2 3 import ( 4 + "time" 5 + 6 + "github.com/go-git/go-git/v5/plumbing" 7 ) 8 9 // A nicer git tree representation. ··· 14 IsFile bool `json:"is_file"` 15 IsSubtree bool `json:"is_subtree"` 16 17 + LastCommit *LastCommitInfo `json:"last_commit,omitempty"` 18 + } 19 + 20 + type LastCommitInfo struct { 21 + Hash plumbing.Hash 22 + Message string 23 + When time.Time 24 }