aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/auth/context.go2
-rw-r--r--api/graphql/graphql_test.go2
-rw-r--r--api/graphql/models/lazy_bug.go6
-rw-r--r--api/graphql/models/lazy_identity.go6
-rw-r--r--api/graphql/resolvers/mutation.go6
-rw-r--r--api/graphql/resolvers/repo.go14
-rw-r--r--api/http/git_file_handlers_test.go4
-rw-r--r--bridge/core/config.go4
-rw-r--r--bridge/github/config.go12
-rw-r--r--bridge/github/export.go7
-rw-r--r--bridge/github/export_test.go28
-rw-r--r--bridge/github/import.go13
-rw-r--r--bridge/github/import_integration_test.go15
-rw-r--r--bridge/github/import_test.go9
-rw-r--r--bridge/gitlab/export.go7
-rw-r--r--bridge/gitlab/export_test.go28
-rw-r--r--bridge/gitlab/import.go9
-rw-r--r--bridge/gitlab/import_test.go9
-rw-r--r--bridge/jira/export.go7
-rw-r--r--bridge/jira/import.go8
-rw-r--r--bridge/launchpad/import.go9
-rw-r--r--cache/bug_excerpt.go9
-rw-r--r--cache/bug_subcache.go21
-rw-r--r--cache/cached.go16
-rw-r--r--cache/filter.go56
-rw-r--r--cache/identity_excerpt.go9
-rw-r--r--cache/identity_subcache.go58
-rw-r--r--cache/multi_repo_cache.go16
-rw-r--r--cache/repo_cache.go49
-rw-r--r--cache/repo_cache_common.go97
-rw-r--r--cache/repo_cache_test.go163
-rw-r--r--cache/subcache.go160
-rw-r--r--commands/bridge/bridge_auth_addtoken.go2
-rw-r--r--commands/bug/bug.go38
-rw-r--r--commands/bug/bug_comment_edit.go2
-rw-r--r--commands/bug/bug_new.go2
-rw-r--r--commands/bug/bug_rm.go2
-rw-r--r--commands/bug/bug_select.go2
-rw-r--r--commands/bug/select/select.go5
-rw-r--r--commands/bug/select/select_test.go13
-rw-r--r--commands/bug/testenv/testenv.go6
-rw-r--r--commands/cmdjson/json_common.go4
-rw-r--r--commands/completion/helper_completion.go24
-rw-r--r--commands/execenv/env_testing.go9
-rw-r--r--commands/label.go2
-rw-r--r--commands/user/user.go6
-rw-r--r--commands/user/user_adopt.go2
-rw-r--r--commands/user/user_new.go2
-rw-r--r--commands/user/user_show.go2
-rw-r--r--commands/webui.go2
-rw-r--r--entities/bug/bug.go13
-rw-r--r--entities/bug/resolver.go2
-rw-r--r--entities/identity/common.go6
-rw-r--r--entities/identity/identity.go31
-rw-r--r--entities/identity/identity_actions_test.go5
-rw-r--r--entities/identity/resolver.go15
-rw-r--r--entity/dag/entity.go34
-rw-r--r--entity/dag/entity_actions_test.go2
-rw-r--r--entity/err.go16
-rw-r--r--entity/interface.go12
-rw-r--r--entity/resolver.go29
-rw-r--r--entity/streamed.go6
-rw-r--r--termui/bug_table.go12
-rw-r--r--termui/label_select.go2
-rw-r--r--termui/termui.go2
65 files changed, 615 insertions, 556 deletions
diff --git a/api/auth/context.go b/api/auth/context.go
index 17171261..2547aaca 100644
--- a/api/auth/context.go
+++ b/api/auth/context.go
@@ -24,5 +24,5 @@ func UserFromCtx(ctx context.Context, r *cache.RepoCache) (*cache.IdentityCache,
if !ok {
return nil, ErrNotAuthenticated
}
- return r.ResolveIdentity(id)
+ return r.Identities().Resolve(id)
}
diff --git a/api/graphql/graphql_test.go b/api/graphql/graphql_test.go
index 2ddfb314..e28ce8ab 100644
--- a/api/graphql/graphql_test.go
+++ b/api/graphql/graphql_test.go
@@ -19,7 +19,7 @@ func TestQueries(t *testing.T) {
random_bugs.FillRepoWithSeed(repo, 10, 42)
mrc := cache.NewMultiRepoCache()
- _, err := mrc.RegisterDefaultRepository(repo)
+ _, _, err := mrc.RegisterDefaultRepository(repo)
require.NoError(t, err)
handler := NewHandler(mrc, nil)
diff --git a/api/graphql/models/lazy_bug.go b/api/graphql/models/lazy_bug.go
index 318fdc99..4b0b598e 100644
--- a/api/graphql/models/lazy_bug.go
+++ b/api/graphql/models/lazy_bug.go
@@ -58,7 +58,7 @@ func (lb *lazyBug) load() error {
return nil
}
- b, err := lb.cache.ResolveBug(lb.excerpt.Id)
+ b, err := lb.cache.Bugs().Resolve(lb.excerpt.Id())
if err != nil {
return err
}
@@ -68,7 +68,7 @@ func (lb *lazyBug) load() error {
}
func (lb *lazyBug) identity(id entity.Id) (IdentityWrapper, error) {
- i, err := lb.cache.ResolveIdentityExcerpt(id)
+ i, err := lb.cache.Identities().ResolveExcerpt(id)
if err != nil {
return nil, err
}
@@ -79,7 +79,7 @@ func (lb *lazyBug) identity(id entity.Id) (IdentityWrapper, error) {
func (lb *lazyBug) IsAuthored() {}
func (lb *lazyBug) Id() entity.Id {
- return lb.excerpt.Id
+ return lb.excerpt.Id()
}
func (lb *lazyBug) LastEdit() time.Time {
diff --git a/api/graphql/models/lazy_identity.go b/api/graphql/models/lazy_identity.go
index 27bc9619..c19d077b 100644
--- a/api/graphql/models/lazy_identity.go
+++ b/api/graphql/models/lazy_identity.go
@@ -48,16 +48,16 @@ func (li *lazyIdentity) load() (*cache.IdentityCache, error) {
return li.id, nil
}
- id, err := li.cache.ResolveIdentity(li.excerpt.Id)
+ id, err := li.cache.Identities().Resolve(li.excerpt.Id())
if err != nil {
- return nil, fmt.Errorf("cache: missing identity %v", li.excerpt.Id)
+ return nil, fmt.Errorf("cache: missing identity %v", li.excerpt.Id())
}
li.id = id
return id, nil
}
func (li *lazyIdentity) Id() entity.Id {
- return li.excerpt.Id
+ return li.excerpt.Id()
}
func (li *lazyIdentity) Name() string {
diff --git a/api/graphql/resolvers/mutation.go b/api/graphql/resolvers/mutation.go
index 3f9f7fe1..32e1fa7c 100644
--- a/api/graphql/resolvers/mutation.go
+++ b/api/graphql/resolvers/mutation.go
@@ -32,7 +32,7 @@ func (r mutationResolver) getBug(repoRef *string, bugPrefix string) (*cache.Repo
return nil, nil, err
}
- b, err := repo.ResolveBugPrefix(bugPrefix)
+ b, err := repo.Bugs().ResolvePrefix(bugPrefix)
if err != nil {
return nil, nil, err
}
@@ -50,7 +50,7 @@ func (r mutationResolver) NewBug(ctx context.Context, input models.NewBugInput)
return nil, err
}
- b, op, err := repo.NewBugRaw(author,
+ b, op, err := repo.Bugs().NewRaw(author,
time.Now().Unix(),
text.CleanupOneLine(input.Title),
text.Cleanup(input.Message),
@@ -181,7 +181,7 @@ func (r mutationResolver) EditComment(ctx context.Context, input models.EditComm
return nil, err
}
- b, target, err := repo.ResolveComment(input.TargetPrefix)
+ b, target, err := repo.Bugs().ResolveComment(input.TargetPrefix)
if err != nil {
return nil, err
}
diff --git a/api/graphql/resolvers/repo.go b/api/graphql/resolvers/repo.go
index 3fcaada1..67b03628 100644
--- a/api/graphql/resolvers/repo.go
+++ b/api/graphql/resolvers/repo.go
@@ -41,7 +41,7 @@ func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *st
}
// Simply pass a []string with the ids to the pagination algorithm
- source, err := obj.Repo.QueryBugs(q)
+ source, err := obj.Repo.Bugs().Query(q)
if err != nil {
return nil, err
}
@@ -60,7 +60,7 @@ func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *st
nodes := make([]models.BugWrapper, len(lazyBugEdges))
for i, lazyBugEdge := range lazyBugEdges {
- excerpt, err := obj.Repo.ResolveBugExcerpt(lazyBugEdge.Id)
+ excerpt, err := obj.Repo.Bugs().ResolveExcerpt(lazyBugEdge.Id)
if err != nil {
return nil, err
}
@@ -86,7 +86,7 @@ func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *st
}
func (repoResolver) Bug(_ context.Context, obj *models.Repository, prefix string) (models.BugWrapper, error) {
- excerpt, err := obj.Repo.ResolveBugExcerptPrefix(prefix)
+ excerpt, err := obj.Repo.Bugs().ResolveExcerptPrefix(prefix)
if err != nil {
return nil, err
}
@@ -103,7 +103,7 @@ func (repoResolver) AllIdentities(_ context.Context, obj *models.Repository, aft
}
// Simply pass a []string with the ids to the pagination algorithm
- source := obj.Repo.AllIdentityIds()
+ source := obj.Repo.Identities().AllIds()
// The edger create a custom edge holding just the id
edger := func(id entity.Id, offset int) connections.Edge {
@@ -119,7 +119,7 @@ func (repoResolver) AllIdentities(_ context.Context, obj *models.Repository, aft
nodes := make([]models.IdentityWrapper, len(lazyIdentityEdges))
for k, lazyIdentityEdge := range lazyIdentityEdges {
- excerpt, err := obj.Repo.ResolveIdentityExcerpt(lazyIdentityEdge.Id)
+ excerpt, err := obj.Repo.Identities().ResolveExcerpt(lazyIdentityEdge.Id)
if err != nil {
return nil, err
}
@@ -145,7 +145,7 @@ func (repoResolver) AllIdentities(_ context.Context, obj *models.Repository, aft
}
func (repoResolver) Identity(_ context.Context, obj *models.Repository, prefix string) (models.IdentityWrapper, error) {
- excerpt, err := obj.Repo.ResolveIdentityExcerptPrefix(prefix)
+ excerpt, err := obj.Repo.Identities().ResolveExcerptPrefix(prefix)
if err != nil {
return nil, err
}
@@ -187,5 +187,5 @@ func (repoResolver) ValidLabels(_ context.Context, obj *models.Repository, after
}, nil
}
- return connections.LabelCon(obj.Repo.ValidLabels(), edger, conMaker, input)
+ return connections.LabelCon(obj.Repo.Bugs().ValidLabels(), edger, conMaker, input)
}
diff --git a/api/http/git_file_handlers_test.go b/api/http/git_file_handlers_test.go
index 736bf75e..b2371abf 100644
--- a/api/http/git_file_handlers_test.go
+++ b/api/http/git_file_handlers_test.go
@@ -22,10 +22,10 @@ func TestGitFileHandlers(t *testing.T) {
repo := repository.CreateGoGitTestRepo(t, false)
mrc := cache.NewMultiRepoCache()
- repoCache, err := mrc.RegisterDefaultRepository(repo)
+ repoCache, _, err := mrc.RegisterDefaultRepository(repo)
require.NoError(t, err)
- author, err := repoCache.NewIdentity("test identity", "test@test.org")
+ author, err := repoCache.Identities().New("test identity", "test@test.org")
require.NoError(t, err)
err = repoCache.SetUserIdentity(author)
diff --git a/bridge/core/config.go b/bridge/core/config.go
index 8ad7b965..ed079eb8 100644
--- a/bridge/core/config.go
+++ b/bridge/core/config.go
@@ -10,7 +10,7 @@ import (
func FinishConfig(repo *cache.RepoCache, metaKey string, login string) error {
// if no user exist with the given login metadata
- _, err := repo.ResolveIdentityImmutableMetadata(metaKey, login)
+ _, err := repo.Identities().ResolveIdentityImmutableMetadata(metaKey, login)
if err != nil && !entity.IsErrNotFound(err) {
// real error
return err
@@ -34,7 +34,7 @@ func FinishConfig(repo *cache.RepoCache, metaKey string, login string) error {
}
// otherwise create a user with that metadata
- i, err := repo.NewIdentityFromGitUserRaw(map[string]string{
+ i, err := repo.Identities().NewFromGitUserRaw(map[string]string{
metaKey: login,
})
if err != nil {
diff --git a/bridge/github/config.go b/bridge/github/config.go
index 6b847394..2f5d1f3b 100644
--- a/bridge/github/config.go
+++ b/bridge/github/config.go
@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
- "math/rand"
"net/http"
"net/url"
"regexp"
@@ -319,17 +318,6 @@ func pollGithubForAuthorization(deviceCode string, intervalSec int64) (string, e
}
}
-func randomFingerprint() string {
- // Doesn't have to be crypto secure, it's just to avoid token collision
- rand.Seed(time.Now().UnixNano())
- var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
- b := make([]rune, 32)
- for i := range b {
- b[i] = letterRunes[rand.Intn(len(letterRunes))]
- }
- return string(b)
-}
-
func promptTokenOptions(repo repository.RepoKeyring, login, owner, project string) (auth.Credential, error) {
creds, err := auth.List(repo,
auth.WithTarget(target),
diff --git a/bridge/github/export.go b/bridge/github/export.go
index 5d1d8661..0d340b49 100644
--- a/bridge/github/export.go
+++ b/bridge/github/export.go
@@ -20,7 +20,6 @@ import (
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entities/common"
- "github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
)
@@ -89,7 +88,7 @@ func (ge *githubExporter) cacheAllClient(repo *cache.RepoCache) error {
continue
}
- user, err := repo.ResolveIdentityImmutableMetadata(metaKeyGithubLogin, login)
+ user, err := repo.Identities().ResolveIdentityImmutableMetadata(metaKeyGithubLogin, login)
if entity.IsErrNotFound(err) {
continue
}
@@ -160,10 +159,10 @@ func (ge *githubExporter) ExportAll(ctx context.Context, repo *cache.RepoCache,
allIdentitiesIds = append(allIdentitiesIds, id)
}
- allBugsIds := repo.AllBugsIds()
+ allBugsIds := repo.Bugs().AllIds()
for _, id := range allBugsIds {
- b, err := repo.ResolveBug(id)
+ b, err := repo.Bugs().Resolve(id)
if err != nil {
out <- core.NewExportError(errors.Wrap(err, "can't load bug"), id)
return
diff --git a/bridge/github/export_test.go b/bridge/github/export_test.go
index 2ebe9622..a2dbf7de 100644
--- a/bridge/github/export_test.go
+++ b/bridge/github/export_test.go
@@ -34,18 +34,18 @@ type testCase struct {
func testCases(t *testing.T, repo *cache.RepoCache) []*testCase {
// simple bug
- simpleBug, _, err := repo.NewBug("simple bug", "new bug")
+ simpleBug, _, err := repo.Bugs().New("simple bug", "new bug")
require.NoError(t, err)
// bug with comments
- bugWithComments, _, err := repo.NewBug("bug with comments", "new bug")
+ bugWithComments, _, err := repo.Bugs().New("bug with comments", "new bug")
require.NoError(t, err)
_, _, err = bugWithComments.AddComment("new comment")
require.NoError(t, err)
// bug with label changes
- bugLabelChange, _, err := repo.NewBug("bug label change", "new bug")
+ bugLabelChange, _, err := repo.Bugs().New("bug label change", "new bug")
require.NoError(t, err)
_, _, err = bugLabelChange.ChangeLabels([]string{"bug"}, nil)
@@ -64,7 +64,7 @@ func testCases(t *testing.T, repo *cache.RepoCache) []*testCase {
require.NoError(t, err)
// bug with comments editions
- bugWithCommentEditions, createOp, err := repo.NewBug("bug with comments editions", "new bug")
+ bugWithCommentEditions, createOp, err := repo.Bugs().New("bug with comments editions", "new bug")
require.NoError(t, err)
_, err = bugWithCommentEditions.EditComment(
@@ -78,7 +78,7 @@ func testCases(t *testing.T, repo *cache.RepoCache) []*testCase {
require.NoError(t, err)
// bug status changed
- bugStatusChanged, _, err := repo.NewBug("bug status changed", "new bug")
+ bugStatusChanged, _, err := repo.Bugs().New("bug status changed", "new bug")
require.NoError(t, err)
_, err = bugStatusChanged.Close()
@@ -88,7 +88,7 @@ func testCases(t *testing.T, repo *cache.RepoCache) []*testCase {
require.NoError(t, err)
// bug title changed
- bugTitleEdited, _, err := repo.NewBug("bug title edited", "new bug")
+ bugTitleEdited, _, err := repo.Bugs().New("bug title edited", "new bug")
require.NoError(t, err)
_, err = bugTitleEdited.SetTitle("bug title edited again")
@@ -141,12 +141,15 @@ func TestGithubPushPull(t *testing.T) {
// create repo backend
repo := repository.CreateGoGitTestRepo(t, false)
- backend, err := cache.NewRepoCache(repo)
+ backend, events, err := cache.NewRepoCache(repo)
require.NoError(t, err)
+ for event := range events {
+ require.NoError(t, event.Err)
+ }
// set author identity
login := "identity-test"
- author, err := backend.NewIdentity("test identity", "test@test.org")
+ author, err := backend.Identities().New("test identity", "test@test.org")
require.NoError(t, err)
author.SetMetadata(metaKeyGithubLogin, login)
err = author.Commit()
@@ -217,8 +220,11 @@ func TestGithubPushPull(t *testing.T) {
repoTwo := repository.CreateGoGitTestRepo(t, false)
// create a second backend
- backendTwo, err := cache.NewRepoCache(repoTwo)
+ backendTwo, events, err := cache.NewRepoCache(repoTwo)
require.NoError(t, err)
+ for event := range events {
+ require.NoError(t, event.Err)
+ }
importer := &githubImporter{}
err = importer.Init(ctx, backend, core.Configuration{
@@ -236,7 +242,7 @@ func TestGithubPushPull(t *testing.T) {
require.NoError(t, result.Err)
}
- require.Len(t, backendTwo.AllBugsIds(), len(tests))
+ require.Len(t, backendTwo.Bugs().AllIds(), len(tests))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -261,7 +267,7 @@ func TestGithubPushPull(t *testing.T) {
require.True(t, ok)
// retrieve bug from backendTwo
- importedBug, err := backendTwo.ResolveBugCreateMetadata(metaKeyGithubId, bugGithubID)
+ importedBug, err := backendTwo.Bugs().ResolveBugCreateMetadata(metaKeyGithubId, bugGithubID)
require.NoError(t, err)
// verify bug have same number of original operations
diff --git a/bridge/github/import.go b/bridge/github/import.go
index a64b7b27..4a51d117 100644
--- a/bridge/github/import.go
+++ b/bridge/github/import.go
@@ -10,7 +10,6 @@ import (
"github.com/MichaelMure/git-bug/bridge/core"
"github.com/MichaelMure/git-bug/bridge/core/auth"
"github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -183,7 +182,7 @@ func (gi *githubImporter) ensureIssue(ctx context.Context, repo *cache.RepoCache
}
// resolve bug
- b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
+ b, err := repo.Bugs().ResolveMatcher(func(excerpt *cache.BugExcerpt) bool {
return excerpt.CreateMetadata[metaKeyGithubUrl] == issue.Url.String() &&
excerpt.CreateMetadata[metaKeyGithubId] == parseId(issue.Id)
})
@@ -213,7 +212,7 @@ func (gi *githubImporter) ensureIssue(ctx context.Context, repo *cache.RepoCache
}
// create bug
- b, _, err = repo.NewBugRaw(
+ b, _, err = repo.Bugs().NewRaw(
author,
issue.CreatedAt.Unix(),
text.CleanupOneLine(title), // TODO: this is the *current* title, not the original one
@@ -498,7 +497,7 @@ func (gi *githubImporter) ensurePerson(ctx context.Context, repo *cache.RepoCach
}
// Look first in the cache
- i, err := repo.ResolveIdentityImmutableMetadata(metaKeyGithubLogin, string(actor.Login))
+ i, err := repo.Identities().ResolveIdentityImmutableMetadata(metaKeyGithubLogin, string(actor.Login))
if err == nil {
return i, nil
}
@@ -531,7 +530,7 @@ func (gi *githubImporter) ensurePerson(ctx context.Context, repo *cache.RepoCach
name = string(actor.Login)
}
- i, err = repo.NewIdentityRaw(
+ i, err = repo.Identities().NewRaw(
name,
email,
string(actor.Login),
@@ -553,7 +552,7 @@ func (gi *githubImporter) ensurePerson(ctx context.Context, repo *cache.RepoCach
func (gi *githubImporter) getGhost(ctx context.Context, repo *cache.RepoCache) (*cache.IdentityCache, error) {
loginName := "ghost"
// Look first in the cache
- i, err := repo.ResolveIdentityImmutableMetadata(metaKeyGithubLogin, loginName)
+ i, err := repo.Identities().ResolveIdentityImmutableMetadata(metaKeyGithubLogin, loginName)
if err == nil {
return i, nil
}
@@ -568,7 +567,7 @@ func (gi *githubImporter) getGhost(ctx context.Context, repo *cache.RepoCache) (
if user.Name != nil {
userName = string(*user.Name)
}
- return repo.NewIdentityRaw(
+ return repo.Identities().NewRaw(
userName,
"",
string(user.Login),
diff --git a/bridge/github/import_integration_test.go b/bridge/github/import_integration_test.go
index 50cbd5c8..6a41517c 100644
--- a/bridge/github/import_integration_test.go
+++ b/bridge/github/import_integration_test.go
@@ -34,8 +34,11 @@ func TestGithubImporterIntegration(t *testing.T) {
// arrange
repo := repository.CreateGoGitTestRepo(t, false)
- backend, err := cache.NewRepoCache(repo)
+ backend, buildEvents, err := cache.NewRepoCache(repo)
require.NoError(t, err)
+ for event := range buildEvents {
+ require.NoError(t, event.Err)
+ }
defer backend.Close()
interrupt.RegisterCleaner(backend.Close)
require.NoError(t, err)
@@ -48,17 +51,17 @@ func TestGithubImporterIntegration(t *testing.T) {
for e := range events {
require.NoError(t, e.Err)
}
- require.Len(t, backend.AllBugsIds(), 5)
- require.Len(t, backend.AllIdentityIds(), 2)
+ require.Len(t, backend.Bugs().AllIds(), 5)
+ require.Len(t, backend.Identities().AllIds(), 2)
- b1, err := backend.ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/1")
+ b1, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/1")
require.NoError(t, err)
ops1 := b1.Snapshot().Operations
require.Equal(t, "marcus", ops1[0].Author().Name())
require.Equal(t, "title 1", ops1[0].(*bug.CreateOperation).Title)
require.Equal(t, "body text 1", ops1[0].(*bug.CreateOperation).Message)
- b3, err := backend.ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/3")
+ b3, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/3")
require.NoError(t, err)
ops3 := b3.Snapshot().Operations
require.Equal(t, "issue 3 comment 1", ops3[1].(*bug.AddCommentOperation).Message)
@@ -66,7 +69,7 @@ func TestGithubImporterIntegration(t *testing.T) {
require.Equal(t, []bug.Label{"bug"}, ops3[3].(*bug.LabelChangeOperation).Added)
require.Equal(t, "title 3, edit 1", ops3[4].(*bug.SetTitleOperation).Title)
- b4, err := backend.ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/4")
+ b4, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/4")
require.NoError(t, err)
ops4 := b4.Snapshot().Operations
require.Equal(t, "edited", ops4[1].(*bug.EditCommentOperation).Message)
diff --git a/bridge/github/import_test.go b/bridge/github/import_test.go
index 5575de98..6539a52f 100644
--- a/bridge/github/import_test.go
+++ b/bridge/github/import_test.go
@@ -28,8 +28,11 @@ func TestGithubImporter(t *testing.T) {
repo := repository.CreateGoGitTestRepo(t, false)
- backend, err := cache.NewRepoCache(repo)
+ backend, buildEvents, err := cache.NewRepoCache(repo)
require.NoError(t, err)
+ for event := range buildEvents {
+ require.NoError(t, event.Err)
+ }
defer backend.Close()
interrupt.RegisterCleaner(backend.Close)
@@ -171,11 +174,11 @@ func TestGithubImporter(t *testing.T) {
fmt.Printf("test repository imported in %f seconds\n", time.Since(start).Seconds())
- require.Len(t, backend.AllBugsIds(), len(tests))
+ require.Len(t, backend.Bugs().AllIds(), len(tests))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- b, err := backend.ResolveBugCreateMetadata(metaKeyGithubUrl, tt.url)
+ b, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGithubUrl, tt.url)
require.NoError(t, err)
ops := b.Snapshot().Operations
diff --git a/bridge/gitlab/export.go b/bridge/gitlab/export.go
index 19b8d496..b3a02447 100644
--- a/bridge/gitlab/export.go
+++ b/bridge/gitlab/export.go
@@ -15,7 +15,6 @@ import (
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entities/common"
- "github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
)
@@ -74,7 +73,7 @@ func (ge *gitlabExporter) cacheAllClient(repo *cache.RepoCache, baseURL string)
continue
}
- user, err := repo.ResolveIdentityImmutableMetadata(metaKeyGitlabLogin, login)
+ user, err := repo.Identities().ResolveIdentityImmutableMetadata(metaKeyGitlabLogin, login)
if entity.IsErrNotFound(err) {
continue
}
@@ -116,14 +115,14 @@ func (ge *gitlabExporter) ExportAll(ctx context.Context, repo *cache.RepoCache,
allIdentitiesIds = append(allIdentitiesIds, id)
}
- allBugsIds := repo.AllBugsIds()
+ allBugsIds := repo.Bugs().AllIds()
for _, id := range allBugsIds {
select {
case <-ctx.Done():
return
default:
- b, err := repo.ResolveBug(id)
+ b, err := repo.Bugs().Resolve(id)
if err != nil {
out <- core.NewExportError(err, id)
return
diff --git a/bridge/gitlab/export_test.go b/bridge/gitlab/export_test.go
index 47d5a9b1..64bc43f4 100644
--- a/bridge/gitlab/export_test.go
+++ b/bridge/gitlab/export_test.go
@@ -37,18 +37,18 @@ type testCase struct {
func testCases(t *testing.T, repo *cache.RepoCache) []*testCase {
// simple bug
- simpleBug, _, err := repo.NewBug("simple bug", "new bug")
+ simpleBug, _, err := repo.Bugs().New("simple bug", "new bug")
require.NoError(t, err)
// bug with comments
- bugWithComments, _, err := repo.NewBug("bug with comments", "new bug")
+ bugWithComments, _, err := repo.Bugs().New("bug with comments", "new bug")
require.NoError(t, err)
_, _, err = bugWithComments.AddComment("new comment")
require.NoError(t, err)
// bug with label changes
- bugLabelChange, _, err := repo.NewBug("bug label change", "new bug")
+ bugLabelChange, _, err := repo.Bugs().New("bug label change", "new bug")
require.NoError(t, err)
_, _, err = bugLabelChange.ChangeLabels([]string{"bug"}, nil)
@@ -61,7 +61,7 @@ func testCases(t *testing.T, repo *cache.RepoCache) []*testCase {
require.NoError(t, err)
// bug with comments editions
- bugWithCommentEditions, createOp, err := repo.NewBug("bug with comments editions", "new bug")
+ bugWithCommentEditions, createOp, err := repo.Bugs().New("bug with comments editions", "new bug")
require.NoError(t, err)
_, err = bugWithCommentEditions.EditComment(
@@ -75,7 +75,7 @@ func testCases(t *testing.T, repo *cache.RepoCache) []*testCase {
require.NoError(t, err)
// bug status changed
- bugStatusChanged, _, err := repo.NewBug("bug status changed", "new bug")
+ bugStatusChanged, _, err := repo.Bugs().New("bug status changed", "new bug")
require.NoError(t, err)
_, err = bugStatusChanged.Close()
@@ -85,7 +85,7 @@ func testCases(t *testing.T, repo *cache.RepoCache) []*testCase {
require.NoError(t, err)
// bug title changed
- bugTitleEdited, _, err := repo.NewBug("bug title edited", "new bug")
+ bugTitleEdited, _, err := repo.Bugs().New("bug title edited", "new bug")
require.NoError(t, err)
_, err = bugTitleEdited.SetTitle("bug title edited again")
@@ -147,12 +147,15 @@ func TestGitlabPushPull(t *testing.T) {
// create repo backend
repo := repository.CreateGoGitTestRepo(t, false)
- backend, err := cache.NewRepoCache(repo)
+ backend, events, err := cache.NewRepoCache(repo)
require.NoError(t, err)
+ for event := range events {
+ require.NoError(t, event.Err)
+ }
// set author identity
login := "test-identity"
- author, err := backend.NewIdentity("test identity", "test@test.org")
+ author, err := backend.Identities().New("test identity", "test@test.org")
require.NoError(t, err)
author.SetMetadata(metaKeyGitlabLogin, login)
err = author.Commit()
@@ -220,8 +223,11 @@ func TestGitlabPushPull(t *testing.T) {
repoTwo := repository.CreateGoGitTestRepo(t, false)
// create a second backend
- backendTwo, err := cache.NewRepoCache(repoTwo)
+ backendTwo, events, err := cache.NewRepoCache(repoTwo)
require.NoError(t, err)
+ for event := range events {
+ require.NoError(t, event.Err)
+ }
importer := &gitlabImporter{}
err = importer.Init(ctx, backend, core.Configuration{
@@ -239,7 +245,7 @@ func TestGitlabPushPull(t *testing.T) {
require.NoError(t, result.Err)
}
- require.Len(t, backendTwo.AllBugsIds(), len(tests))
+ require.Len(t, backendTwo.Bugs().AllIds(), len(tests))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -264,7 +270,7 @@ func TestGitlabPushPull(t *testing.T) {
require.True(t, ok)
// retrieve bug from backendTwo
- importedBug, err := backendTwo.ResolveBugCreateMetadata(metaKeyGitlabId, bugGitlabID)
+ importedBug, err := backendTwo.Bugs().ResolveBugCreateMetadata(metaKeyGitlabId, bugGitlabID)
require.NoError(t, err)
// verify bug have same number of original operations
diff --git a/bridge/gitlab/import.go b/bridge/gitlab/import.go
index 85999e90..5947fb60 100644
--- a/bridge/gitlab/import.go
+++ b/bridge/gitlab/import.go
@@ -11,7 +11,6 @@ import (
"github.com/MichaelMure/git-bug/bridge/core"
"github.com/MichaelMure/git-bug/bridge/core/auth"
"github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -109,7 +108,7 @@ func (gi *gitlabImporter) ensureIssue(repo *cache.RepoCache, issue *gitlab.Issue
}
// resolve bug
- b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
+ b, err := repo.Bugs().ResolveMatcher(func(excerpt *cache.BugExcerpt) bool {
return excerpt.CreateMetadata[core.MetaKeyOrigin] == target &&
excerpt.CreateMetadata[metaKeyGitlabId] == fmt.Sprintf("%d", issue.IID) &&
excerpt.CreateMetadata[metaKeyGitlabBaseUrl] == gi.conf[confKeyGitlabBaseUrl] &&
@@ -123,7 +122,7 @@ func (gi *gitlabImporter) ensureIssue(repo *cache.RepoCache, issue *gitlab.Issue
}
// if bug was never imported, create bug
- b, _, err = repo.NewBugRaw(
+ b, _, err = repo.Bugs().NewRaw(
author,
issue.CreatedAt.Unix(),
text.CleanupOneLine(issue.Title),
@@ -338,7 +337,7 @@ func (gi *gitlabImporter) ensureIssueEvent(repo *cache.RepoCache, b *cache.BugCa
func (gi *gitlabImporter) ensurePerson(repo *cache.RepoCache, id int) (*cache.IdentityCache, error) {
// Look first in the cache
- i, err := repo.ResolveIdentityImmutableMetadata(metaKeyGitlabId, strconv.Itoa(id))
+ i, err := repo.Identities().ResolveIdentityImmutableMetadata(metaKeyGitlabId, strconv.Itoa(id))
if err == nil {
return i, nil
}
@@ -351,7 +350,7 @@ func (gi *gitlabImporter) ensurePerson(repo *cache.RepoCache, id int) (*cache.Id
return nil, err
}
- i, err = repo.NewIdentityRaw(
+ i, err = repo.Identities().NewRaw(
user.Name,
user.PublicEmail,
user.Username,
diff --git a/bridge/gitlab/import_test.go b/bridge/gitlab/import_test.go
index d98da4ef..ac91610d 100644
--- a/bridge/gitlab/import_test.go
+++ b/bridge/gitlab/import_test.go
@@ -33,8 +33,11 @@ func TestGitlabImport(t *testing.T) {
repo := repository.CreateGoGitTestRepo(t, false)
- backend, err := cache.NewRepoCache(repo)
+ backend, buildEvents, err := cache.NewRepoCache(repo)
require.NoError(t, err)
+ for event := range buildEvents {
+ require.NoError(t, event.Err)
+ }
defer backend.Close()
interrupt.RegisterCleaner(backend.Close)
@@ -126,11 +129,11 @@ func TestGitlabImport(t *testing.T) {
fmt.Printf("test repository imported in %f seconds\n", time.Since(start).Seconds())
- require.Len(t, backend.AllBugsIds(), len(tests))
+ require.Len(t, backend.Bugs().AllIds(), len(tests))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- b, err := backend.ResolveBugCreateMetadata(metaKeyGitlabUrl, tt.url)
+ b, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGitlabUrl, tt.url)
require.NoError(t, err)
ops := b.Snapshot().Operations
diff --git a/bridge/jira/export.go b/bridge/jira/export.go
index 10b6823d..95f9e28c 100644
--- a/bridge/jira/export.go
+++ b/bridge/jira/export.go
@@ -14,7 +14,6 @@ import (
"github.com/MichaelMure/git-bug/bridge/core/auth"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entities/bug"
- "github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
)
@@ -102,7 +101,7 @@ func (je *jiraExporter) cacheAllClient(ctx context.Context, repo *cache.RepoCach
continue
}
- user, err := repo.ResolveIdentityImmutableMetadata(metaKeyJiraLogin, login)
+ user, err := repo.Identities().ResolveIdentityImmutableMetadata(metaKeyJiraLogin, login)
if entity.IsErrNotFound(err) {
continue
}
@@ -146,10 +145,10 @@ func (je *jiraExporter) ExportAll(ctx context.Context, repo *cache.RepoCache, si
allIdentitiesIds = append(allIdentitiesIds, id)
}
- allBugsIds := repo.AllBugsIds()
+ allBugsIds := repo.Bugs().AllIds()
for _, id := range allBugsIds {
- b, err := repo.ResolveBug(id)
+ b, err := repo.Bugs().Resolve(id)
if err != nil {
out <- core.NewExportError(errors.Wrap(err, "can't load bug"), id)
return
diff --git a/bridge/jira/import.go b/bridge/jira/import.go
index 4cec1133..d8a5f8dd 100644
--- a/bridge/jira/import.go
+++ b/bridge/jira/import.go
@@ -184,7 +184,7 @@ func (ji *jiraImporter) ImportAll(ctx context.Context, repo *cache.RepoCache, si
// Create a bug.Person from a JIRA user
func (ji *jiraImporter) ensurePerson(repo *cache.RepoCache, user User) (*cache.IdentityCache, error) {
// Look first in the cache
- i, err := repo.ResolveIdentityImmutableMetadata(
+ i, err := repo.Identities().ResolveIdentityImmutableMetadata(
metaKeyJiraUser, string(user.Key))
if err == nil {
return i, nil
@@ -193,7 +193,7 @@ func (ji *jiraImporter) ensurePerson(repo *cache.RepoCache, user User) (*cache.I
return nil, err
}
- i, err = repo.NewIdentityRaw(
+ i, err = repo.Identities().NewRaw(
user.DisplayName,
user.EmailAddress,
user.Key,
@@ -219,7 +219,7 @@ func (ji *jiraImporter) ensureIssue(repo *cache.RepoCache, issue Issue) (*cache.
return nil, err
}
- b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
+ b, err := repo.Bugs().ResolveMatcher(func(excerpt *cache.BugExcerpt) bool {
if _, ok := excerpt.CreateMetadata[metaKeyJiraBaseUrl]; ok &&
excerpt.CreateMetadata[metaKeyJiraBaseUrl] != ji.conf[confKeyBaseUrl] {
return false
@@ -234,7 +234,7 @@ func (ji *jiraImporter) ensureIssue(repo *cache.RepoCache, issue Issue) (*cache.
}
if entity.IsErrNotFound(err) {
- b, _, err = repo.NewBugRaw(
+ b, _, err = repo.Bugs().NewRaw(
author,
issue.Fields.Created.Unix(),
text.CleanupOneLine(issue.Fields.Summary),
diff --git a/bridge/launchpad/import.go b/bridge/launchpad/import.go
index 0f8ecf9f..6a20217c 100644
--- a/bridge/launchpad/import.go
+++ b/bridge/launchpad/import.go
@@ -7,7 +7,6 @@ import (
"github.com/MichaelMure/git-bug/bridge/core"
"github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -23,7 +22,7 @@ func (li *launchpadImporter) Init(_ context.Context, repo *cache.RepoCache, conf
func (li *launchpadImporter) ensurePerson(repo *cache.RepoCache, owner LPPerson) (*cache.IdentityCache, error) {
// Look first in the cache
- i, err := repo.ResolveIdentityImmutableMetadata(metaKeyLaunchpadLogin, owner.Login)
+ i, err := repo.Identities().ResolveIdentityImmutableMetadata(metaKeyLaunchpadLogin, owner.Login)
if err == nil {
return i, nil
}
@@ -31,7 +30,7 @@ func (li *launchpadImporter) ensurePerson(repo *cache.RepoCache, owner LPPerson)
return nil, err
}
- return repo.NewIdentityRaw(
+ return repo.Identities().NewRaw(
owner.Name,
"",
owner.Login,
@@ -64,7 +63,7 @@ func (li *launchpadImporter) ImportAll(ctx context.Context, repo *cache.RepoCach
return
default:
lpBugID := fmt.Sprintf("%d", lpBug.ID)
- b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
+ b, err := repo.Bugs().ResolveMatcher(func(excerpt *cache.BugExcerpt) bool {
return excerpt.CreateMetadata[core.MetaKeyOrigin] == target &&
excerpt.CreateMetadata[metaKeyLaunchpadID] == lpBugID
})
@@ -81,7 +80,7 @@ func (li *launchpadImporter) ImportAll(ctx context.Context, repo *cache.RepoCach
if entity.IsErrNotFound(err) {
createdAt, _ := time.Parse(time.RFC3339, lpBug.CreatedAt)
- b, _, err = repo.NewBugRaw(
+ b, _, err = repo.Bugs().NewRaw(
owner,
createdAt.Unix(),
text.CleanupOneLine(lpBug.Title),
diff --git a/cache/bug_excerpt.go b/cache/bug_excerpt.go
index b4748fd2..26b7ec74 100644
--- a/cache/bug_excerpt.go
+++ b/cache/bug_excerpt.go
@@ -15,6 +15,8 @@ func init() {
gob.Register(BugExcerpt{})
}
+var _ Excerpt = &BugExcerpt{}
+
// BugExcerpt hold a subset of the bug values to be able to sort and filter bugs
// efficiently without having to read and compile each raw bugs.
type BugExcerpt struct {
@@ -36,7 +38,8 @@ type BugExcerpt struct {
CreateMetadata map[string]string
}
-func NewBugExcerpt(b bug.Interface, snap *bug.Snapshot) *BugExcerpt {
+func NewBugExcerpt(b *BugCache) *BugExcerpt {
+ snap := b.Snapshot()
participantsIds := make([]entity.Id, 0, len(snap.Participants))
for _, participant := range snap.Participants {
participantsIds = append(participantsIds, participant.Id())
@@ -66,6 +69,10 @@ func NewBugExcerpt(b bug.Interface, snap *bug.Snapshot) *BugExcerpt {
return e
}
+func (b *BugExcerpt) setId(id entity.Id) {
+ b.id = id
+}
+
func (b *BugExcerpt) Id() entity.Id {
return b.id
}
diff --git a/cache/bug_subcache.go b/cache/bug_subcache.go
index f0a8889b..14b56cdc 100644
--- a/cache/bug_subcache.go
+++ b/cache/bug_subcache.go
@@ -24,11 +24,7 @@ func NewRepoCacheBug(repo repository.ClockedRepo,
return NewBugCache(b, repo, getUserIdentity, entityUpdated)
}
- makeExcerpt := func(b *bug.Bug) *BugExcerpt {
- return NewBugExcerpt(b, b.Compile())
- }
-
- makeIndex := func(b *BugCache) []string {
+ makeIndexData := func(b *BugCache) []string {
snap := b.Snapshot()
var res []string
for _, comment := range snap.Comments {
@@ -38,9 +34,16 @@ func NewRepoCacheBug(repo repository.ClockedRepo,
return res
}
+ actions := Actions[*bug.Bug]{
+ ReadWithResolver: bug.ReadWithResolver,
+ ReadAllWithResolver: bug.ReadAllWithResolver,
+ Remove: bug.Remove,
+ MergeAll: bug.MergeAll,
+ }
+
sc := NewSubCache[*bug.Bug, *BugExcerpt, *BugCache](
repo, resolvers, getUserIdentity,
- makeCached, makeExcerpt, makeIndex,
+ makeCached, NewBugExcerpt, makeIndexData, actions,
"bug", "bugs",
formatVersion, defaultMaxLoadedBugs,
)
@@ -104,8 +107,8 @@ func (c *RepoCacheBug) ResolveComment(prefix string) (*BugCache, entity.Combined
return matchingBug, matchingCommentId, nil
}
-// QueryBugs return the id of all Bug matching the given Query
-func (c *RepoCacheBug) QueryBugs(q *query.Query) ([]entity.Id, error) {
+// Query return the id of all Bug matching the given Query
+func (c *RepoCacheBug) Query(q *query.Query) ([]entity.Id, error) {
c.mu.RLock()
defer c.mu.RUnlock()
@@ -140,7 +143,7 @@ func (c *RepoCacheBug) QueryBugs(q *query.Query) ([]entity.Id, error) {
}
for _, excerpt := range foundBySearch {
- if matcher.Match(excerpt, c) {
+ if matcher.Match(excerpt, c.resolvers()) {
filtered = append(filtered, excerpt)
}
}
diff --git a/cache/cached.go b/cache/cached.go
index 74757356..5a31ee59 100644
--- a/cache/cached.go
+++ b/cache/cached.go
@@ -3,10 +3,10 @@ package cache
import (
"sync"
- "github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/repository"
+ "github.com/MichaelMure/git-bug/util/lamport"
)
// type withSnapshot[SnapT dag.Snapshot, OpT dag.OperationWithApply[SnapT]] struct {
@@ -97,7 +97,7 @@ func (e *CachedEntityBase[SnapT, OpT]) ResolveOperationWithMetadata(key string,
}
if len(matching) > 1 {
- return "", bug.NewErrMultipleMatchOp(matching)
+ return "", entity.NewErrMultipleMatch("operation", matching)
}
return matching[0], nil
@@ -136,3 +136,15 @@ func (e *CachedEntityBase[SnapT, OpT]) NeedCommit() bool {
defer e.mu.RUnlock()
return e.entity.NeedCommit()
}
+
+func (e *CachedEntityBase[SnapT, OpT]) CreateLamportTime() lamport.Time {
+ return e.entity.CreateLamportTime()
+}
+
+func (e *CachedEntityBase[SnapT, OpT]) EditLamportTime() lamport.Time {
+ return e.entity.EditLamportTime()
+}
+
+func (e *CachedEntityBase[SnapT, OpT]) FirstOp() OpT {
+ return e.entity.FirstOp()
+}
diff --git a/cache/filter.go b/cache/filter.go
index 01f635c5..5a15e402 100644
--- a/cache/filter.go
+++ b/cache/filter.go
@@ -8,28 +8,22 @@ import (
"github.com/MichaelMure/git-bug/query"
)
-// resolver has the resolving functions needed by filters.
-// This exists mainly to go through the functions of the cache with proper locking.
-type resolver interface {
- ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error)
-}
-
// Filter is a predicate that match a subset of bugs
-type Filter func(excerpt *BugExcerpt, resolver resolver) bool
+type Filter func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool
// StatusFilter return a Filter that match a bug status
func StatusFilter(status common.Status) Filter {
- return func(excerpt *BugExcerpt, resolver resolver) bool {
+ return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
return excerpt.Status == status
}
}
// AuthorFilter return a Filter that match a bug author
func AuthorFilter(query string) Filter {
- return func(excerpt *BugExcerpt, resolver resolver) bool {
+ return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
query = strings.ToLower(query)
- author, err := resolver.ResolveIdentityExcerpt(excerpt.AuthorId)
+ author, err := entity.Resolve[*IdentityExcerpt](resolvers, excerpt.AuthorId)
if err != nil {
panic(err)
}
@@ -40,7 +34,7 @@ func AuthorFilter(query string) Filter {
// MetadataFilter return a Filter that match a bug metadata at creation time
func MetadataFilter(pair query.StringPair) Filter {
- return func(excerpt *BugExcerpt, resolver resolver) bool {
+ return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
if value, ok := excerpt.CreateMetadata[pair.Key]; ok {
return value == pair.Value
}
@@ -50,7 +44,7 @@ func MetadataFilter(pair query.StringPair) Filter {
// LabelFilter return a Filter that match a label
func LabelFilter(label string) Filter {
- return func(excerpt *BugExcerpt, resolver resolver) bool {
+ return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
for _, l := range excerpt.Labels {
if string(l) == label {
return true
@@ -62,11 +56,11 @@ func LabelFilter(label string) Filter {
// ActorFilter return a Filter that match a bug actor
func ActorFilter(query string) Filter {
- return func(excerpt *BugExcerpt, resolver resolver) bool {
+ return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
query = strings.ToLower(query)
for _, id := range excerpt.Actors {
- identityExcerpt, err := resolver.ResolveIdentityExcerpt(id)
+ identityExcerpt, err := entity.Resolve[*IdentityExcerpt](resolvers, id)
if err != nil {
panic(err)
}
@@ -81,11 +75,11 @@ func ActorFilter(query string) Filter {
// ParticipantFilter return a Filter that match a bug participant
func ParticipantFilter(query string) Filter {
- return func(excerpt *BugExcerpt, resolver resolver) bool {
+ return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
query = strings.ToLower(query)
for _, id := range excerpt.Participants {
- identityExcerpt, err := resolver.ResolveIdentityExcerpt(id)
+ identityExcerpt, err := entity.Resolve[*IdentityExcerpt](resolvers, id)
if err != nil {
panic(err)
}
@@ -100,7 +94,7 @@ func ParticipantFilter(query string) Filter {
// TitleFilter return a Filter that match if the title contains the given query
func TitleFilter(query string) Filter {
- return func(excerpt *BugExcerpt, resolver resolver) bool {
+ return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
return strings.Contains(
strings.ToLower(excerpt.Title),
strings.ToLower(query),
@@ -110,7 +104,7 @@ func TitleFilter(query string) Filter {
// NoLabelFilter return a Filter that match the absence of labels
func NoLabelFilter() Filter {
- return func(excerpt *BugExcerpt, resolver resolver) bool {
+ return func(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
return len(excerpt.Labels) == 0
}
}
@@ -161,36 +155,36 @@ func compileMatcher(filters query.Filters) *Matcher {
}
// Match check if a bug match the set of filters
-func (f *Matcher) Match(excerpt *BugExcerpt, resolver resolver) bool {
- if match := f.orMatch(f.Status, excerpt, resolver); !match {
+func (f *Matcher) Match(excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
+ if match := f.orMatch(f.Status, excerpt, resolvers); !match {
return false
}
- if match := f.orMatch(f.Author, excerpt, resolver); !match {
+ if match := f.orMatch(f.Author, excerpt, resolvers); !match {
return false
}
- if match := f.orMatch(f.Metadata, excerpt, resolver); !match {
+ if match := f.orMatch(f.Metadata, excerpt, resolvers); !match {
return false
}
- if match := f.orMatch(f.Participant, excerpt, resolver); !match {
+ if match := f.orMatch(f.Participant, excerpt, resolvers); !match {
return false
}
- if match := f.orMatch(f.Actor, excerpt, resolver); !match {
+ if match := f.orMatch(f.Actor, excerpt, resolvers); !match {
return false
}
- if match := f.andMatch(f.Label, excerpt, resolver); !match {
+ if match := f.andMatch(f.Label, excerpt, resolvers); !match {
return false
}
- if match := f.andMatch(f.NoFilters, excerpt, resolver); !match {
+ if match := f.andMatch(f.NoFilters, excerpt, resolvers); !match {
return false
}
- if match := f.andMatch(f.Title, excerpt, resolver); !match {
+ if match := f.andMatch(f.Title, excerpt, resolvers); !match {
return false
}
@@ -198,28 +192,28 @@ func (f *Matcher) Match(excerpt *BugExcerpt, resolver resolver) bool {
}
// Check if any of the filters provided match the bug
-func (*Matcher) orMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
+func (*Matcher) orMatch(filters []Filter, excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
if len(filters) == 0 {
return true
}
match := false
for _, f := range filters {
- match = match || f(excerpt, resolver)
+ match = match || f(excerpt, resolvers)
}
return match
}
// Check if all the filters provided match the bug
-func (*Matcher) andMatch(filters []Filter, excerpt *BugExcerpt, resolver resolver) bool {
+func (*Matcher) andMatch(filters []Filter, excerpt *BugExcerpt, resolvers entity.Resolvers) bool {
if len(filters) == 0 {
return true
}
match := true
for _, f := range filters {
- match = match && f(excerpt, resolver)
+ match = match && f(excerpt, resolvers)
}
return match
diff --git a/cache/identity_excerpt.go b/cache/identity_excerpt.go
index 126c712b..79d88537 100644
--- a/cache/identity_excerpt.go
+++ b/cache/identity_excerpt.go
@@ -5,7 +5,6 @@ import (
"fmt"
"strings"
- "github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
)
@@ -14,6 +13,8 @@ func init() {
gob.Register(IdentityExcerpt{})
}
+var _ Excerpt = &IdentityExcerpt{}
+
// IdentityExcerpt hold a subset of the identity values to be able to sort and
// filter identities efficiently without having to read and compile each raw
// identity.
@@ -25,7 +26,7 @@ type IdentityExcerpt struct {
ImmutableMetadata map[string]string
}
-func NewIdentityExcerpt(i *identity.Identity) *IdentityExcerpt {
+func NewIdentityExcerpt(i *IdentityCache) *IdentityExcerpt {
return &IdentityExcerpt{
id: i.Id(),
Name: i.Name(),
@@ -34,6 +35,10 @@ func NewIdentityExcerpt(i *identity.Identity) *IdentityExcerpt {
}
}
+func (i *IdentityExcerpt) setId(id entity.Id) {
+ i.id = id
+}
+
func (i *IdentityExcerpt) Id() entity.Id {
return i.id
}
diff --git a/cache/identity_subcache.go b/cache/identity_subcache.go
index b175d731..a623d0e1 100644
--- a/cache/identity_subcache.go
+++ b/cache/identity_subcache.go
@@ -20,18 +20,34 @@ func NewRepoCacheIdentity(repo repository.ClockedRepo,
return NewIdentityCache(i, repo, entityUpdated)
}
- makeExcerpt := func(i *identity.Identity) *IdentityExcerpt {
- return NewIdentityExcerpt(i)
- }
-
makeIndex := func(i *IdentityCache) []string {
// no indexing
return nil
}
+ // TODO: this is terribly ugly, but we are currently stuck with the fact that identities are NOT using the fancy dag framework.
+ // This lead to various complication here and there to handle entities generically, and avoid large code duplication.
+ // TL;DR: something has to give, and this is the less ugly solution I found. This "normalize" identities as just another "dag framework"
+ // entity. Ideally identities would be converted to the dag framework, but right now that could lead to potential attack: if an old
+ // private key is leaked, it would be possible to craft a legal identity update that take over the most recent version. While this is
+ // meaningless in the case of a normal entity, it's really an issues for identities.
+
+ actions := Actions[*identity.Identity]{
+ ReadWithResolver: func(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (*identity.Identity, error) {
+ return identity.ReadLocal(repo, id)
+ },
+ ReadAllWithResolver: func(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[*identity.Identity] {
+ return identity.ReadAllLocal(repo)
+ },
+ Remove: identity.RemoveIdentity,
+ MergeAll: func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) <-chan entity.MergeResult {
+ return identity.MergeAll(repo, remote)
+ },
+ }
+
sc := NewSubCache[*identity.Identity, *IdentityExcerpt, *IdentityCache](
repo, resolvers, getUserIdentity,
- makeCached, makeExcerpt, makeIndex,
+ makeCached, NewIdentityExcerpt, makeIndex, actions,
"identity", "identities",
formatVersion, defaultMaxLoadedBugs,
)
@@ -47,32 +63,32 @@ func (c *RepoCacheIdentity) ResolveIdentityImmutableMetadata(key string, value s
})
}
-func (c *RepoCacheIdentity) NewIdentityFromGitUser() (*IdentityCache, error) {
- return c.NewIdentityFromGitUserRaw(nil)
+// New create a new identity
+// The new identity is written in the repository (commit)
+func (c *RepoCacheIdentity) New(name string, email string) (*IdentityCache, error) {
+ return c.NewRaw(name, email, "", "", nil, nil)
}
-func (c *RepoCacheIdentity) NewIdentityFromGitUserRaw(metadata map[string]string) (*IdentityCache, error) {
- i, err := identity.NewFromGitUser(c.repo)
+// NewFull create a new identity
+// The new identity is written in the repository (commit)
+func (c *RepoCacheIdentity) NewFull(name string, email string, login string, avatarUrl string, keys []*identity.Key) (*IdentityCache, error) {
+ return c.NewRaw(name, email, login, avatarUrl, keys, nil)
+}
+
+func (c *RepoCacheIdentity) NewRaw(name string, email string, login string, avatarUrl string, keys []*identity.Key, metadata map[string]string) (*IdentityCache, error) {
+ i, err := identity.NewIdentityFull(c.repo, name, email, login, avatarUrl, keys)
if err != nil {
return nil, err
}
return c.finishIdentity(i, metadata)
}
-// NewIdentity create a new identity
-// The new identity is written in the repository (commit)
-func (c *RepoCacheIdentity) NewIdentity(name string, email string) (*IdentityCache, error) {
- return c.NewIdentityRaw(name, email, "", "", nil, nil)
-}
-
-// NewIdentityFull create a new identity
-// The new identity is written in the repository (commit)
-func (c *RepoCacheIdentity) NewIdentityFull(name string, email string, login string, avatarUrl string, keys []*identity.Key) (*IdentityCache, error) {
- return c.NewIdentityRaw(name, email, login, avatarUrl, keys, nil)
+func (c *RepoCacheIdentity) NewFromGitUser() (*IdentityCache, error) {
+ return c.NewFromGitUserRaw(nil)
}
-func (c *RepoCacheIdentity) NewIdentityRaw(name string, email string, login string, avatarUrl string, keys []*identity.Key, metadata map[string]string) (*IdentityCache, error) {
- i, err := identity.NewIdentityFull(c.repo, name, email, login, avatarUrl, keys)
+func (c *RepoCacheIdentity) NewFromGitUserRaw(metadata map[string]string) (*IdentityCache, error) {
+ i, err := identity.NewFromGitUser(c.repo)
if err != nil {
return nil, err
}
diff --git a/cache/multi_repo_cache.go b/cache/multi_repo_cache.go
index 98f868e9..12d26e26 100644
--- a/cache/multi_repo_cache.go
+++ b/cache/multi_repo_cache.go
@@ -21,25 +21,25 @@ func NewMultiRepoCache() *MultiRepoCache {
}
// RegisterRepository register a named repository. Use this for multi-repo setup
-func (c *MultiRepoCache) RegisterRepository(ref string, repo repository.ClockedRepo) (*RepoCache, error) {
- r, err := NewRepoCache(repo)
+func (c *MultiRepoCache) RegisterRepository(ref string, repo repository.ClockedRepo) (*RepoCache, chan BuildEvent, error) {
+ r, events, err := NewRepoCache(repo)
if err != nil {
- return nil, err
+ return nil, nil, err
}
c.repos[ref] = r
- return r, nil
+ return r, events, nil
}
// RegisterDefaultRepository register an unnamed repository. Use this for mono-repo setup
-func (c *MultiRepoCache) RegisterDefaultRepository(repo repository.ClockedRepo) (*RepoCache, error) {
- r, err := NewRepoCache(repo)
+func (c *MultiRepoCache) RegisterDefaultRepository(repo repository.ClockedRepo) (*RepoCache, chan BuildEvent, error) {
+ r, events, err := NewRepoCache(repo)
if err != nil {
- return nil, err
+ return nil, nil, err
}
c.repos[defaultRepoName] = r
- return r, nil
+ return r, events, nil
}
// DefaultRepo retrieve the default repository
diff --git a/cache/repo_cache.go b/cache/repo_cache.go
index b2facac3..2cac711b 100644
--- a/cache/repo_cache.go
+++ b/cache/repo_cache.go
@@ -30,8 +30,10 @@ var _ repository.RepoKeyring = &RepoCache{}
type cacheMgmt interface {
Typename() string
Load() error
- Write() error
Build() error
+ SetCacheSize(size int)
+ MergeAll(remote string) <-chan entity.MergeResult
+ GetNamespace() string
Close() error
}
@@ -86,18 +88,24 @@ func NewNamedRepoCache(r repository.ClockedRepo, name string) (*RepoCache, chan
c.subcaches = append(c.subcaches, c.bugs)
c.resolvers = entity.Resolvers{
- &IdentityCache{}: entity.ResolverFunc[*IdentityCache](c.identities.Resolve),
- &BugCache{}: entity.ResolverFunc[*BugCache](c.bugs.Resolve),
+ &IdentityCache{}: entity.ResolverFunc[*IdentityCache](c.identities.Resolve),
+ &IdentityExcerpt{}: entity.ResolverFunc[*IdentityExcerpt](c.identities.ResolveExcerpt),
+ &BugCache{}: entity.ResolverFunc[*BugCache](c.bugs.Resolve),
+ &BugExcerpt{}: entity.ResolverFunc[*BugExcerpt](c.bugs.ResolveExcerpt),
}
err := c.lock()
if err != nil {
- return &RepoCache{}, nil, err
+ closed := make(chan BuildEvent)
+ close(closed)
+ return &RepoCache{}, closed, err
}
err = c.load()
if err == nil {
- return c, nil, nil
+ closed := make(chan BuildEvent)
+ close(closed)
+ return c, closed, nil
}
// Cache is either missing, broken or outdated. Rebuilding.
@@ -122,8 +130,9 @@ func (c *RepoCache) getResolvers() entity.Resolvers {
// setCacheSize change the maximum number of loaded bugs
func (c *RepoCache) setCacheSize(size int) {
- c.maxLoadedBugs = size
- c.evictIfNeeded()
+ for _, subcache := range c.subcaches {
+ subcache.SetCacheSize(size)
+ }
}
// load will try to read from the disk all the cache files
@@ -135,15 +144,6 @@ func (c *RepoCache) load() error {
return errWait.Wait()
}
-// write will serialize on disk all the cache files
-func (c *RepoCache) write() error {
- var errWait multierr.ErrWaitGroup
- for _, mgmt := range c.subcaches {
- errWait.Go(mgmt.Write)
- }
- return errWait.Wait()
-}
-
func (c *RepoCache) lock() error {
err := repoIsAvailable(c.repo)
if err != nil {
@@ -190,10 +190,14 @@ const (
BuildEventFinished
)
+// BuildEvent carry an event happening during the cache build process.
type BuildEvent struct {
+ // Err carry an error if the build process failed. If set, no other field matter.
+ Err error
+ // Typename is the name of the entity of which the event relate to.
Typename string
- Event BuildEventType
- Err error
+ // Event is the type of the event.
+ Event BuildEventType
}
func (c *RepoCache) buildCache() chan BuildEvent {
@@ -221,15 +225,6 @@ func (c *RepoCache) buildCache() chan BuildEvent {
return
}
- err = subcache.Write()
- if err != nil {
- out <- BuildEvent{
- Typename: subcache.Typename(),
- Err: err,
- }
- return
- }
-
out <- BuildEvent{
Typename: subcache.Typename(),
Event: BuildEventFinished,
diff --git a/cache/repo_cache_common.go b/cache/repo_cache_common.go
index 1aed04b2..f768b8e2 100644
--- a/cache/repo_cache_common.go
+++ b/cache/repo_cache_common.go
@@ -1,10 +1,11 @@
package cache
import (
+ "sync"
+
"github.com/go-git/go-billy/v5"
"github.com/pkg/errors"
- "github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/repository"
@@ -72,76 +73,40 @@ func (c *RepoCache) StoreData(data []byte) (repository.Hash, error) {
// Fetch retrieve updates from a remote
// This does not change the local bugs or identities state
func (c *RepoCache) Fetch(remote string) (string, error) {
- stdout1, err := identity.Fetch(c.repo, remote)
- if err != nil {
- return stdout1, err
- }
-
- stdout2, err := bug.Fetch(c.repo, remote)
- if err != nil {
- return stdout2, err
+ prefixes := make([]string, len(c.subcaches))
+ for i, subcache := range c.subcaches {
+ prefixes[i] = subcache.GetNamespace()
}
- return stdout1 + stdout2, nil
+ // fetch everything at once, to have a single auth step if required.
+ return c.repo.FetchRefs(remote, prefixes...)
}
// MergeAll will merge all the available remote bug and identities
func (c *RepoCache) MergeAll(remote string) <-chan entity.MergeResult {
out := make(chan entity.MergeResult)
- // Intercept merge results to update the cache properly
+ dependency := [][]cacheMgmt{
+ {c.identities},
+ {c.bugs},
+ }
+
+ // run MergeAll according to entities dependencies and merge the results
go func() {
defer close(out)
- author, err := c.GetUserIdentity()
- if err != nil {
- out <- entity.NewMergeError(err, "")
- return
- }
-
- results := identity.MergeAll(c.repo, remote)
- for result := range results {
- out <- result
-
- if result.Err != nil {
- continue
- }
-
- switch result.Status {
- case entity.MergeStatusNew, entity.MergeStatusUpdated:
- i := result.Entity.(*identity.Identity)
- c.muIdentity.Lock()
- c.identitiesExcerpts[result.Id] = NewIdentityExcerpt(i)
- c.muIdentity.Unlock()
+ for _, subcaches := range dependency {
+ var wg sync.WaitGroup
+ for _, subcache := range subcaches {
+ wg.Add(1)
+ go func(subcache cacheMgmt) {
+ for res := range subcache.MergeAll(remote) {
+ out <- res
+ }
+ wg.Done()
+ }(subcache)
}
- }
-
- results = bug.MergeAll(c.repo, c.resolvers, remote, author)
- for result := range results {
- out <- result
-
- if result.Err != nil {
- continue
- }
-
- // TODO: have subcache do the merging?
- switch result.Status {
- case entity.MergeStatusNew:
- b := result.Entity.(*bug.Bug)
- _, err := c.bugs.add(b)
- case entity.MergeStatusUpdated:
- _, err := c.bugs.entityUpdated(b)
- snap := b.Compile()
- c.muBug.Lock()
- c.bugExcerpts[result.Id] = NewBugExcerpt(b, snap)
- c.muBug.Unlock()
- }
- }
-
- err = c.write()
- if err != nil {
- out <- entity.NewMergeError(err, "")
- return
+ wg.Wait()
}
}()
@@ -150,17 +115,13 @@ func (c *RepoCache) MergeAll(remote string) <-chan entity.MergeResult {
// Push update a remote with the local changes
func (c *RepoCache) Push(remote string) (string, error) {
- stdout1, err := identity.Push(c.repo, remote)
- if err != nil {
- return stdout1, err
- }
-
- stdout2, err := bug.Push(c.repo, remote)
- if err != nil {
- return stdout2, err
+ prefixes := make([]string, len(c.subcaches))
+ for i, subcache := range c.subcaches {
+ prefixes[i] = subcache.GetNamespace()
}
- return stdout1 + stdout2, nil
+ // push everything at once, to have a single auth step if required
+ return c.repo.PushRefs(remote, prefixes...)
}
// Pull will do a Fetch + MergeAll
diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go
index 58ade144..395872f7 100644
--- a/cache/repo_cache_test.go
+++ b/cache/repo_cache_test.go
@@ -8,20 +8,27 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/query"
"github.com/MichaelMure/git-bug/repository"
)
+func noBuildEventErrors(t *testing.T, c chan BuildEvent) {
+ t.Helper()
+ for event := range c {
+ require.NoError(t, event.Err)
+ }
+}
+
func TestCache(t *testing.T) {
repo := repository.CreateGoGitTestRepo(t, false)
- cache, err := NewRepoCache(repo)
+ cache, events, err := NewRepoCache(repo)
+ noBuildEventErrors(t, events)
require.NoError(t, err)
// Create, set and get user identity
- iden1, err := cache.NewIdentity("René Descartes", "rene@descartes.fr")
+ iden1, err := cache.Identities().New("René Descartes", "rene@descartes.fr")
require.NoError(t, err)
err = cache.SetUserIdentity(iden1)
require.NoError(t, err)
@@ -30,102 +37,105 @@ func TestCache(t *testing.T) {
require.Equal(t, iden1.Id(), userIden.Id())
// it's possible to create two identical identities
- iden2, err := cache.NewIdentity("René Descartes", "rene@descartes.fr")
+ iden2, err := cache.Identities().New("René Descartes", "rene@descartes.fr")
require.NoError(t, err)
// Two identical identities yield a different id
require.NotEqual(t, iden1.Id(), iden2.Id())
// There is now two identities in the cache
- require.Len(t, cache.AllIdentityIds(), 2)
- require.Len(t, cache.identitiesExcerpts, 2)
- require.Len(t, cache.identities, 2)
+ require.Len(t, cache.Identities().AllIds(), 2)
+ require.Len(t, cache.identities.excerpts, 2)
+ require.Len(t, cache.identities.cached, 2)
// Create a bug
- bug1, _, err := cache.NewBug("title", "message")
+ bug1, _, err := cache.Bugs().New("title", "message")
require.NoError(t, err)
// It's possible to create two identical bugs
- bug2, _, err := cache.NewBug("title", "message")
+ bug2, _, err := cache.Bugs().New("title", "message")
require.NoError(t, err)
// two identical bugs yield a different id
require.NotEqual(t, bug1.Id(), bug2.Id())
// There is now two bugs in the cache
- require.Len(t, cache.AllBugsIds(), 2)
- require.Len(t, cache.bugExcerpts, 2)
- require.Len(t, cache.bugs, 2)
+ require.Len(t, cache.Bugs().AllIds(), 2)
+ require.Len(t, cache.bugs.excerpts, 2)
+ require.Len(t, cache.bugs.cached, 2)
// Resolving
- _, err = cache.ResolveIdentity(iden1.Id())
+ _, err = cache.Identities().Resolve(iden1.Id())
require.NoError(t, err)
- _, err = cache.ResolveIdentityExcerpt(iden1.Id())
+ _, err = cache.Identities().ResolveExcerpt(iden1.Id())
require.NoError(t, err)
- _, err = cache.ResolveIdentityPrefix(iden1.Id().String()[:10])
+ _, err = cache.Identities().ResolvePrefix(iden1.Id().String()[:10])
require.NoError(t, err)
- _, err = cache.ResolveBug(bug1.Id())
+ _, err = cache.Bugs().Resolve(bug1.Id())
require.NoError(t, err)
- _, err = cache.ResolveBugExcerpt(bug1.Id())
+ _, err = cache.Bugs().ResolveExcerpt(bug1.Id())
require.NoError(t, err)
- _, err = cache.ResolveBugPrefix(bug1.Id().String()[:10])
+ _, err = cache.Bugs().ResolvePrefix(bug1.Id().String()[:10])
require.NoError(t, err)
// Querying
q, err := query.Parse("status:open author:descartes sort:edit-asc")
require.NoError(t, err)
- res, err := cache.QueryBugs(q)
+ res, err := cache.Bugs().Query(q)
require.NoError(t, err)
require.Len(t, res, 2)
// Close
require.NoError(t, cache.Close())
- require.Empty(t, cache.bugs)
- require.Empty(t, cache.bugExcerpts)
- require.Empty(t, cache.identities)
- require.Empty(t, cache.identitiesExcerpts)
+ require.Empty(t, cache.bugs.cached)
+ require.Empty(t, cache.bugs.excerpts)
+ require.Empty(t, cache.identities.cached)
+ require.Empty(t, cache.identities.excerpts)
// Reload, only excerpt are loaded, but as we need to load the identities used in the bugs
// to check the signatures, we also load the identity used above
- cache, err = NewRepoCache(repo)
+ cache, events, err = NewRepoCache(repo)
+ noBuildEventErrors(t, events)
require.NoError(t, err)
- require.Empty(t, cache.bugs)
- require.Len(t, cache.identities, 1)
- require.Len(t, cache.bugExcerpts, 2)
- require.Len(t, cache.identitiesExcerpts, 2)
+ require.Empty(t, cache.bugs.cached)
+ require.Len(t, cache.bugs.excerpts, 2)
+ require.Len(t, cache.identities.cached, 0)
+ require.Len(t, cache.identities.excerpts, 2)
// Resolving load from the disk
- _, err = cache.ResolveIdentity(iden1.Id())
+ _, err = cache.Identities().Resolve(iden1.Id())
require.NoError(t, err)
- _, err = cache.ResolveIdentityExcerpt(iden1.Id())
+ _, err = cache.Identities().ResolveExcerpt(iden1.Id())
require.NoError(t, err)
- _, err = cache.ResolveIdentityPrefix(iden1.Id().String()[:10])
+ _, err = cache.Identities().ResolvePrefix(iden1.Id().String()[:10])
require.NoError(t, err)
- _, err = cache.ResolveBug(bug1.Id())
+ _, err = cache.Bugs().Resolve(bug1.Id())
require.NoError(t, err)
- _, err = cache.ResolveBugExcerpt(bug1.Id())
+ _, err = cache.Bugs().ResolveExcerpt(bug1.Id())
require.NoError(t, err)
- _, err = cache.ResolveBugPrefix(bug1.Id().String()[:10])
+ _, err = cache.Bugs().ResolvePrefix(bug1.Id().String()[:10])
require.NoError(t, err)
}
func TestCachePushPull(t *testing.T) {
repoA, repoB, _ := repository.SetupGoGitReposAndRemote(t)
- cacheA, err := NewRepoCache(repoA)
+ cacheA, events, err := NewRepoCache(repoA)
+ noBuildEventErrors(t, events)
require.NoError(t, err)
- cacheB, err := NewRepoCache(repoB)
+ cacheB, events, err := NewRepoCache(repoB)
+ noBuildEventErrors(t, events)
require.NoError(t, err)
// Create, set and get user identity
- reneA, err := cacheA.NewIdentity("René Descartes", "rene@descartes.fr")
+ reneA, err := cacheA.Identities().New("René Descartes", "rene@descartes.fr")
require.NoError(t, err)
err = cacheA.SetUserIdentity(reneA)
require.NoError(t, err)
- isaacB, err := cacheB.NewIdentity("Isaac Newton", "isaac@newton.uk")
+ isaacB, err := cacheB.Identities().New("Isaac Newton", "isaac@newton.uk")
require.NoError(t, err)
err = cacheB.SetUserIdentity(isaacB)
require.NoError(t, err)
@@ -137,7 +147,7 @@ func TestCachePushPull(t *testing.T) {
require.NoError(t, err)
// Create a bug in A
- _, _, err = cacheA.NewBug("bug1", "message")
+ _, _, err = cacheA.Bugs().New("bug1", "message")
require.NoError(t, err)
// A --> remote --> B
@@ -147,17 +157,17 @@ func TestCachePushPull(t *testing.T) {
err = cacheB.Pull("origin")
require.NoError(t, err)
- require.Len(t, cacheB.AllBugsIds(), 1)
+ require.Len(t, cacheB.Bugs().AllIds(), 1)
// retrieve and set identity
- reneB, err := cacheB.ResolveIdentity(reneA.Id())
+ reneB, err := cacheB.Identities().Resolve(reneA.Id())
require.NoError(t, err)
err = cacheB.SetUserIdentity(reneB)
require.NoError(t, err)
// B --> remote --> A
- _, _, err = cacheB.NewBug("bug2", "message")
+ _, _, err = cacheB.Bugs().New("bug2", "message")
require.NoError(t, err)
_, err = cacheB.Push("origin")
@@ -166,7 +176,7 @@ func TestCachePushPull(t *testing.T) {
err = cacheA.Pull("origin")
require.NoError(t, err)
- require.Len(t, cacheA.AllBugsIds(), 2)
+ require.Len(t, cacheA.Bugs().AllIds(), 2)
}
func TestRemove(t *testing.T) {
@@ -180,20 +190,21 @@ func TestRemove(t *testing.T) {
err = repo.AddRemote("remoteB", remoteB.GetLocalRemote())
require.NoError(t, err)
- repoCache, err := NewRepoCache(repo)
+ repoCache, events, err := NewRepoCache(repo)
+ noBuildEventErrors(t, events)
require.NoError(t, err)
- rene, err := repoCache.NewIdentity("René Descartes", "rene@descartes.fr")
+ rene, err := repoCache.Identities().New("René Descartes", "rene@descartes.fr")
require.NoError(t, err)
err = repoCache.SetUserIdentity(rene)
require.NoError(t, err)
- _, _, err = repoCache.NewBug("title", "message")
+ _, _, err = repoCache.Bugs().New("title", "message")
require.NoError(t, err)
// and one more for testing
- b1, _, err := repoCache.NewBug("title", "message")
+ b1, _, err := repoCache.Bugs().New("title", "message")
require.NoError(t, err)
_, err = repoCache.Push("remoteA")
@@ -208,72 +219,73 @@ func TestRemove(t *testing.T) {
_, err = repoCache.Fetch("remoteB")
require.NoError(t, err)
- err = repoCache.RemoveBug(b1.Id().String())
+ err = repoCache.Bugs().Remove(b1.Id().String())
require.NoError(t, err)
- assert.Equal(t, 1, len(repoCache.bugs))
- assert.Equal(t, 1, len(repoCache.bugExcerpts))
+ assert.Len(t, repoCache.bugs.cached, 1)
+ assert.Len(t, repoCache.bugs.excerpts, 1)
- _, err = repoCache.ResolveBug(b1.Id())
- assert.ErrorIs(t, entity.ErrNotFound{}, err)
+ _, err = repoCache.Bugs().Resolve(b1.Id())
+ assert.ErrorAs(t, entity.ErrNotFound{}, err)
}
func TestCacheEviction(t *testing.T) {
repo := repository.CreateGoGitTestRepo(t, false)
- repoCache, err := NewRepoCache(repo)
+ repoCache, events, err := NewRepoCache(repo)
+ noBuildEventErrors(t, events)
require.NoError(t, err)
repoCache.setCacheSize(2)
- require.Equal(t, 2, repoCache.maxLoadedBugs)
- require.Equal(t, 0, repoCache.loadedBugs.Len())
- require.Equal(t, 0, len(repoCache.bugs))
+ require.Equal(t, 2, repoCache.bugs.maxLoaded)
+ require.Len(t, repoCache.bugs.cached, 0)
+ require.Equal(t, repoCache.bugs.lru.Len(), 0)
// Generating some bugs
- rene, err := repoCache.NewIdentity("René Descartes", "rene@descartes.fr")
+ rene, err := repoCache.Identities().New("René Descartes", "rene@descartes.fr")
require.NoError(t, err)
err = repoCache.SetUserIdentity(rene)
require.NoError(t, err)
- bug1, _, err := repoCache.NewBug("title", "message")
+ bug1, _, err := repoCache.Bugs().New("title", "message")
require.NoError(t, err)
checkBugPresence(t, repoCache, bug1, true)
- require.Equal(t, 1, repoCache.loadedBugs.Len())
- require.Equal(t, 1, len(repoCache.bugs))
+ require.Len(t, repoCache.bugs.cached, 1)
+ require.Equal(t, 1, repoCache.bugs.lru.Len())
- bug2, _, err := repoCache.NewBug("title", "message")
+ bug2, _, err := repoCache.Bugs().New("title", "message")
require.NoError(t, err)
checkBugPresence(t, repoCache, bug1, true)
checkBugPresence(t, repoCache, bug2, true)
- require.Equal(t, 2, repoCache.loadedBugs.Len())
- require.Equal(t, 2, len(repoCache.bugs))
+ require.Len(t, repoCache.bugs.cached, 2)
+ require.Equal(t, 2, repoCache.bugs.lru.Len())
// Number of bugs should not exceed max size of lruCache, oldest one should be evicted
- bug3, _, err := repoCache.NewBug("title", "message")
+ bug3, _, err := repoCache.Bugs().New("title", "message")
require.NoError(t, err)
- require.Equal(t, 2, repoCache.loadedBugs.Len())
- require.Equal(t, 2, len(repoCache.bugs))
+ require.Len(t, repoCache.bugs.cached, 2)
+ require.Equal(t, 2, repoCache.bugs.lru.Len())
checkBugPresence(t, repoCache, bug1, false)
checkBugPresence(t, repoCache, bug2, true)
checkBugPresence(t, repoCache, bug3, true)
// Accessing bug should update position in lruCache and therefore it should not be evicted
- repoCache.loadedBugs.Get(bug2.Id())
- oldestId, _ := repoCache.loadedBugs.GetOldest()
+ repoCache.bugs.lru.Get(bug2.Id())
+ oldestId, _ := repoCache.bugs.lru.GetOldest()
require.Equal(t, bug3.Id(), oldestId)
checkBugPresence(t, repoCache, bug1, false)
checkBugPresence(t, repoCache, bug2, true)
checkBugPresence(t, repoCache, bug3, true)
- require.Equal(t, 2, repoCache.loadedBugs.Len())
- require.Equal(t, 2, len(repoCache.bugs))
+ require.Len(t, repoCache.bugs.cached, 2)
+ require.Equal(t, 2, repoCache.bugs.lru.Len())
}
func checkBugPresence(t *testing.T, cache *RepoCache, bug *BugCache, presence bool) {
id := bug.Id()
- require.Equal(t, presence, cache.loadedBugs.Contains(id))
- b, ok := cache.bugs[id]
+ require.Equal(t, presence, cache.bugs.lru.Contains(id))
+ b, ok := cache.bugs.cached[id]
require.Equal(t, presence, ok)
if ok {
require.Equal(t, bug, b)
@@ -287,12 +299,13 @@ func TestLongDescription(t *testing.T) {
repo := repository.CreateGoGitTestRepo(t, false)
- backend, err := NewRepoCache(repo)
+ backend, events, err := NewRepoCache(repo)
+ noBuildEventErrors(t, events)
require.NoError(t, err)
- i, err := backend.NewIdentity("René Descartes", "rene@descartes.fr")
+ i, err := backend.Identities().New("René Descartes", "rene@descartes.fr")
require.NoError(t, err)
- _, _, err = backend.NewBugRaw(i, time.Now().Unix(), text, text, nil, nil)
+ _, _, err = backend.Bugs().NewRaw(i, time.Now().Unix(), text, text, nil, nil)
require.NoError(t, err)
}
diff --git a/cache/subcache.go b/cache/subcache.go
index 1737da43..0678988a 100644
--- a/cache/subcache.go
+++ b/cache/subcache.go
@@ -4,18 +4,18 @@ import (
"bytes"
"encoding/gob"
"fmt"
- "os"
"sync"
"github.com/pkg/errors"
- "github.com/MichaelMure/git-bug/entities/bug"
+ "github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/repository"
)
type Excerpt interface {
Id() entity.Id
+ setId(id entity.Id)
}
type CacheEntity interface {
@@ -25,15 +25,27 @@ type CacheEntity interface {
type getUserIdentityFunc func() (*IdentityCache, error)
+// Actions expose a number of action functions on Entities, to give upper layers (cache) a way to normalize interactions.
+// Note: ideally this wouldn't exist, the cache layer would assume that everything is an entity/dag, and directly use the
+// functions from this package, but right now identities are not using that framework.
+type Actions[EntityT entity.Interface] struct {
+ ReadWithResolver func(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (EntityT, error)
+ ReadAllWithResolver func(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[EntityT]
+ Remove func(repo repository.ClockedRepo, id entity.Id) error
+ MergeAll func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) <-chan entity.MergeResult
+}
+
+var _ cacheMgmt = &SubCache[entity.Interface, Excerpt, CacheEntity]{}
+
type SubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity] struct {
repo repository.ClockedRepo
resolvers func() entity.Resolvers
- getUserIdentity getUserIdentityFunc
- readWithResolver func(repository.ClockedRepo, entity.Resolvers, entity.Id) (EntityT, error)
- makeCached func(entity EntityT, entityUpdated func(id entity.Id) error) CacheT
- makeExcerpt func(EntityT) ExcerptT
- makeIndex func(CacheT) []string
+ getUserIdentity getUserIdentityFunc
+ makeCached func(entity EntityT, entityUpdated func(id entity.Id) error) CacheT
+ makeExcerpt func(CacheT) ExcerptT
+ makeIndexData func(CacheT) []string
+ actions Actions[EntityT]
typename string
namespace string
@@ -50,14 +62,19 @@ func NewSubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity]
repo repository.ClockedRepo,
resolvers func() entity.Resolvers, getUserIdentity getUserIdentityFunc,
makeCached func(entity EntityT, entityUpdated func(id entity.Id) error) CacheT,
- makeExcerpt func(EntityT) ExcerptT,
- makeIndex func(CacheT) []string,
+ makeExcerpt func(CacheT) ExcerptT,
+ makeIndexData func(CacheT) []string,
+ actions Actions[EntityT],
typename, namespace string,
version uint, maxLoaded int) *SubCache[EntityT, ExcerptT, CacheT] {
return &SubCache[EntityT, ExcerptT, CacheT]{
repo: repo,
resolvers: resolvers,
getUserIdentity: getUserIdentity,
+ makeCached: makeCached,
+ makeExcerpt: makeExcerpt,
+ makeIndexData: makeIndexData,
+ actions: actions,
typename: typename,
namespace: namespace,
version: version,
@@ -98,6 +115,12 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Load() error {
return fmt.Errorf("unknown %s cache format version %v", sc.namespace, aux.Version)
}
+ // the id is not serialized in the excerpt itself (non-exported field in go, long story ...),
+ // so we fix it here, which doubles as enforcing coherency.
+ for id, excerpt := range aux.Excerpts {
+ excerpt.setId(id)
+ }
+
sc.excerpts = aux.Excerpts
index, err := sc.repo.GetIndex(sc.typename)
@@ -118,7 +141,7 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Load() error {
}
// Write will serialize on disk the entity cache file
-func (sc *SubCache[EntityT, ExcerptT, CacheT]) Write() error {
+func (sc *SubCache[EntityT, ExcerptT, CacheT]) write() error {
sc.mu.RLock()
defer sc.mu.RUnlock()
@@ -155,9 +178,7 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Write() error {
func (sc *SubCache[EntityT, ExcerptT, CacheT]) Build() error {
sc.excerpts = make(map[entity.Id]ExcerptT)
- sc.readWithResolver
-
- allBugs := bug.ReadAllWithResolver(c.repo, c.resolvers)
+ allEntities := sc.actions.ReadAllWithResolver(sc.repo, sc.resolvers())
index, err := sc.repo.GetIndex(sc.typename)
if err != nil {
@@ -172,15 +193,17 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Build() error {
indexer, indexEnd := index.IndexBatch()
- for b := range allBugs {
- if b.Err != nil {
- return b.Err
+ for e := range allEntities {
+ if e.Err != nil {
+ return e.Err
}
- snap := b.Bug.Compile()
- c.bugExcerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, snap)
+ // TODO: doesn't actually record in cache, should we?
+ cached := sc.makeCached(e.Entity, sc.entityUpdated)
+ sc.excerpts[e.Entity.Id()] = sc.makeExcerpt(cached)
- if err := indexer(snap); err != nil {
+ indexData := sc.makeIndexData(cached)
+ if err := indexer(e.Entity.Id().String(), indexData); err != nil {
return err
}
}
@@ -190,10 +213,19 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Build() error {
return err
}
- _, _ = fmt.Fprintln(os.Stderr, "Done.")
+ err = sc.write()
+ if err != nil {
+ return err
+ }
+
return nil
}
+func (sc *SubCache[EntityT, ExcerptT, CacheT]) SetCacheSize(size int) {
+ sc.maxLoaded = size
+ sc.evictIfNeeded()
+}
+
func (sc *SubCache[EntityT, ExcerptT, CacheT]) Close() error {
sc.mu.Lock()
defer sc.mu.Unlock()
@@ -229,7 +261,7 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Resolve(id entity.Id) (CacheT, er
}
sc.mu.RUnlock()
- e, err := sc.readWithResolver(sc.repo, sc.resolvers(), id)
+ e, err := sc.actions.ReadWithResolver(sc.repo, sc.resolvers(), id)
if err != nil {
return *new(CacheT), err
}
@@ -315,8 +347,6 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) resolveMatcher(f func(ExcerptT) b
return matching[0], nil
}
-var errNotInCache = errors.New("entity missing from cache")
-
func (sc *SubCache[EntityT, ExcerptT, CacheT]) add(e EntityT) (CacheT, error) {
sc.mu.Lock()
if _, has := sc.cached[e.Id()]; has {
@@ -348,26 +378,74 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Remove(prefix string) error {
sc.mu.Lock()
- err = bug.Remove(c.repo, b.Id())
+ err = sc.actions.Remove(sc.repo, e.Id())
if err != nil {
- c.muBug.Unlock()
-
+ sc.mu.Unlock()
return err
}
- delete(c.bugs, b.Id())
- delete(c.bugExcerpts, b.Id())
- c.loadedBugs.Remove(b.Id())
+ delete(sc.cached, e.Id())
+ delete(sc.excerpts, e.Id())
+ sc.lru.Remove(e.Id())
+
+ sc.mu.Unlock()
+
+ return sc.write()
+}
+
+func (sc *SubCache[EntityT, ExcerptT, CacheT]) MergeAll(remote string) <-chan entity.MergeResult {
+ out := make(chan entity.MergeResult)
- c.muBug.Unlock()
+ // Intercept merge results to update the cache properly
+ go func() {
+ defer close(out)
+
+ author, err := sc.getUserIdentity()
+ if err != nil {
+ out <- entity.NewMergeError(err, "")
+ return
+ }
+
+ results := sc.actions.MergeAll(sc.repo, sc.resolvers(), remote, author)
+ for result := range results {
+ out <- result
+
+ if result.Err != nil {
+ continue
+ }
+
+ switch result.Status {
+ case entity.MergeStatusNew, entity.MergeStatusUpdated:
+ e := result.Entity.(EntityT)
+
+ // TODO: doesn't actually record in cache, should we?
+ cached := sc.makeCached(e, sc.entityUpdated)
+
+ sc.mu.Lock()
+ sc.excerpts[result.Id] = sc.makeExcerpt(cached)
+ sc.mu.Unlock()
+ }
+ }
+
+ err = sc.write()
+ if err != nil {
+ out <- entity.NewMergeError(err, "")
+ return
+ }
+ }()
+
+ return out
- return c.writeBugCache()
+}
+
+func (sc *SubCache[EntityT, ExcerptT, CacheT]) GetNamespace() string {
+ return sc.namespace
}
// entityUpdated is a callback to trigger when the excerpt of an entity changed
func (sc *SubCache[EntityT, ExcerptT, CacheT]) entityUpdated(id entity.Id) error {
sc.mu.Lock()
- b, ok := sc.cached[id]
+ e, ok := sc.cached[id]
if !ok {
sc.mu.Unlock()
@@ -376,19 +454,24 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) entityUpdated(id entity.Id) error
// memory and thus concurrent write.
// Failing immediately here is the simple and safe solution to avoid
// complicated data loss.
- return errNotInCache
+ return errors.New("entity missing from cache")
}
sc.lru.Get(id)
// sc.excerpts[id] = bug2.NewBugExcerpt(b.bug, b.Snapshot())
- sc.excerpts[id] = bug2.NewBugExcerpt(b.bug, b.Snapshot())
+ sc.excerpts[id] = sc.makeExcerpt(e)
sc.mu.Unlock()
- if err := sc.addBugToSearchIndex(b.Snapshot()); err != nil {
+ index, err := sc.repo.GetIndex(sc.typename)
+ if err != nil {
+ return err
+ }
+
+ err = index.IndexOne(e.Id().String(), sc.makeIndexData(e))
+ if err != nil {
return err
}
- // we only need to write the bug cache
- return sc.Write()
+ return sc.write()
}
// evictIfNeeded will evict an entity from the cache if needed
@@ -405,7 +488,8 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) evictIfNeeded() {
continue
}
- b.Lock()
+ // TODO
+ // b.Lock()
sc.lru.Remove(id)
delete(sc.cached, id)
diff --git a/commands/bridge/bridge_auth_addtoken.go b/commands/bridge/bridge_auth_addtoken.go
index bcab7fc3..2992fa63 100644
--- a/commands/bridge/bridge_auth_addtoken.go
+++ b/commands/bridge/bridge_auth_addtoken.go
@@ -94,7 +94,7 @@ func runBridgeAuthAddToken(env *execenv.Env, opts bridgeAuthAddTokenOptions, arg
if opts.user == "" {
user, err = env.Backend.GetUserIdentity()
} else {
- user, err = env.Backend.ResolveIdentityPrefix(opts.user)
+ user, err = env.Backend.Identities().ResolvePrefix(opts.user)
}
if err != nil {
return err
diff --git a/commands/bug/bug.go b/commands/bug/bug.go
index 04bf8980..bab040d8 100644
--- a/commands/bug/bug.go
+++ b/commands/bug/bug.go
@@ -142,14 +142,14 @@ func runBug(env *execenv.Env, opts bugOptions, args []string) error {
return err
}
- allIds, err := env.Backend.QueryBugs(q)
+ allIds, err := env.Backend.Bugs().Query(q)
if err != nil {
return err
}
bugExcerpt := make([]*cache.BugExcerpt, len(allIds))
for i, id := range allIds {
- b, err := env.Backend.ResolveBugExcerpt(id)
+ b, err := env.Backend.Bugs().ResolveExcerpt(id)
if err != nil {
return err
}
@@ -208,8 +208,8 @@ func bugsJsonFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error
jsonBugs := make([]JSONBugExcerpt, len(bugExcerpts))
for i, b := range bugExcerpts {
jsonBug := JSONBugExcerpt{
- Id: b.Id.String(),
- HumanId: b.Id.Human(),
+ Id: b.Id().String(),
+ HumanId: b.Id().Human(),
CreateTime: cmdjson.NewTime(b.CreateTime(), b.CreateLamportTime),
EditTime: cmdjson.NewTime(b.EditTime(), b.EditLamportTime),
Status: b.Status.String(),
@@ -219,7 +219,7 @@ func bugsJsonFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error
Metadata: b.CreateMetadata,
}
- author, err := env.Backend.ResolveIdentityExcerpt(b.AuthorId)
+ author, err := env.Backend.Identities().ResolveExcerpt(b.AuthorId)
if err != nil {
return err
}
@@ -227,7 +227,7 @@ func bugsJsonFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error
jsonBug.Actors = make([]cmdjson.Identity, len(b.Actors))
for i, element := range b.Actors {
- actor, err := env.Backend.ResolveIdentityExcerpt(element)
+ actor, err := env.Backend.Identities().ResolveExcerpt(element)
if err != nil {
return err
}
@@ -236,7 +236,7 @@ func bugsJsonFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error
jsonBug.Participants = make([]cmdjson.Identity, len(b.Participants))
for i, element := range b.Participants {
- participant, err := env.Backend.ResolveIdentityExcerpt(element)
+ participant, err := env.Backend.Identities().ResolveExcerpt(element)
if err != nil {
return err
}
@@ -252,7 +252,7 @@ func bugsJsonFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error
func bugsCompactFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
for _, b := range bugExcerpts {
- author, err := env.Backend.ResolveIdentityExcerpt(b.AuthorId)
+ author, err := env.Backend.Identities().ResolveExcerpt(b.AuthorId)
if err != nil {
return err
}
@@ -266,7 +266,7 @@ func bugsCompactFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
}
env.Out.Printf("%s %s %s %s %s\n",
- colors.Cyan(b.Id.Human()),
+ colors.Cyan(b.Id().Human()),
colors.Yellow(b.Status),
text.LeftPadMaxLine(strings.TrimSpace(b.Title), 40, 0),
text.LeftPadMaxLine(labelsTxt.String(), 5, 0),
@@ -278,7 +278,7 @@ func bugsCompactFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
func bugsIDFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
for _, b := range bugExcerpts {
- env.Out.Println(b.Id.String())
+ env.Out.Println(b.Id().String())
}
return nil
@@ -286,7 +286,7 @@ func bugsIDFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
for _, b := range bugExcerpts {
- author, err := env.Backend.ResolveIdentityExcerpt(b.AuthorId)
+ author, err := env.Backend.Identities().ResolveExcerpt(b.AuthorId)
if err != nil {
return err
}
@@ -313,7 +313,7 @@ func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
}
env.Out.Printf("%s\t%s\t%s\t%s\t%s\n",
- colors.Cyan(b.Id.Human()),
+ colors.Cyan(b.Id().Human()),
colors.Yellow(b.Status),
titleFmt+labelsFmt,
colors.Magenta(authorFmt),
@@ -325,7 +325,7 @@ func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
func bugsPlainFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
for _, b := range bugExcerpts {
- env.Out.Printf("%s [%s] %s\n", b.Id.Human(), b.Status, strings.TrimSpace(b.Title))
+ env.Out.Printf("%s [%s] %s\n", b.Id().Human(), b.Status, strings.TrimSpace(b.Title))
}
return nil
}
@@ -353,7 +353,7 @@ func bugsOrgmodeFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
title = b.Title
}
- author, err := env.Backend.ResolveIdentityExcerpt(b.AuthorId)
+ author, err := env.Backend.Identities().ResolveExcerpt(b.AuthorId)
if err != nil {
return err
}
@@ -370,7 +370,7 @@ func bugsOrgmodeFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
env.Out.Printf("* %-6s %s %s %s: %s %s\n",
status,
- b.Id.Human(),
+ b.Id().Human(),
formatTime(b.CreateTime()),
author.DisplayName(),
title,
@@ -381,26 +381,26 @@ func bugsOrgmodeFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
env.Out.Printf("** Actors:\n")
for _, element := range b.Actors {
- actor, err := env.Backend.ResolveIdentityExcerpt(element)
+ actor, err := env.Backend.Identities().ResolveExcerpt(element)
if err != nil {
return err
}
env.Out.Printf(": %s %s\n",
- actor.Id.Human(),
+ actor.Id().Human(),
actor.DisplayName(),
)
}
env.Out.Printf("** Participants:\n")
for _, element := range b.Participants {
- participant, err := env.Backend.ResolveIdentityExcerpt(element)
+ participant, err := env.Backend.Identities().ResolveExcerpt(element)
if err != nil {
return err
}
env.Out.Printf(": %s %s\n",
- participant.Id.Human(),
+ participant.Id().Human(),
participant.DisplayName(),
)
}
diff --git a/commands/bug/bug_comment_edit.go b/commands/bug/bug_comment_edit.go
index 8be7cb80..2a0289f5 100644
--- a/commands/bug/bug_comment_edit.go
+++ b/commands/bug/bug_comment_edit.go
@@ -41,7 +41,7 @@ func newBugCommentEditCommand() *cobra.Command {
}
func runBugCommentEdit(env *execenv.Env, opts bugCommentEditOptions, args []string) error {
- b, commentId, err := env.Backend.ResolveComment(args[0])
+ b, commentId, err := env.Backend.Bugs().ResolveComment(args[0])
if err != nil {
return err
}
diff --git a/commands/bug/bug_new.go b/commands/bug/bug_new.go
index 4f73a09c..fbfb9def 100644
--- a/commands/bug/bug_new.go
+++ b/commands/bug/bug_new.go
@@ -63,7 +63,7 @@ func runBugNew(env *execenv.Env, opts bugNewOptions) error {
}
}
- b, _, err := env.Backend.NewBug(
+ b, _, err := env.Backend.Bugs().New(
text.CleanupOneLine(opts.title),
text.Cleanup(opts.message),
)
diff --git a/commands/bug/bug_rm.go b/commands/bug/bug_rm.go
index 1d2a7524..04881d54 100644
--- a/commands/bug/bug_rm.go
+++ b/commands/bug/bug_rm.go
@@ -34,7 +34,7 @@ func runBugRm(env *execenv.Env, args []string) (err error) {
return errors.New("you must provide a bug prefix to remove")
}
- err = env.Backend.RemoveBug(args[0])
+ err = env.Backend.Bugs().Remove(args[0])
if err != nil {
return
diff --git a/commands/bug/bug_select.go b/commands/bug/bug_select.go
index 0b1cb15c..2a4d1201 100644
--- a/commands/bug/bug_select.go
+++ b/commands/bug/bug_select.go
@@ -46,7 +46,7 @@ func runBugSelect(env *execenv.Env, args []string) error {
prefix := args[0]
- b, err := env.Backend.ResolveBugPrefix(prefix)
+ b, err := env.Backend.Bugs().ResolvePrefix(prefix)
if err != nil {
return err
}
diff --git a/commands/bug/select/select.go b/commands/bug/select/select.go
index 42d65bc2..7096dde4 100644
--- a/commands/bug/select/select.go
+++ b/commands/bug/select/select.go
@@ -9,7 +9,6 @@ import (
"github.com/pkg/errors"
"github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entity"
)
@@ -28,7 +27,7 @@ var ErrNoValidId = errors.New("you must provide a bug id or use the \"select\" c
func ResolveBug(repo *cache.RepoCache, args []string) (*cache.BugCache, []string, error) {
// At first, try to use the first argument as a bug prefix
if len(args) > 0 {
- b, err := repo.ResolveBugPrefix(args[0])
+ b, err := repo.Bugs().ResolvePrefix(args[0])
if err == nil {
return b, args[1:], nil
@@ -115,7 +114,7 @@ func selected(repo *cache.RepoCache) (*cache.BugCache, error) {
return nil, fmt.Errorf("select file in invalid, removing it")
}
- b, err := repo.ResolveBug(id)
+ b, err := repo.Bugs().Resolve(id)
if err != nil {
return nil, err
}
diff --git a/commands/bug/select/select_test.go b/commands/bug/select/select_test.go
index 702700f4..5533ac2b 100644
--- a/commands/bug/select/select_test.go
+++ b/commands/bug/select/select_test.go
@@ -13,8 +13,11 @@ import (
func TestSelect(t *testing.T) {
repo := repository.CreateGoGitTestRepo(t, false)
- repoCache, err := cache.NewRepoCache(repo)
+ repoCache, events, err := cache.NewRepoCache(repo)
require.NoError(t, err)
+ for event := range events {
+ require.NoError(t, event.Err)
+ }
_, _, err = ResolveBug(repoCache, []string{})
require.Equal(t, ErrNoValidId, err)
@@ -28,18 +31,18 @@ func TestSelect(t *testing.T) {
// generate a bunch of bugs
- rene, err := repoCache.NewIdentity("René Descartes", "rene@descartes.fr")
+ rene, err := repoCache.Identities().New("René Descartes", "rene@descartes.fr")
require.NoError(t, err)
for i := 0; i < 10; i++ {
- _, _, err := repoCache.NewBugRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
+ _, _, err := repoCache.Bugs().NewRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
require.NoError(t, err)
}
// and two more for testing
- b1, _, err := repoCache.NewBugRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
+ b1, _, err := repoCache.Bugs().NewRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
require.NoError(t, err)
- b2, _, err := repoCache.NewBugRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
+ b2, _, err := repoCache.Bugs().NewRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
require.NoError(t, err)
err = Select(repoCache, b1.Id())
diff --git a/commands/bug/testenv/testenv.go b/commands/bug/testenv/testenv.go
index 10f20950..acd1f389 100644
--- a/commands/bug/testenv/testenv.go
+++ b/commands/bug/testenv/testenv.go
@@ -19,7 +19,7 @@ func NewTestEnvAndUser(t *testing.T) (*execenv.Env, entity.Id) {
testEnv := execenv.NewTestEnv(t)
- i, err := testEnv.Backend.NewIdentity(testUserName, testUserEmail)
+ i, err := testEnv.Backend.Identities().New(testUserName, testUserEmail)
require.NoError(t, err)
err = testEnv.Backend.SetUserIdentity(i)
@@ -38,7 +38,7 @@ func NewTestEnvAndBug(t *testing.T) (*execenv.Env, entity.Id) {
testEnv, _ := NewTestEnvAndUser(t)
- b, _, err := testEnv.Backend.NewBug(testBugTitle, testBugMessage)
+ b, _, err := testEnv.Backend.Bugs().New(testBugTitle, testBugMessage)
require.NoError(t, err)
return testEnv, b.Id()
@@ -53,7 +53,7 @@ func NewTestEnvAndBugWithComment(t *testing.T) (*execenv.Env, entity.Id, entity.
env, bugID := NewTestEnvAndBug(t)
- b, err := env.Backend.ResolveBug(bugID)
+ b, err := env.Backend.Bugs().Resolve(bugID)
require.NoError(t, err)
commentId, _, err := b.AddComment(testCommentMessage)
diff --git a/commands/cmdjson/json_common.go b/commands/cmdjson/json_common.go
index 60e6e751..34077915 100644
--- a/commands/cmdjson/json_common.go
+++ b/commands/cmdjson/json_common.go
@@ -26,8 +26,8 @@ func NewIdentity(i identity.Interface) Identity {
func NewIdentityFromExcerpt(excerpt *cache.IdentityExcerpt) Identity {
return Identity{
- Id: excerpt.Id.String(),
- HumanId: excerpt.Id.Human(),
+ Id: excerpt.Id().String(),
+ HumanId: excerpt.Id().Human(),
Name: excerpt.Name,
Login: excerpt.Login,
}
diff --git a/commands/completion/helper_completion.go b/commands/completion/helper_completion.go
index 27fbd615..691f0895 100644
--- a/commands/completion/helper_completion.go
+++ b/commands/completion/helper_completion.go
@@ -88,11 +88,11 @@ func Bug(env *execenv.Env) ValidArgsFunction {
}
func bugWithBackend(backend *cache.RepoCache, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
- allIds := backend.AllBugsIds()
+ allIds := backend.Bugs().AllIds()
bugExcerpt := make([]*cache.BugExcerpt, len(allIds))
for i, id := range allIds {
var err error
- bugExcerpt[i], err = backend.ResolveBugExcerpt(id)
+ bugExcerpt[i], err = backend.Bugs().ResolveExcerpt(id)
if err != nil {
return handleError(err)
}
@@ -138,7 +138,7 @@ func BugAndLabels(env *execenv.Env, addOrRemove bool) ValidArgsFunction {
seenLabels[label] = true
}
- allLabels := env.Backend.ValidLabels()
+ allLabels := env.Backend.Bugs().ValidLabels()
labels = make([]bug.Label, 0, len(allLabels))
for _, label := range allLabels {
if !seenLabels[label] {
@@ -200,7 +200,7 @@ func Label(env *execenv.Env) ValidArgsFunction {
_ = env.Backend.Close()
}()
- labels := env.Backend.ValidLabels()
+ labels := env.Backend.Bugs().ValidLabels()
completions = make([]string, len(labels))
for i, label := range labels {
if strings.Contains(label.String(), " ") {
@@ -243,10 +243,10 @@ func Ls(env *execenv.Env) ValidArgsFunction {
if !strings.HasPrefix(toComplete, key) {
continue
}
- ids := env.Backend.AllIdentityIds()
+ ids := env.Backend.Identities().AllIds()
completions = make([]string, len(ids))
for i, id := range ids {
- user, err := env.Backend.ResolveIdentityExcerpt(id)
+ user, err := env.Backend.Identities().ResolveExcerpt(id)
if err != nil {
return handleError(err)
}
@@ -266,7 +266,7 @@ func Ls(env *execenv.Env) ValidArgsFunction {
if !strings.HasPrefix(toComplete, key) {
continue
}
- labels := env.Backend.ValidLabels()
+ labels := env.Backend.Bugs().ValidLabels()
completions = make([]string, len(labels))
for i, label := range labels {
if strings.Contains(label.String(), " ") {
@@ -300,14 +300,14 @@ func User(env *execenv.Env) ValidArgsFunction {
_ = env.Backend.Close()
}()
- ids := env.Backend.AllIdentityIds()
+ ids := env.Backend.Identities().AllIds()
completions = make([]string, len(ids))
for i, id := range ids {
- user, err := env.Backend.ResolveIdentityExcerpt(id)
+ user, err := env.Backend.Identities().ResolveExcerpt(id)
if err != nil {
return handleError(err)
}
- completions[i] = user.Id.Human() + "\t" + user.DisplayName()
+ completions[i] = user.Id().Human() + "\t" + user.DisplayName()
}
return completions, cobra.ShellCompDirectiveNoFileComp
}
@@ -322,10 +322,10 @@ func UserForQuery(env *execenv.Env) ValidArgsFunction {
_ = env.Backend.Close()
}()
- ids := env.Backend.AllIdentityIds()
+ ids := env.Backend.Identities().AllIds()
completions = make([]string, len(ids))
for i, id := range ids {
- user, err := env.Backend.ResolveIdentityExcerpt(id)
+ user, err := env.Backend.Identities().ResolveExcerpt(id)
if err != nil {
return handleError(err)
}
diff --git a/commands/execenv/env_testing.go b/commands/execenv/env_testing.go
index 7d9fbd60..ddba735f 100644
--- a/commands/execenv/env_testing.go
+++ b/commands/execenv/env_testing.go
@@ -5,9 +5,10 @@ import (
"fmt"
"testing"
+ "github.com/stretchr/testify/require"
+
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/repository"
- "github.com/stretchr/testify/require"
)
type TestOut struct {
@@ -33,8 +34,12 @@ func NewTestEnv(t *testing.T) *Env {
buf := new(bytes.Buffer)
- backend, err := cache.NewRepoCache(repo)
+ backend, events, err := cache.NewRepoCache(repo)
require.NoError(t, err)
+ for event := range events {
+ require.NoError(t, event.Err)
+ }
+
t.Cleanup(func() {
backend.Close()
})
diff --git a/commands/label.go b/commands/label.go
index 70090d26..08b9e31f 100644
--- a/commands/label.go
+++ b/commands/label.go
@@ -25,7 +25,7 @@ Note: in the future, a proper label policy could be implemented where valid labe
}
func runLabel(env *execenv.Env) error {
- labels := env.Backend.ValidLabels()
+ labels := env.Backend.Bugs().ValidLabels()
for _, l := range labels {
env.Out.Println(l)
diff --git a/commands/user/user.go b/commands/user/user.go
index 191fb828..9a1e477c 100644
--- a/commands/user/user.go
+++ b/commands/user/user.go
@@ -46,10 +46,10 @@ func NewUserCommand() *cobra.Command {
}
func runUser(env *execenv.Env, opts userOptions) error {
- ids := env.Backend.AllIdentityIds()
+ ids := env.Backend.Identities().AllIds()
var users []*cache.IdentityExcerpt
for _, id := range ids {
- user, err := env.Backend.ResolveIdentityExcerpt(id)
+ user, err := env.Backend.Identities().ResolveExcerpt(id)
if err != nil {
return err
}
@@ -69,7 +69,7 @@ func runUser(env *execenv.Env, opts userOptions) error {
func userDefaultFormatter(env *execenv.Env, users []*cache.IdentityExcerpt) error {
for _, user := range users {
env.Out.Printf("%s %s\n",
- colors.Cyan(user.Id.Human()),
+ colors.Cyan(user.Id().Human()),
user.DisplayName(),
)
}
diff --git a/commands/user/user_adopt.go b/commands/user/user_adopt.go
index f5944053..30fdb442 100644
--- a/commands/user/user_adopt.go
+++ b/commands/user/user_adopt.go
@@ -27,7 +27,7 @@ func newUserAdoptCommand() *cobra.Command {
func runUserAdopt(env *execenv.Env, args []string) error {
prefix := args[0]
- i, err := env.Backend.ResolveIdentityPrefix(prefix)
+ i, err := env.Backend.Identities().ResolvePrefix(prefix)
if err != nil {
return err
}
diff --git a/commands/user/user_new.go b/commands/user/user_new.go
index d7224512..7b287492 100644
--- a/commands/user/user_new.go
+++ b/commands/user/user_new.go
@@ -69,7 +69,7 @@ func runUserNew(env *execenv.Env, opts userNewOptions) error {
}
}
- id, err := env.Backend.NewIdentityRaw(opts.name, opts.email, "", opts.avatarURL, nil, nil)
+ id, err := env.Backend.Identities().NewRaw(opts.name, opts.email, "", opts.avatarURL, nil, nil)
if err != nil {
return err
}
diff --git a/commands/user/user_show.go b/commands/user/user_show.go
index 36c09e8e..225d0ef4 100644
--- a/commands/user/user_show.go
+++ b/commands/user/user_show.go
@@ -49,7 +49,7 @@ func runUserShow(env *execenv.Env, opts userShowOptions, args []string) error {
var id *cache.IdentityCache
var err error
if len(args) == 1 {
- id, err = env.Backend.ResolveIdentityPrefix(args[0])
+ id, err = env.Backend.Identities().ResolvePrefix(args[0])
} else {
id, err = env.Backend.GetUserIdentity()
}
diff --git a/commands/webui.go b/commands/webui.go
index 5fe66aa7..e1e0fc2b 100644
--- a/commands/webui.go
+++ b/commands/webui.go
@@ -105,7 +105,7 @@ func runWebUI(env *execenv.Env, opts webUIOptions) error {
}
mrc := cache.NewMultiRepoCache()
- _, err := mrc.RegisterDefaultRepository(env.Repo)
+ _, _, err := mrc.RegisterDefaultRepository(env.Repo)
if err != nil {
return err
}
diff --git a/entities/bug/bug.go b/entities/bug/bug.go
index 4c4a9a74..271e7dbd 100644
--- a/entities/bug/bug.go
+++ b/entities/bug/bug.go
@@ -27,15 +27,6 @@ var def = dag.Definition{
FormatVersion: formatVersion,
}
-var Actions = dag.Actions[*Bug]{
- Wrap: wrapper,
- New: NewBug,
- Read: Read,
- ReadWithResolver: ReadWithResolver,
- ReadAll: ReadAll,
- ListLocalIds: ListLocalIds,
-}
-
var ClockLoader = dag.ClockLoader(def)
type Interface interface {
@@ -75,12 +66,12 @@ func ReadWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers, i
}
// ReadAll read and parse all local bugs
-func ReadAll(repo repository.ClockedRepo) <-chan dag.StreamedEntity[*Bug] {
+func ReadAll(repo repository.ClockedRepo) <-chan entity.StreamedEntity[*Bug] {
return dag.ReadAll(def, wrapper, repo, simpleResolvers(repo))
}
// ReadAllWithResolver read and parse all local bugs
-func ReadAllWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan dag.StreamedEntity[*Bug] {
+func ReadAllWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[*Bug] {
return dag.ReadAll(def, wrapper, repo, resolvers)
}
diff --git a/entities/bug/resolver.go b/entities/bug/resolver.go
index e7beb0e4..b0a05917 100644
--- a/entities/bug/resolver.go
+++ b/entities/bug/resolver.go
@@ -16,6 +16,6 @@ func NewSimpleResolver(repo repository.ClockedRepo) *SimpleResolver {
return &SimpleResolver{repo: repo}
}
-func (r *SimpleResolver) Resolve(id entity.Id) (entity.Interface, error) {
+func (r *SimpleResolver) Resolve(id entity.Id) (entity.Resolved, error) {
return Read(r.repo, id)
}
diff --git a/entities/identity/common.go b/entities/identity/common.go
index ba35792c..88e30e33 100644
--- a/entities/identity/common.go
+++ b/entities/identity/common.go
@@ -3,14 +3,8 @@ package identity
import (
"encoding/json"
"fmt"
-
- "github.com/MichaelMure/git-bug/entity"
)
-func NewErrMultipleMatch(matching []entity.Id) *entity.ErrMultipleMatch {
- return entity.NewErrMultipleMatch("identity", matching)
-}
-
// Custom unmarshaling function to allow package user to delegate
// the decoding of an Identity and distinguish between an Identity
// and a Bare.
diff --git a/entities/identity/identity.go b/entities/identity/identity.go
index 9bc53aed..572d2c14 100644
--- a/entities/identity/identity.go
+++ b/entities/identity/identity.go
@@ -25,10 +25,6 @@ var ErrNoIdentitySet = errors.New("No identity is set.\n" +
"\"git bug user new\" or adopted with \"git bug user adopt\"")
var ErrMultipleIdentitiesSet = errors.New("multiple user identities set")
-func NewErrMultipleMatchIdentity(matching []entity.Id) *entity.ErrMultipleMatch {
- return entity.NewErrMultipleMatch("identity", matching)
-}
-
var _ Interface = &Identity{}
var _ entity.Interface = &Identity{}
@@ -174,7 +170,7 @@ func RemoveIdentity(repo repository.ClockedRepo, id entity.Id) error {
return err
}
if len(refs) > 1 {
- return NewErrMultipleMatchIdentity(entity.RefsToIds(refs))
+ return entity.NewErrMultipleMatch("identity", entity.RefsToIds(refs))
}
if len(refs) == 1 {
// we have the identity locally
@@ -193,7 +189,7 @@ func RemoveIdentity(repo repository.ClockedRepo, id entity.Id) error {
return err
}
if len(remoteRefs) > 1 {
- return NewErrMultipleMatchIdentity(entity.RefsToIds(refs))
+ return entity.NewErrMultipleMatch("identity", entity.RefsToIds(refs))
}
if len(remoteRefs) == 1 {
// found the identity in a remote
@@ -215,44 +211,39 @@ func RemoveIdentity(repo repository.ClockedRepo, id entity.Id) error {
return nil
}
-type StreamedIdentity struct {
- Identity *Identity
- Err error
-}
-
// ReadAllLocal read and parse all local Identity
-func ReadAllLocal(repo repository.ClockedRepo) <-chan StreamedIdentity {
+func ReadAllLocal(repo repository.ClockedRepo) <-chan entity.StreamedEntity[*Identity] {
return readAll(repo, identityRefPattern)
}
// ReadAllRemote read and parse all remote Identity for a given remote
-func ReadAllRemote(repo repository.ClockedRepo, remote string) <-chan StreamedIdentity {
+func ReadAllRemote(repo repository.ClockedRepo, remote string) <-chan entity.StreamedEntity[*Identity] {
refPrefix := fmt.Sprintf(identityRemoteRefPattern, remote)
return readAll(repo, refPrefix)
}
// readAll read and parse all available bug with a given ref prefix
-func readAll(repo repository.ClockedRepo, refPrefix string) <-chan StreamedIdentity {
- out := make(chan StreamedIdentity)
+func readAll(repo repository.ClockedRepo, refPrefix string) <-chan entity.StreamedEntity[*Identity] {
+ out := make(chan entity.StreamedEntity[*Identity])
go func() {
defer close(out)
refs, err := repo.ListRefs(refPrefix)
if err != nil {
- out <- StreamedIdentity{Err: err}
+ out <- entity.StreamedEntity[*Identity]{Err: err}
return
}
for _, ref := range refs {
- b, err := read(repo, ref)
+ i, err := read(repo, ref)
if err != nil {
- out <- StreamedIdentity{Err: err}
+ out <- entity.StreamedEntity[*Identity]{Err: err}
return
}
- out <- StreamedIdentity{Identity: b}
+ out <- entity.StreamedEntity[*Identity]{Entity: i}
}
}()
@@ -308,7 +299,7 @@ func (i *Identity) Mutate(repo repository.RepoClock, f func(orig *Mutator)) erro
return nil
}
-// Write the identity into the Repository. In particular, this ensure that
+// Commit write the identity into the Repository. In particular, this ensures that
// the Id is properly set.
func (i *Identity) Commit(repo repository.ClockedRepo) error {
if !i.NeedCommit() {
diff --git a/entities/identity/identity_actions_test.go b/entities/identity/identity_actions_test.go
index 351fb7a4..e9626cb9 100644
--- a/entities/identity/identity_actions_test.go
+++ b/entities/identity/identity_actions_test.go
@@ -5,6 +5,7 @@ import (
"github.com/stretchr/testify/require"
+ "github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/repository"
)
@@ -145,13 +146,13 @@ func TestIdentityPushPull(t *testing.T) {
}
}
-func allIdentities(t testing.TB, identities <-chan StreamedIdentity) []*Identity {
+func allIdentities(t testing.TB, identities <-chan entity.StreamedEntity[*Identity]) []*Identity {
var result []*Identity
for streamed := range identities {
if streamed.Err != nil {
t.Fatal(streamed.Err)
}
- result = append(result, streamed.Identity)
+ result = append(result, streamed.Entity)
}
return result
}
diff --git a/entities/identity/resolver.go b/entities/identity/resolver.go
index 5468a8f8..a4b676f3 100644
--- a/entities/identity/resolver.go
+++ b/entities/identity/resolver.go
@@ -16,19 +16,6 @@ func NewSimpleResolver(repo repository.Repo) *SimpleResolver {
return &SimpleResolver{repo: repo}
}
-func (r *SimpleResolver) Resolve(id entity.Id) (entity.Interface, error) {
+func (r *SimpleResolver) Resolve(id entity.Id) (entity.Resolved, error) {
return ReadLocal(r.repo, id)
}
-
-var _ entity.Resolver = &StubResolver{}
-
-// StubResolver is a Resolver that doesn't load anything, only returning IdentityStub instances
-type StubResolver struct{}
-
-func NewStubResolver() *StubResolver {
- return &StubResolver{}
-}
-
-func (s *StubResolver) Resolve(id entity.Id) (entity.Interface, error) {
- return &IdentityStub{id: id}, nil
-}
diff --git a/entity/dag/entity.go b/entity/dag/entity.go
index 03b97aa0..2028e1b4 100644
--- a/entity/dag/entity.go
+++ b/entity/dag/entity.go
@@ -33,19 +33,6 @@ type Definition struct {
FormatVersion uint
}
-type Actions[EntityT entity.Interface] struct {
- Wrap func(e *Entity) EntityT
- New func() EntityT
- Read func(repo repository.ClockedRepo, id entity.Id) (EntityT, error)
- ReadWithResolver func(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (EntityT, error)
- ReadAll func(repo repository.ClockedRepo) <-chan StreamedEntity[EntityT]
- ListLocalIds func(repo repository.Repo) ([]entity.Id, error)
- Fetch func(repo repository.Repo, remote string) (string, error)
- Push func(repo repository.Repo, remote string) (string, error)
- Pull func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) error
- MergeAll func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) <-chan entity.MergeResult
-}
-
// Entity is a data structure stored in a chain of git objects, supporting actions like Push, Pull and Merge.
type Entity struct {
// A Lamport clock is a logical clock that allow to order event
@@ -96,6 +83,9 @@ func readRemote[EntityT entity.Interface](def Definition, wrapper func(e *Entity
// read fetch from git and decode an Entity at an arbitrary git reference.
func read[EntityT entity.Interface](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, ref string) (EntityT, error) {
rootHash, err := repo.ResolveRef(ref)
+ if err == repository.ErrNotFound {
+ return *new(EntityT), entity.NewErrNotFound(def.Typename)
+ }
if err != nil {
return *new(EntityT), err
}
@@ -260,6 +250,9 @@ func read[EntityT entity.Interface](def Definition, wrapper func(e *Entity) Enti
// operation blobs can be implemented instead.
func readClockNoCheck(def Definition, repo repository.ClockedRepo, ref string) error {
rootHash, err := repo.ResolveRef(ref)
+ if err == repository.ErrNotFound {
+ return entity.NewErrNotFound(def.Typename)
+ }
if err != nil {
return err
}
@@ -306,14 +299,9 @@ func readClockNoCheck(def Definition, repo repository.ClockedRepo, ref string) e
return nil
}
-type StreamedEntity[EntityT entity.Interface] struct {
- Entity EntityT
- Err error
-}
-
// ReadAll read and parse all local Entity
-func ReadAll[EntityT entity.Interface](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan StreamedEntity[EntityT] {
- out := make(chan StreamedEntity[EntityT])
+func ReadAll[EntityT entity.Interface](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[EntityT] {
+ out := make(chan entity.StreamedEntity[EntityT])
go func() {
defer close(out)
@@ -322,7 +310,7 @@ func ReadAll[EntityT entity.Interface](def Definition, wrapper func(e *Entity) E
refs, err := repo.ListRefs(refPrefix)
if err != nil {
- out <- StreamedEntity[EntityT]{Err: err}
+ out <- entity.StreamedEntity[EntityT]{Err: err}
return
}
@@ -330,11 +318,11 @@ func ReadAll[EntityT entity.Interface](def Definition, wrapper func(e *Entity) E
e, err := read[EntityT](def, wrapper, repo, resolvers, ref)
if err != nil {
- out <- StreamedEntity[EntityT]{Err: err}
+ out <- entity.StreamedEntity[EntityT]{Err: err}
return
}
- out <- StreamedEntity[EntityT]{Entity: e}
+ out <- entity.StreamedEntity[EntityT]{Entity: e}
}
}()
diff --git a/entity/dag/entity_actions_test.go b/entity/dag/entity_actions_test.go
index d93059de..fd219644 100644
--- a/entity/dag/entity_actions_test.go
+++ b/entity/dag/entity_actions_test.go
@@ -11,7 +11,7 @@ import (
"github.com/MichaelMure/git-bug/repository"
)
-func allEntities(t testing.TB, bugs <-chan StreamedEntity[*Foo]) []*Foo {
+func allEntities(t testing.TB, bugs <-chan entity.StreamedEntity[*Foo]) []*Foo {
t.Helper()
var result []*Foo
diff --git a/entity/err.go b/entity/err.go
index 9d7c266e..4453d36e 100644
--- a/entity/err.go
+++ b/entity/err.go
@@ -5,6 +5,8 @@ import (
"strings"
)
+// ErrNotFound is to be returned when an entity, item, element is
+// not found.
type ErrNotFound struct {
typename string
}
@@ -22,13 +24,15 @@ func IsErrNotFound(err error) bool {
return ok
}
+// ErrMultipleMatch is to be returned when more than one entity, item, element
+// is found, where only one was expected.
type ErrMultipleMatch struct {
- entityType string
- Matching []Id
+ typename string
+ Matching []Id
}
-func NewErrMultipleMatch(entityType string, matching []Id) *ErrMultipleMatch {
- return &ErrMultipleMatch{entityType: entityType, Matching: matching}
+func NewErrMultipleMatch(typename string, matching []Id) *ErrMultipleMatch {
+ return &ErrMultipleMatch{typename: typename, Matching: matching}
}
func (e ErrMultipleMatch) Error() string {
@@ -39,7 +43,7 @@ func (e ErrMultipleMatch) Error() string {
}
return fmt.Sprintf("Multiple matching %s found:\n%s",
- e.entityType,
+ e.typename,
strings.Join(matching, "\n"))
}
@@ -48,6 +52,8 @@ func IsErrMultipleMatch(err error) bool {
return ok
}
+// ErrInvalidFormat is to be returned when reading on-disk data with an unexpected
+// format or version.
type ErrInvalidFormat struct {
version uint
expected uint
diff --git a/entity/interface.go b/entity/interface.go
index 572ba602..3035ac88 100644
--- a/entity/interface.go
+++ b/entity/interface.go
@@ -12,15 +12,3 @@ type Interface interface {
// Validate check if the Entity data is valid
Validate() error
}
-
-// type Commitable interface {
-// Interface
-// NeedCommit() bool
-// CommitAsNeeded(repo repository.ClockedRepo) error
-// Commit(repo repository.ClockedRepo) error
-// }
-
-//
-// type Operation interface {
-//
-// }
diff --git a/entity/resolver.go b/entity/resolver.go
index 9cacbf00..bd16b901 100644
--- a/entity/resolver.go
+++ b/entity/resolver.go
@@ -5,16 +5,23 @@ import (
"sync"
)
+// Resolved is a minimal interface on which Resolver operates on.
+// Notably, this operates on Entity and Excerpt in the cache.
+type Resolved interface {
+ // Id returns the object identifier.
+ Id() Id
+}
+
// Resolver is an interface to find an Entity from its Id
type Resolver interface {
- Resolve(id Id) (Interface, error)
+ Resolve(id Id) (Resolved, error)
}
// Resolvers is a collection of Resolver, for different type of Entity
-type Resolvers map[Interface]Resolver
+type Resolvers map[Resolved]Resolver
// Resolve use the appropriate sub-resolver for the given type and find the Entity matching the Id.
-func Resolve[T Interface](rs Resolvers, id Id) (T, error) {
+func Resolve[T Resolved](rs Resolvers, id Id) (T, error) {
var zero T
for t, resolver := range rs {
switch t.(type) {
@@ -35,17 +42,17 @@ var _ Resolver = &CachedResolver{}
type CachedResolver struct {
resolver Resolver
mu sync.RWMutex
- entities map[Id]Interface
+ entities map[Id]Resolved
}
func NewCachedResolver(resolver Resolver) *CachedResolver {
return &CachedResolver{
resolver: resolver,
- entities: make(map[Id]Interface),
+ entities: make(map[Id]Resolved),
}
}
-func (c *CachedResolver) Resolve(id Id) (Interface, error) {
+func (c *CachedResolver) Resolve(id Id) (Resolved, error) {
c.mu.RLock()
if i, ok := c.entities[id]; ok {
c.mu.RUnlock()
@@ -64,18 +71,18 @@ func (c *CachedResolver) Resolve(id Id) (Interface, error) {
return i, nil
}
-var _ Resolver = ResolverFunc[Interface](nil)
+var _ Resolver = ResolverFunc[Resolved](nil)
// ResolverFunc is a helper to morph a function resolver into a Resolver
-type ResolverFunc[T Interface] func(id Id) (T, error)
+type ResolverFunc[EntityT Resolved] func(id Id) (EntityT, error)
-func (fn ResolverFunc[T]) Resolve(id Id) (Interface, error) {
+func (fn ResolverFunc[EntityT]) Resolve(id Id) (Resolved, error) {
return fn(id)
}
// MakeResolver create a resolver able to return the given entities.
-func MakeResolver(entities ...Interface) Resolver {
- return ResolverFunc[Interface](func(id Id) (Interface, error) {
+func MakeResolver(entities ...Resolved) Resolver {
+ return ResolverFunc[Resolved](func(id Id) (Resolved, error) {
for _, entity := range entities {
if entity.Id() == id {
return entity, nil
diff --git a/entity/streamed.go b/entity/streamed.go
new file mode 100644
index 00000000..789224a3
--- /dev/null
+++ b/entity/streamed.go
@@ -0,0 +1,6 @@
+package entity
+
+type StreamedEntity[EntityT Interface] struct {
+ Entity EntityT
+ Err error
+}
diff --git a/termui/bug_table.go b/termui/bug_table.go
index f3f8b2f3..9db13ada 100644
--- a/termui/bug_table.go
+++ b/termui/bug_table.go
@@ -239,7 +239,7 @@ func (bt *bugTable) disable(g *gocui.Gui) error {
func (bt *bugTable) paginate(max int) error {
var err error
- bt.allIds, err = bt.repo.QueryBugs(bt.query)
+ bt.allIds, err = bt.repo.Bugs().Query(bt.query)
if err != nil {
return err
}
@@ -265,7 +265,7 @@ func (bt *bugTable) doPaginate(max int) error {
bt.excerpts = make([]*cache.BugExcerpt, len(ids))
for i, id := range ids {
- excerpt, err := bt.repo.ResolveBugExcerpt(id)
+ excerpt, err := bt.repo.Bugs().ResolveExcerpt(id)
if err != nil {
return err
}
@@ -319,12 +319,12 @@ func (bt *bugTable) render(v *gocui.View, maxX int) {
labelsTxt.WriteString(lc256.Unescape())
}
- author, err := bt.repo.ResolveIdentityExcerpt(excerpt.AuthorId)
+ author, err := bt.repo.Identities().ResolveExcerpt(excerpt.AuthorId)
if err != nil {
panic(err)
}
- id := text.LeftPadMaxLine(excerpt.Id.Human(), columnWidths["id"], 0)
+ id := text.LeftPadMaxLine(excerpt.Id().Human(), columnWidths["id"], 0)
status := text.LeftPadMaxLine(excerpt.Status.String(), columnWidths["status"], 0)
labels := text.TruncateMax(labelsTxt.String(), minInt(columnWidths["title"]-2, 10))
title := text.LeftPadMaxLine(strings.TrimSpace(excerpt.Title), columnWidths["title"]-text.Len(labels), 0)
@@ -451,8 +451,8 @@ func (bt *bugTable) openBug(g *gocui.Gui, v *gocui.View) error {
// There are no open bugs, just do nothing
return nil
}
- id := bt.excerpts[bt.selectCursor].Id
- b, err := bt.repo.ResolveBug(id)
+ id := bt.excerpts[bt.selectCursor].Id()
+ b, err := bt.repo.Bugs().Resolve(id)
if err != nil {
return err
}
diff --git a/termui/label_select.go b/termui/label_select.go
index 2282583d..6721165e 100644
--- a/termui/label_select.go
+++ b/termui/label_select.go
@@ -37,7 +37,7 @@ func newLabelSelect() *labelSelect {
func (ls *labelSelect) SetBug(cache *cache.RepoCache, bug *cache.BugCache) {
ls.cache = cache
ls.bug = bug
- ls.labels = cache.ValidLabels()
+ ls.labels = cache.Bugs().ValidLabels()
// Find which labels are currently applied to the bug
bugLabels := bug.Snapshot().Labels
diff --git a/termui/termui.go b/termui/termui.go
index 4dd6e27d..79577ba9 100644
--- a/termui/termui.go
+++ b/termui/termui.go
@@ -200,7 +200,7 @@ func newBugWithEditor(repo *cache.RepoCache) error {
return errTerminateMainloop
} else {
- b, _, err = repo.NewBug(
+ b, _, err = repo.Bugs().New(
text.CleanupOneLine(title),
text.Cleanup(message),
)