Monorepo for Tangled tangled.org

knotserver: last commit for each file

This is a super trivial implementation last commit for each file. It's
slow and inefficient despite using ristretto for in-memory caching.

We should move this to storing in sqlite in the future.

authored by oppi.li and committed by anirudh.fi 75dedd41 82f42852

Changed files
+75 -7
knotserver
types
+2
go.mod
··· 11 11 github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 12 12 github.com/casbin/casbin/v2 v2.103.0 13 13 github.com/cyphar/filepath-securejoin v0.3.3 14 + github.com/dgraph-io/ristretto v0.2.0 14 15 github.com/dustin/go-humanize v1.0.1 15 16 github.com/gliderlabs/ssh v0.3.5 16 17 github.com/go-chi/chi/v5 v5.2.0 ··· 81 82 github.com/multiformats/go-varint v0.0.7 // indirect 82 83 github.com/opentracing/opentracing-go v1.2.0 // indirect 83 84 github.com/pjbgf/sha1cd v0.3.0 // indirect 85 + github.com/pkg/errors v0.9.1 // indirect 84 86 github.com/pmezard/go-difflib v1.0.0 // indirect 85 87 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 86 88 github.com/prometheus/client_golang v1.19.1 // indirect
+4
go.sum
··· 50 50 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 51 51 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 52 52 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 53 + github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE= 54 + github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU= 55 + github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= 56 + github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 53 57 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 54 58 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 55 59 github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+48
knotserver/git/git.go
··· 7 7 "io/fs" 8 8 "path" 9 9 "sort" 10 + "sync" 10 11 "time" 11 12 13 + "github.com/dgraph-io/ristretto" 12 14 "github.com/go-git/go-git/v5" 13 15 "github.com/go-git/go-git/v5/plumbing" 14 16 "github.com/go-git/go-git/v5/plumbing/object" 15 17 ) 18 + 19 + var ( 20 + commitCache *ristretto.Cache 21 + cacheMu sync.RWMutex 22 + ) 23 + 24 + func init() { 25 + cache, _ := ristretto.NewCache(&ristretto.Config{ 26 + NumCounters: 1e7, 27 + MaxCost: 1 << 30, 28 + BufferItems: 64, 29 + }) 30 + commitCache = cache 31 + } 16 32 17 33 var ( 18 34 ErrBinaryFile = fmt.Errorf("binary file") ··· 277 293 } 278 294 279 295 return nil 296 + } 297 + 298 + func (g *GitRepo) LastCommitTime(filePath string) (*object.Commit, error) { 299 + cacheMu.RLock() 300 + if commit, exists := commitCache.Get(filePath); exists { 301 + cacheMu.RUnlock() 302 + return commit.(*object.Commit), nil 303 + } 304 + cacheMu.RUnlock() 305 + 306 + commitIter, err := g.r.Log(&git.LogOptions{ 307 + From: g.h, 308 + PathFilter: func(s string) bool { 309 + return s == filePath 310 + }, 311 + Order: git.LogOrderCommitterTime, 312 + }) 313 + 314 + if err != nil { 315 + return nil, fmt.Errorf("failed to get commit log for %s: %w", filePath, err) 316 + } 317 + 318 + commit, err := commitIter.Next() 319 + if err != nil { 320 + return nil, fmt.Errorf("no commit found for %s", filePath) 321 + } 322 + 323 + cacheMu.Lock() 324 + commitCache.Set(filePath, commit, 1) 325 + cacheMu.Unlock() 326 + 327 + return commit, nil 280 328 } 281 329 282 330 func newInfoWrapper(
+15 -7
knotserver/git/tree.go
··· 20 20 } 21 21 22 22 if path == "" { 23 - files = makeNiceTree(tree) 23 + files = g.makeNiceTree(tree) 24 24 } else { 25 25 o, err := tree.FindEntry(path) 26 26 if err != nil { ··· 33 33 return nil, err 34 34 } 35 35 36 - files = makeNiceTree(subtree) 36 + files = g.makeNiceTree(subtree) 37 37 } 38 38 } 39 39 40 40 return files, nil 41 41 } 42 42 43 - func makeNiceTree(t *object.Tree) []types.NiceTree { 43 + func (g *GitRepo) makeNiceTree(t *object.Tree) []types.NiceTree { 44 44 nts := []types.NiceTree{} 45 45 46 46 for _, e := range t.Entries { 47 47 mode, _ := e.Mode.ToOSFileMode() 48 48 sz, _ := t.Size(e.Name) 49 + 50 + lastCommit, err := g.LastCommitTime(e.Name) 51 + if err != nil { 52 + continue 53 + } 54 + 49 55 nts = append(nts, types.NiceTree{ 50 - Name: e.Name, 51 - Mode: mode.String(), 52 - IsFile: e.Mode.IsFile(), 53 - Size: sz, 56 + Name: e.Name, 57 + Mode: mode.String(), 58 + IsFile: e.Mode.IsFile(), 59 + Size: sz, 60 + LastCommit: lastCommit, 54 61 }) 62 + 55 63 } 56 64 57 65 return nts
+6
types/tree.go
··· 1 1 package types 2 2 3 + import ( 4 + "github.com/go-git/go-git/v5/plumbing/object" 5 + ) 6 + 3 7 // A nicer git tree representation. 4 8 type NiceTree struct { 5 9 Name string `json:"name"` ··· 7 11 Size int64 `json:"size"` 8 12 IsFile bool `json:"is_file"` 9 13 IsSubtree bool `json:"is_subtree"` 14 + 15 + LastCommit *object.Commit `json:"last_commit,omitempty"` 10 16 }