repo_cache_identity.go

  1package cache
  2
  3import (
  4	"bytes"
  5	"encoding/gob"
  6	"fmt"
  7	"os"
  8	"path"
  9
 10	"github.com/MichaelMure/git-bug/entity"
 11	"github.com/MichaelMure/git-bug/identity"
 12	"github.com/MichaelMure/git-bug/repository"
 13)
 14
 15const identityCacheFile = "identity-cache"
 16
 17func identityCacheFilePath(repo repository.Repo) string {
 18	return path.Join(repo.GetPath(), "git-bug", identityCacheFile)
 19}
 20
 21// identityUpdated is a callback to trigger when the excerpt of an identity
 22// changed, that is each time an identity is updated
 23func (c *RepoCache) identityUpdated(id entity.Id) error {
 24	c.muIdentity.Lock()
 25
 26	i, ok := c.identities[id]
 27	if !ok {
 28		c.muIdentity.Unlock()
 29		panic("missing identity in the cache")
 30	}
 31
 32	c.identitiesExcerpts[id] = NewIdentityExcerpt(i.Identity)
 33	c.muIdentity.Unlock()
 34
 35	// we only need to write the identity cache
 36	return c.writeIdentityCache()
 37}
 38
 39// load will try to read from the disk the identity cache file
 40func (c *RepoCache) loadIdentityCache() error {
 41	c.muIdentity.Lock()
 42	defer c.muIdentity.Unlock()
 43
 44	f, err := os.Open(identityCacheFilePath(c.repo))
 45	if err != nil {
 46		return err
 47	}
 48
 49	decoder := gob.NewDecoder(f)
 50
 51	aux := struct {
 52		Version  uint
 53		Excerpts map[entity.Id]*IdentityExcerpt
 54	}{}
 55
 56	err = decoder.Decode(&aux)
 57	if err != nil {
 58		return err
 59	}
 60
 61	if aux.Version != formatVersion {
 62		return fmt.Errorf("unknown cache format version %v", aux.Version)
 63	}
 64
 65	c.identitiesExcerpts = aux.Excerpts
 66	return nil
 67}
 68
 69// write will serialize on disk the identity cache file
 70func (c *RepoCache) writeIdentityCache() error {
 71	c.muIdentity.RLock()
 72	defer c.muIdentity.RUnlock()
 73
 74	var data bytes.Buffer
 75
 76	aux := struct {
 77		Version  uint
 78		Excerpts map[entity.Id]*IdentityExcerpt
 79	}{
 80		Version:  formatVersion,
 81		Excerpts: c.identitiesExcerpts,
 82	}
 83
 84	encoder := gob.NewEncoder(&data)
 85
 86	err := encoder.Encode(aux)
 87	if err != nil {
 88		return err
 89	}
 90
 91	f, err := os.Create(identityCacheFilePath(c.repo))
 92	if err != nil {
 93		return err
 94	}
 95
 96	_, err = f.Write(data.Bytes())
 97	if err != nil {
 98		return err
 99	}
100
101	return f.Close()
102}
103
104// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id
105func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) {
106	c.muIdentity.RLock()
107	defer c.muIdentity.RUnlock()
108
109	e, ok := c.identitiesExcerpts[id]
110	if !ok {
111		return nil, identity.ErrIdentityNotExist
112	}
113
114	return e, nil
115}
116
117// ResolveIdentity retrieve an identity matching the exact given id
118func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) {
119	c.muIdentity.RLock()
120	cached, ok := c.identities[id]
121	c.muIdentity.RUnlock()
122	if ok {
123		return cached, nil
124	}
125
126	i, err := identity.ReadLocal(c.repo, id)
127	if err != nil {
128		return nil, err
129	}
130
131	cached = NewIdentityCache(c, i)
132
133	c.muIdentity.Lock()
134	c.identities[id] = cached
135	c.muIdentity.Unlock()
136
137	return cached, nil
138}
139
140// ResolveIdentityExcerptPrefix retrieve a IdentityExcerpt matching an id prefix.
141// It fails if multiple identities match.
142func (c *RepoCache) ResolveIdentityExcerptPrefix(prefix string) (*IdentityExcerpt, error) {
143	return c.ResolveIdentityExcerptMatcher(func(excerpt *IdentityExcerpt) bool {
144		return excerpt.Id.HasPrefix(prefix)
145	})
146}
147
148// ResolveIdentityPrefix retrieve an Identity matching an id prefix.
149// It fails if multiple identities match.
150func (c *RepoCache) ResolveIdentityPrefix(prefix string) (*IdentityCache, error) {
151	return c.ResolveIdentityMatcher(func(excerpt *IdentityExcerpt) bool {
152		return excerpt.Id.HasPrefix(prefix)
153	})
154}
155
156// ResolveIdentityImmutableMetadata retrieve an Identity that has the exact given metadata on
157// one of it's version. If multiple version have the same key, the first defined take precedence.
158func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) (*IdentityCache, error) {
159	return c.ResolveIdentityMatcher(func(excerpt *IdentityExcerpt) bool {
160		return excerpt.ImmutableMetadata[key] == value
161	})
162}
163
164func (c *RepoCache) ResolveIdentityExcerptMatcher(f func(*IdentityExcerpt) bool) (*IdentityExcerpt, error) {
165	id, err := c.resolveIdentityMatcher(f)
166	if err != nil {
167		return nil, err
168	}
169	return c.ResolveIdentityExcerpt(id)
170}
171
172func (c *RepoCache) ResolveIdentityMatcher(f func(*IdentityExcerpt) bool) (*IdentityCache, error) {
173	id, err := c.resolveIdentityMatcher(f)
174	if err != nil {
175		return nil, err
176	}
177	return c.ResolveIdentity(id)
178}
179
180func (c *RepoCache) resolveIdentityMatcher(f func(*IdentityExcerpt) bool) (entity.Id, error) {
181	c.muIdentity.RLock()
182	defer c.muIdentity.RUnlock()
183
184	// preallocate but empty
185	matching := make([]entity.Id, 0, 5)
186
187	for _, excerpt := range c.identitiesExcerpts {
188		if f(excerpt) {
189			matching = append(matching, excerpt.Id)
190		}
191	}
192
193	if len(matching) > 1 {
194		return entity.UnsetId, identity.NewErrMultipleMatch(matching)
195	}
196
197	if len(matching) == 0 {
198		return entity.UnsetId, identity.ErrIdentityNotExist
199	}
200
201	return matching[0], nil
202}
203
204// AllIdentityIds return all known identity ids
205func (c *RepoCache) AllIdentityIds() []entity.Id {
206	c.muIdentity.RLock()
207	defer c.muIdentity.RUnlock()
208
209	result := make([]entity.Id, len(c.identitiesExcerpts))
210
211	i := 0
212	for _, excerpt := range c.identitiesExcerpts {
213		result[i] = excerpt.Id
214		i++
215	}
216
217	return result
218}
219
220func (c *RepoCache) NewIdentityFromGitUser() (*IdentityCache, error) {
221	return c.NewIdentityFromGitUserRaw(nil)
222}
223
224func (c *RepoCache) NewIdentityFromGitUserRaw(metadata map[string]string) (*IdentityCache, error) {
225	i, err := identity.NewFromGitUser(c.repo)
226	if err != nil {
227		return nil, err
228	}
229	return c.finishIdentity(i, metadata)
230}
231
232// NewIdentity create a new identity
233// The new identity is written in the repository (commit)
234func (c *RepoCache) NewIdentity(name string, email string) (*IdentityCache, error) {
235	return c.NewIdentityRaw(name, email, "", "", nil)
236}
237
238// NewIdentityFull create a new identity
239// The new identity is written in the repository (commit)
240func (c *RepoCache) NewIdentityFull(name string, email string, login string, avatarUrl string) (*IdentityCache, error) {
241	return c.NewIdentityRaw(name, email, login, avatarUrl, nil)
242}
243
244func (c *RepoCache) NewIdentityRaw(name string, email string, login string, avatarUrl string, metadata map[string]string) (*IdentityCache, error) {
245	i := identity.NewIdentityFull(name, email, login, avatarUrl)
246	return c.finishIdentity(i, metadata)
247}
248
249func (c *RepoCache) finishIdentity(i *identity.Identity, metadata map[string]string) (*IdentityCache, error) {
250	for key, value := range metadata {
251		i.SetMetadata(key, value)
252	}
253
254	err := i.Commit(c.repo)
255	if err != nil {
256		return nil, err
257	}
258
259	c.muIdentity.Lock()
260	if _, has := c.identities[i.Id()]; has {
261		return nil, fmt.Errorf("identity %s already exist in the cache", i.Id())
262	}
263
264	cached := NewIdentityCache(c, i)
265	c.identities[i.Id()] = cached
266	c.muIdentity.Unlock()
267
268	// force the write of the excerpt
269	err = c.identityUpdated(i.Id())
270	if err != nil {
271		return nil, err
272	}
273
274	return cached, nil
275}