aboutsummaryrefslogtreecommitdiffstats
path: root/cache/repo_cache_identity.go
diff options
context:
space:
mode:
Diffstat (limited to 'cache/repo_cache_identity.go')
-rw-r--r--cache/repo_cache_identity.go275
1 files changed, 275 insertions, 0 deletions
diff --git a/cache/repo_cache_identity.go b/cache/repo_cache_identity.go
new file mode 100644
index 00000000..8957d4aa
--- /dev/null
+++ b/cache/repo_cache_identity.go
@@ -0,0 +1,275 @@
+package cache
+
+import (
+ "bytes"
+ "encoding/gob"
+ "fmt"
+ "os"
+ "path"
+
+ "github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/repository"
+)
+
+const identityCacheFile = "identity-cache"
+
+func identityCacheFilePath(repo repository.Repo) string {
+ return path.Join(repo.GetPath(), "git-bug", identityCacheFile)
+}
+
+// identityUpdated is a callback to trigger when the excerpt of an identity
+// changed, that is each time an identity is updated
+func (c *RepoCache) identityUpdated(id entity.Id) error {
+ c.muIdentity.Lock()
+
+ i, ok := c.identities[id]
+ if !ok {
+ c.muIdentity.Unlock()
+ panic("missing identity in the cache")
+ }
+
+ c.identitiesExcerpts[id] = NewIdentityExcerpt(i.Identity)
+ c.muIdentity.Unlock()
+
+ // we only need to write the identity cache
+ return c.writeIdentityCache()
+}
+
+// load will try to read from the disk the identity cache file
+func (c *RepoCache) loadIdentityCache() error {
+ c.muIdentity.Lock()
+ defer c.muIdentity.Unlock()
+
+ f, err := os.Open(identityCacheFilePath(c.repo))
+ if err != nil {
+ return err
+ }
+
+ decoder := gob.NewDecoder(f)
+
+ aux := struct {
+ Version uint
+ Excerpts map[entity.Id]*IdentityExcerpt
+ }{}
+
+ err = decoder.Decode(&aux)
+ if err != nil {
+ return err
+ }
+
+ if aux.Version != formatVersion {
+ return fmt.Errorf("unknown cache format version %v", aux.Version)
+ }
+
+ c.identitiesExcerpts = aux.Excerpts
+ return nil
+}
+
+// write will serialize on disk the identity cache file
+func (c *RepoCache) writeIdentityCache() error {
+ c.muIdentity.RLock()
+ defer c.muIdentity.RUnlock()
+
+ var data bytes.Buffer
+
+ aux := struct {
+ Version uint
+ Excerpts map[entity.Id]*IdentityExcerpt
+ }{
+ Version: formatVersion,
+ Excerpts: c.identitiesExcerpts,
+ }
+
+ encoder := gob.NewEncoder(&data)
+
+ err := encoder.Encode(aux)
+ if err != nil {
+ return err
+ }
+
+ f, err := os.Create(identityCacheFilePath(c.repo))
+ if err != nil {
+ return err
+ }
+
+ _, err = f.Write(data.Bytes())
+ if err != nil {
+ return err
+ }
+
+ return f.Close()
+}
+
+// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id
+func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) {
+ c.muIdentity.RLock()
+ defer c.muIdentity.RUnlock()
+
+ e, ok := c.identitiesExcerpts[id]
+ if !ok {
+ return nil, identity.ErrIdentityNotExist
+ }
+
+ return e, nil
+}
+
+// ResolveIdentity retrieve an identity matching the exact given id
+func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) {
+ c.muIdentity.RLock()
+ cached, ok := c.identities[id]
+ c.muIdentity.RUnlock()
+ if ok {
+ return cached, nil
+ }
+
+ i, err := identity.ReadLocal(c.repo, id)
+ if err != nil {
+ return nil, err
+ }
+
+ cached = NewIdentityCache(c, i)
+
+ c.muIdentity.Lock()
+ c.identities[id] = cached
+ c.muIdentity.Unlock()
+
+ return cached, nil
+}
+
+// ResolveIdentityExcerptPrefix retrieve a IdentityExcerpt matching an id prefix.
+// It fails if multiple identities match.
+func (c *RepoCache) ResolveIdentityExcerptPrefix(prefix string) (*IdentityExcerpt, error) {
+ return c.ResolveIdentityExcerptMatcher(func(excerpt *IdentityExcerpt) bool {
+ return excerpt.Id.HasPrefix(prefix)
+ })
+}
+
+// ResolveIdentityPrefix retrieve an Identity matching an id prefix.
+// It fails if multiple identities match.
+func (c *RepoCache) ResolveIdentityPrefix(prefix string) (*IdentityCache, error) {
+ return c.ResolveIdentityMatcher(func(excerpt *IdentityExcerpt) bool {
+ return excerpt.Id.HasPrefix(prefix)
+ })
+}
+
+// ResolveIdentityImmutableMetadata retrieve an Identity that has the exact given metadata on
+// one of it's version. If multiple version have the same key, the first defined take precedence.
+func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) (*IdentityCache, error) {
+ return c.ResolveIdentityMatcher(func(excerpt *IdentityExcerpt) bool {
+ return excerpt.ImmutableMetadata[key] == value
+ })
+}
+
+func (c *RepoCache) ResolveIdentityExcerptMatcher(f func(*IdentityExcerpt) bool) (*IdentityExcerpt, error) {
+ id, err := c.resolveIdentityMatcher(f)
+ if err != nil {
+ return nil, err
+ }
+ return c.ResolveIdentityExcerpt(id)
+}
+
+func (c *RepoCache) ResolveIdentityMatcher(f func(*IdentityExcerpt) bool) (*IdentityCache, error) {
+ id, err := c.resolveIdentityMatcher(f)
+ if err != nil {
+ return nil, err
+ }
+ return c.ResolveIdentity(id)
+}
+
+func (c *RepoCache) resolveIdentityMatcher(f func(*IdentityExcerpt) bool) (entity.Id, error) {
+ c.muIdentity.RLock()
+ defer c.muIdentity.RUnlock()
+
+ // preallocate but empty
+ matching := make([]entity.Id, 0, 5)
+
+ for _, excerpt := range c.identitiesExcerpts {
+ if f(excerpt) {
+ matching = append(matching, excerpt.Id)
+ }
+ }
+
+ if len(matching) > 1 {
+ return entity.UnsetId, identity.NewErrMultipleMatch(matching)
+ }
+
+ if len(matching) == 0 {
+ return entity.UnsetId, identity.ErrIdentityNotExist
+ }
+
+ return matching[0], nil
+}
+
+// AllIdentityIds return all known identity ids
+func (c *RepoCache) AllIdentityIds() []entity.Id {
+ c.muIdentity.RLock()
+ defer c.muIdentity.RUnlock()
+
+ result := make([]entity.Id, len(c.identitiesExcerpts))
+
+ i := 0
+ for _, excerpt := range c.identitiesExcerpts {
+ result[i] = excerpt.Id
+ i++
+ }
+
+ return result
+}
+
+func (c *RepoCache) NewIdentityFromGitUser() (*IdentityCache, error) {
+ return c.NewIdentityFromGitUserRaw(nil)
+}
+
+func (c *RepoCache) NewIdentityFromGitUserRaw(metadata map[string]string) (*IdentityCache, error) {
+ i, err := identity.NewFromGitUser(c.repo)
+ 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 *RepoCache) NewIdentity(name string, email string) (*IdentityCache, error) {
+ return c.NewIdentityRaw(name, email, "", "", nil)
+}
+
+// NewIdentityFull create a new identity
+// The new identity is written in the repository (commit)
+func (c *RepoCache) NewIdentityFull(name string, email string, login string, avatarUrl string) (*IdentityCache, error) {
+ return c.NewIdentityRaw(name, email, login, avatarUrl, nil)
+}
+
+func (c *RepoCache) NewIdentityRaw(name string, email string, login string, avatarUrl string, metadata map[string]string) (*IdentityCache, error) {
+ i := identity.NewIdentityFull(name, email, login, avatarUrl)
+ return c.finishIdentity(i, metadata)
+}
+
+func (c *RepoCache) finishIdentity(i *identity.Identity, metadata map[string]string) (*IdentityCache, error) {
+ for key, value := range metadata {
+ i.SetMetadata(key, value)
+ }
+
+ err := i.Commit(c.repo)
+ if err != nil {
+ return nil, err
+ }
+
+ c.muIdentity.Lock()
+ if _, has := c.identities[i.Id()]; has {
+ return nil, fmt.Errorf("identity %s already exist in the cache", i.Id())
+ }
+
+ cached := NewIdentityCache(c, i)
+ c.identities[i.Id()] = cached
+ c.muIdentity.Unlock()
+
+ // force the write of the excerpt
+ err = c.identityUpdated(i.Id())
+ if err != nil {
+ return nil, err
+ }
+
+ return cached, nil
+}