Skip to content

Commit

Permalink
perf(fs/git): only fetch known or newly requested references
Browse files Browse the repository at this point in the history
Signed-off-by: George MacRorie <[email protected]>
  • Loading branch information
GeorgeMac committed May 21, 2024
1 parent 26ff952 commit b36e3f1
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 22 deletions.
32 changes: 23 additions & 9 deletions internal/storage/fs/git/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package git
import (
"context"
"errors"
"fmt"
"io/fs"
"slices"
"sync"

"github.com/go-git/go-git/v5"
Expand Down Expand Up @@ -136,7 +138,7 @@ func NewSnapshotStore(ctx context.Context, logger *zap.Logger, url string, opts
}

// do an initial fetch to setup remote tracking branches
if _, err := store.fetch(ctx); err != nil {
if _, err := store.fetch(ctx, store.snaps.References()); err != nil {
return nil, err
}

Expand Down Expand Up @@ -180,8 +182,13 @@ func (s *SnapshotStore) View(ctx context.Context, storeRef storage.Reference, fn
return fn(snap)
}

refs := s.snaps.References()
if !slices.Contains(refs, ref) {
refs = append(refs, ref)
}

// force attempt a fetch to get the latest references
if _, err := s.fetch(ctx); err != nil {
if _, err := s.fetch(ctx, refs); err != nil {
return err
}

Expand All @@ -202,7 +209,7 @@ func (s *SnapshotStore) View(ctx context.Context, storeRef storage.Reference, fn
// HEAD updates to a new revision, it builds a snapshot and updates it
// on the store.
func (s *SnapshotStore) update(ctx context.Context) (bool, error) {
if updated, err := s.fetch(ctx); !(err == nil && updated) {
if updated, err := s.fetch(ctx, s.snaps.References()); !(err == nil && updated) {
// either nothing updated or err != nil
return updated, err
}
Expand All @@ -223,16 +230,23 @@ func (s *SnapshotStore) update(ctx context.Context) (bool, error) {
return true, errors.Join(errs...)
}

func (s *SnapshotStore) fetch(ctx context.Context) (bool, error) {
func (s *SnapshotStore) fetch(ctx context.Context, heads []string) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()

refSpecs := []config.RefSpec{
"+refs/tags/*:refs/tags/*",
}

for _, head := range heads {
refSpecs = append(refSpecs,
config.RefSpec(fmt.Sprintf("+refs/heads/%[1]s:refs/heads/%[1]s", head)),
)
}

if err := s.repo.FetchContext(ctx, &git.FetchOptions{
Auth: s.auth,
RefSpecs: []config.RefSpec{
"+refs/heads/*:refs/heads/*",
"+refs/tags/*:refs/tags/*",
},
Auth: s.auth,
RefSpecs: refSpecs,
}); err != nil {
if !errors.Is(err, git.NoErrAlreadyUpToDate) {
return false, err
Expand Down
68 changes: 55 additions & 13 deletions internal/storage/fs/git/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ flags:
require.NoError(t, fi.Close())

// commit changes
_, err = tree.Commit("chore: update features.yml", &git.CommitOptions{
_, err = tree.Commit("chore: update features.yml add foo and bar", &git.CommitOptions{
All: true,
Author: &object.Signature{Email: "[email protected]", Name: "dev"},
})
Expand All @@ -185,18 +185,6 @@ flags:
RefSpecs: []config.RefSpec{"refs/heads/new-branch:refs/heads/new-branch"},
}))

// wait until the snapshot is updated or
// we timeout
select {
case <-ch:
case <-time.After(time.Minute):
t.Fatal("timed out waiting for snapshot")
}

require.NoError(t, err)

t.Log("received new snapshot")

require.NoError(t, store.View(ctx, "", func(s storage.ReadOnlyStore) error {
_, err := s.GetFlag(ctx, storage.NewResource("production", "bar"))
require.Error(t, err, "flag should not be found in default revision")
Expand All @@ -209,11 +197,65 @@ flags:
return nil
}))

// should be able to fetch flag from previously unfetched reference
require.NoError(t, store.View(ctx, "new-branch", func(s storage.ReadOnlyStore) error {
_, err := s.GetFlag(ctx, storage.NewResource("production", "bar"))
require.NoError(t, err, "flag should be present on new-branch")
return nil
}))

// flag bar should not yet be present
require.NoError(t, store.View(ctx, "new-branch", func(s storage.ReadOnlyStore) error {
_, err := s.GetFlag(ctx, storage.NewResource("production", "baz"))
require.Error(t, err, "flag should not be found in explicitly named new-branch revision")
return nil
}))

// update features.yml, now with the bar flag
fi, err = workdir.OpenFile("features.yml", os.O_TRUNC|os.O_RDWR, os.ModePerm)
require.NoError(t, err)

updated = []byte(`namespace: production
flags:
- key: foo
name: Foo
- key: bar
name: Bar
- key: baz
name: Baz`)

_, err = fi.Write(updated)
require.NoError(t, err)
require.NoError(t, fi.Close())

// commit changes
_, err = tree.Commit("chore: update features.yml add baz", &git.CommitOptions{
All: true,
Author: &object.Signature{Email: "[email protected]", Name: "dev"},
})
require.NoError(t, err)

// push new commit
require.NoError(t, repo.Push(&git.PushOptions{
Auth: &http.BasicAuth{Username: "root", Password: "password"},
RemoteName: "origin",
RefSpecs: []config.RefSpec{"refs/heads/new-branch:refs/heads/new-branch"},
}))

// we should expect to see a modified event now because
// the new reference should be tracked
select {
case <-ch:
case <-time.After(time.Minute):
t.Fatal("timed out waiting for fetch")
}

// should be able to fetch flag bar now that it has been pushed
require.NoError(t, store.View(ctx, "new-branch", func(s storage.ReadOnlyStore) error {
_, err := s.GetFlag(ctx, storage.NewResource("production", "baz"))
require.NoError(t, err, "flag should be present on new-branch")
return nil
}))
}

func Test_Store_View_WithSemverRevision(t *testing.T) {
Expand Down

0 comments on commit b36e3f1

Please sign in to comment.