1package cache
2
3import (
4 "fmt"
5
6 "github.com/MichaelMure/git-bug/entities/identity"
7 "github.com/MichaelMure/git-bug/entity"
8 "github.com/MichaelMure/git-bug/repository"
9)
10
11type RepoCacheIdentity struct {
12 *SubCache[*identity.Identity, *IdentityExcerpt, *IdentityCache]
13}
14
15func NewRepoCacheIdentity(repo repository.ClockedRepo,
16 resolvers func() entity.Resolvers,
17 getUserIdentity getUserIdentityFunc) *RepoCacheIdentity {
18
19 makeCached := func(i *identity.Identity, entityUpdated func(id entity.Id) error) *IdentityCache {
20 return NewIdentityCache(i, repo, entityUpdated)
21 }
22
23 makeIndex := func(i *IdentityCache) []string {
24 // no indexing
25 return nil
26 }
27
28 // TODO: this is terribly ugly, but we are currently stuck with the fact that identities are NOT using the fancy dag framework.
29 // This lead to various complication here and there to handle entities generically, and avoid large code duplication.
30 // TL;DR: something has to give, and this is the less ugly solution I found. This "normalize" identities as just another "dag framework"
31 // entity. Ideally identities would be converted to the dag framework, but right now that could lead to potential attack: if an old
32 // private key is leaked, it would be possible to craft a legal identity update that take over the most recent version. While this is
33 // meaningless in the case of a normal entity, it's really an issues for identities.
34
35 actions := Actions[*identity.Identity]{
36 ReadWithResolver: func(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (*identity.Identity, error) {
37 return identity.ReadLocal(repo, id)
38 },
39 ReadAllWithResolver: func(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[*identity.Identity] {
40 return identity.ReadAllLocal(repo)
41 },
42 Remove: identity.Remove,
43 RemoveAll: identity.RemoveAll,
44 MergeAll: func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor entity.Interface) <-chan entity.MergeResult {
45 return identity.MergeAll(repo, remote)
46 },
47 }
48
49 sc := NewSubCache[*identity.Identity, *IdentityExcerpt, *IdentityCache](
50 repo, resolvers, getUserIdentity,
51 makeCached, NewIdentityExcerpt, makeIndex, actions,
52 identity.Typename, identity.Namespace,
53 formatVersion, defaultMaxLoadedBugs,
54 )
55
56 return &RepoCacheIdentity{SubCache: sc}
57}
58
59// ResolveIdentityImmutableMetadata retrieve an Identity that has the exact given metadata on
60// one of its version. If multiple version have the same key, the first defined take precedence.
61func (c *RepoCacheIdentity) ResolveIdentityImmutableMetadata(key string, value string) (*IdentityCache, error) {
62 return c.ResolveMatcher(func(excerpt *IdentityExcerpt) bool {
63 return excerpt.ImmutableMetadata[key] == value
64 })
65}
66
67// New create a new identity
68// The new identity is written in the repository (commit)
69func (c *RepoCacheIdentity) New(name string, email string) (*IdentityCache, error) {
70 return c.NewRaw(name, email, "", "", nil, nil)
71}
72
73// NewFull create a new identity
74// The new identity is written in the repository (commit)
75func (c *RepoCacheIdentity) NewFull(name string, email string, login string, avatarUrl string, keys []*identity.Key) (*IdentityCache, error) {
76 return c.NewRaw(name, email, login, avatarUrl, keys, nil)
77}
78
79func (c *RepoCacheIdentity) NewRaw(name string, email string, login string, avatarUrl string, keys []*identity.Key, metadata map[string]string) (*IdentityCache, error) {
80 i, err := identity.NewIdentityFull(c.repo, name, email, login, avatarUrl, keys)
81 if err != nil {
82 return nil, err
83 }
84 return c.finishIdentity(i, metadata)
85}
86
87func (c *RepoCacheIdentity) NewFromGitUser() (*IdentityCache, error) {
88 return c.NewFromGitUserRaw(nil)
89}
90
91func (c *RepoCacheIdentity) NewFromGitUserRaw(metadata map[string]string) (*IdentityCache, error) {
92 i, err := identity.NewFromGitUser(c.repo)
93 if err != nil {
94 return nil, err
95 }
96 return c.finishIdentity(i, metadata)
97}
98
99func (c *RepoCacheIdentity) finishIdentity(i *identity.Identity, metadata map[string]string) (*IdentityCache, error) {
100 for key, value := range metadata {
101 i.SetMetadata(key, value)
102 }
103
104 err := i.Commit(c.repo)
105 if err != nil {
106 return nil, err
107 }
108
109 c.mu.Lock()
110 if _, has := c.cached[i.Id()]; has {
111 return nil, fmt.Errorf("identity %s already exist in the cache", i.Id())
112 }
113
114 cached := NewIdentityCache(i, c.repo, c.entityUpdated)
115 c.cached[i.Id()] = cached
116 c.mu.Unlock()
117
118 // force the write of the excerpt
119 err = c.entityUpdated(i.Id())
120 if err != nil {
121 return nil, err
122 }
123
124 return cached, nil
125}