From c3a68445577eeb38faa66c874b35760419176f17 Mon Sep 17 00:00:00 2001 From: Akshay Date: Tue, 18 Feb 2025 17:09:43 +0000 Subject: [PATCH] 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. --- go.mod | 2 ++ go.sum | 4 ++++ knotserver/git/git.go | 48 ++++++++++++++++++++++++++++++++++++++++++ knotserver/git/tree.go | 22 +++++++++++++------ types/tree.go | 6 ++++++ 5 files changed, 75 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 589bd06..3a7392e 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 github.com/casbin/casbin/v2 v2.103.0 github.com/cyphar/filepath-securejoin v0.3.3 + github.com/dgraph-io/ristretto v0.2.0 github.com/dustin/go-humanize v1.0.1 github.com/gliderlabs/ssh v0.3.5 github.com/go-chi/chi/v5 v5.2.0 @@ -81,6 +82,7 @@ require ( github.com/multiformats/go-varint v0.0.7 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect github.com/prometheus/client_golang v1.19.1 // indirect diff --git a/go.sum b/go.sum index 595ad51..989c0e6 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,10 @@ github.com/cyphar/filepath-securejoin v0.3.3/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1na github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE= +github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= diff --git a/knotserver/git/git.go b/knotserver/git/git.go index 779f442..0e870b0 100644 --- a/knotserver/git/git.go +++ b/knotserver/git/git.go @@ -7,13 +7,29 @@ import ( "io/fs" "path" "sort" + "sync" "time" + "github.com/dgraph-io/ristretto" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) +var ( + commitCache *ristretto.Cache + cacheMu sync.RWMutex +) + +func init() { + cache, _ := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e7, + MaxCost: 1 << 30, + BufferItems: 64, + }) + commitCache = cache +} + var ( ErrBinaryFile = fmt.Errorf("binary file") ) @@ -279,6 +295,38 @@ func (g *GitRepo) WriteTar(w io.Writer, prefix string) error { return nil } +func (g *GitRepo) LastCommitTime(filePath string) (*object.Commit, error) { + cacheMu.RLock() + if commit, exists := commitCache.Get(filePath); exists { + cacheMu.RUnlock() + return commit.(*object.Commit), nil + } + cacheMu.RUnlock() + + commitIter, err := g.r.Log(&git.LogOptions{ + From: g.h, + PathFilter: func(s string) bool { + return s == filePath + }, + Order: git.LogOrderCommitterTime, + }) + + if err != nil { + return nil, fmt.Errorf("failed to get commit log for %s: %w", filePath, err) + } + + commit, err := commitIter.Next() + if err != nil { + return nil, fmt.Errorf("no commit found for %s", filePath) + } + + cacheMu.Lock() + commitCache.Set(filePath, commit, 1) + cacheMu.Unlock() + + return commit, nil +} + func newInfoWrapper( name string, prefix string, diff --git a/knotserver/git/tree.go b/knotserver/git/tree.go index a567b6d..f47355b 100644 --- a/knotserver/git/tree.go +++ b/knotserver/git/tree.go @@ -20,7 +20,7 @@ func (g *GitRepo) FileTree(path string) ([]types.NiceTree, error) { } if path == "" { - files = makeNiceTree(tree) + files = g.makeNiceTree(tree) } else { o, err := tree.FindEntry(path) if err != nil { @@ -33,25 +33,33 @@ func (g *GitRepo) FileTree(path string) ([]types.NiceTree, error) { return nil, err } - files = makeNiceTree(subtree) + files = g.makeNiceTree(subtree) } } return files, nil } -func makeNiceTree(t *object.Tree) []types.NiceTree { +func (g *GitRepo) makeNiceTree(t *object.Tree) []types.NiceTree { nts := []types.NiceTree{} for _, e := range t.Entries { mode, _ := e.Mode.ToOSFileMode() sz, _ := t.Size(e.Name) + + lastCommit, err := g.LastCommitTime(e.Name) + if err != nil { + continue + } + nts = append(nts, types.NiceTree{ - Name: e.Name, - Mode: mode.String(), - IsFile: e.Mode.IsFile(), - Size: sz, + Name: e.Name, + Mode: mode.String(), + IsFile: e.Mode.IsFile(), + Size: sz, + LastCommit: lastCommit, }) + } return nts diff --git a/types/tree.go b/types/tree.go index de100aa..9248a0e 100644 --- a/types/tree.go +++ b/types/tree.go @@ -1,5 +1,9 @@ package types +import ( + "github.com/go-git/go-git/v5/plumbing/object" +) + // A nicer git tree representation. type NiceTree struct { Name string `json:"name"` @@ -7,4 +11,6 @@ type NiceTree struct { Size int64 `json:"size"` IsFile bool `json:"is_file"` IsSubtree bool `json:"is_subtree"` + + LastCommit *object.Commit `json:"last_commit,omitempty"` } -- 2.54.0