aboutsummaryrefslogtreecommitdiffstats
path: root/cache
diff options
context:
space:
mode:
Diffstat (limited to 'cache')
-rw-r--r--cache/bug_cache.go109
-rw-r--r--cache/bug_excerpt.go41
-rw-r--r--cache/filter.go49
-rw-r--r--cache/identity_cache.go43
-rw-r--r--cache/identity_excerpt.go70
-rw-r--r--cache/multi_repo_cache.go1
-rw-r--r--cache/repo_cache.go402
7 files changed, 608 insertions, 107 deletions
diff --git a/cache/bug_cache.go b/cache/bug_cache.go
index 52e9eafb..5fc76658 100644
--- a/cache/bug_cache.go
+++ b/cache/bug_cache.go
@@ -9,6 +9,10 @@ import (
"github.com/MichaelMure/git-bug/util/git"
)
+// BugCache is a wrapper around a Bug. It provide multiple functions:
+//
+// 1. Provide a higher level API to use than the raw API from Bug.
+// 2. Maintain an up to date Snapshot available.
type BugCache struct {
repoCache *RepoCache
bug *bug.WithSnapshot
@@ -53,8 +57,8 @@ func (e ErrMultipleMatchOp) Error() string {
return fmt.Sprintf("Multiple matching operation found:\n%s", strings.Join(casted, "\n"))
}
-// ResolveTargetWithMetadata will find an operation that has the matching metadata
-func (c *BugCache) ResolveTargetWithMetadata(key string, value string) (git.Hash, error) {
+// ResolveOperationWithMetadata will find an operation that has the matching metadata
+func (c *BugCache) ResolveOperationWithMetadata(key string, value string) (git.Hash, error) {
// preallocate but empty
matching := make([]git.Hash, 0, 5)
@@ -82,45 +86,45 @@ func (c *BugCache) ResolveTargetWithMetadata(key string, value string) (git.Hash
return matching[0], nil
}
-func (c *BugCache) AddComment(message string) error {
+func (c *BugCache) AddComment(message string) (*bug.AddCommentOperation, error) {
return c.AddCommentWithFiles(message, nil)
}
-func (c *BugCache) AddCommentWithFiles(message string, files []git.Hash) error {
- author, err := bug.GetUser(c.repoCache.repo)
+func (c *BugCache) AddCommentWithFiles(message string, files []git.Hash) (*bug.AddCommentOperation, error) {
+ author, err := c.repoCache.GetUserIdentity()
if err != nil {
- return err
+ return nil, err
}
return c.AddCommentRaw(author, time.Now().Unix(), message, files, nil)
}
-func (c *BugCache) AddCommentRaw(author bug.Person, unixTime int64, message string, files []git.Hash, metadata map[string]string) error {
- op, err := bug.AddCommentWithFiles(c.bug, author, unixTime, message, files)
+func (c *BugCache) AddCommentRaw(author *IdentityCache, unixTime int64, message string, files []git.Hash, metadata map[string]string) (*bug.AddCommentOperation, error) {
+ op, err := bug.AddCommentWithFiles(c.bug, author.Identity, unixTime, message, files)
if err != nil {
- return err
+ return nil, err
}
for key, value := range metadata {
op.SetMetadata(key, value)
}
- return c.notifyUpdated()
+ return op, c.notifyUpdated()
}
-func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelChangeResult, error) {
- author, err := bug.GetUser(c.repoCache.repo)
+func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
+ author, err := c.repoCache.GetUserIdentity()
if err != nil {
- return nil, err
+ return nil, nil, err
}
return c.ChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
}
-func (c *BugCache) ChangeLabelsRaw(author bug.Person, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, error) {
- changes, op, err := bug.ChangeLabels(c.bug, author, unixTime, added, removed)
+func (c *BugCache) ChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
+ changes, op, err := bug.ChangeLabels(c.bug, author.Identity, unixTime, added, removed)
if err != nil {
- return changes, err
+ return changes, nil, err
}
for key, value := range metadata {
@@ -129,107 +133,112 @@ func (c *BugCache) ChangeLabelsRaw(author bug.Person, unixTime int64, added []st
err = c.notifyUpdated()
if err != nil {
- return nil, err
+ return nil, nil, err
}
- return changes, nil
+ return changes, op, nil
}
-func (c *BugCache) Open() error {
- author, err := bug.GetUser(c.repoCache.repo)
+func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
+ author, err := c.repoCache.GetUserIdentity()
if err != nil {
- return err
+ return nil, err
}
return c.OpenRaw(author, time.Now().Unix(), nil)
}
-func (c *BugCache) OpenRaw(author bug.Person, unixTime int64, metadata map[string]string) error {
- op, err := bug.Open(c.bug, author, unixTime)
+func (c *BugCache) OpenRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
+ op, err := bug.Open(c.bug, author.Identity, unixTime)
if err != nil {
- return err
+ return nil, err
}
for key, value := range metadata {
op.SetMetadata(key, value)
}
- return c.notifyUpdated()
+ return op, c.notifyUpdated()
}
-func (c *BugCache) Close() error {
- author, err := bug.GetUser(c.repoCache.repo)
+func (c *BugCache) Close() (*bug.SetStatusOperation, error) {
+ author, err := c.repoCache.GetUserIdentity()
if err != nil {
- return err
+ return nil, err
}
return c.CloseRaw(author, time.Now().Unix(), nil)
}
-func (c *BugCache) CloseRaw(author bug.Person, unixTime int64, metadata map[string]string) error {
- op, err := bug.Close(c.bug, author, unixTime)
+func (c *BugCache) CloseRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
+ op, err := bug.Close(c.bug, author.Identity, unixTime)
if err != nil {
- return err
+ return nil, err
}
for key, value := range metadata {
op.SetMetadata(key, value)
}
- return c.notifyUpdated()
+ return op, c.notifyUpdated()
}
-func (c *BugCache) SetTitle(title string) error {
- author, err := bug.GetUser(c.repoCache.repo)
+func (c *BugCache) SetTitle(title string) (*bug.SetTitleOperation, error) {
+ author, err := c.repoCache.GetUserIdentity()
if err != nil {
- return err
+ return nil, err
}
return c.SetTitleRaw(author, time.Now().Unix(), title, nil)
}
-func (c *BugCache) SetTitleRaw(author bug.Person, unixTime int64, title string, metadata map[string]string) error {
- op, err := bug.SetTitle(c.bug, author, unixTime, title)
+func (c *BugCache) SetTitleRaw(author *IdentityCache, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
+ op, err := bug.SetTitle(c.bug, author.Identity, unixTime, title)
if err != nil {
- return err
+ return nil, err
}
for key, value := range metadata {
op.SetMetadata(key, value)
}
- return c.notifyUpdated()
+ return op, c.notifyUpdated()
}
-func (c *BugCache) EditComment(target git.Hash, message string) error {
- author, err := bug.GetUser(c.repoCache.repo)
+func (c *BugCache) EditComment(target git.Hash, message string) (*bug.EditCommentOperation, error) {
+ author, err := c.repoCache.GetUserIdentity()
if err != nil {
- return err
+ return nil, err
}
return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil)
}
-func (c *BugCache) EditCommentRaw(author bug.Person, unixTime int64, target git.Hash, message string, metadata map[string]string) error {
- op, err := bug.EditComment(c.bug, author, unixTime, target, message)
+func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target git.Hash, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
+ op, err := bug.EditComment(c.bug, author.Identity, unixTime, target, message)
if err != nil {
- return err
+ return nil, err
}
for key, value := range metadata {
op.SetMetadata(key, value)
}
- return c.notifyUpdated()
+ return op, c.notifyUpdated()
}
func (c *BugCache) Commit() error {
- return c.bug.Commit(c.repoCache.repo)
+ err := c.bug.Commit(c.repoCache.repo)
+ if err != nil {
+ return err
+ }
+ return c.notifyUpdated()
}
func (c *BugCache) CommitAsNeeded() error {
- if c.bug.HasPendingOp() {
- return c.bug.Commit(c.repoCache.repo)
+ err := c.bug.CommitAsNeeded(c.repoCache.repo)
+ if err != nil {
+ return err
}
- return nil
+ return c.notifyUpdated()
}
diff --git a/cache/bug_excerpt.go b/cache/bug_excerpt.go
index 7a11fa82..fd06e51b 100644
--- a/cache/bug_excerpt.go
+++ b/cache/bug_excerpt.go
@@ -4,9 +4,15 @@ import (
"encoding/gob"
"github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/lamport"
)
+// Package initialisation used to register the type for (de)serialization
+func init() {
+ gob.Register(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 {
@@ -18,29 +24,52 @@ type BugExcerpt struct {
EditUnixTime int64
Status bug.Status
- Author bug.Person
Labels []bug.Label
+ // If author is identity.Bare, LegacyAuthor is set
+ // If author is identity.Identity, AuthorId is set and data is deported
+ // in a IdentityExcerpt
+ LegacyAuthor LegacyAuthorExcerpt
+ AuthorId string
+
CreateMetadata map[string]string
}
+// identity.Bare data are directly embedded in the bug excerpt
+type LegacyAuthorExcerpt struct {
+ Name string
+ Login string
+}
+
func NewBugExcerpt(b bug.Interface, snap *bug.Snapshot) *BugExcerpt {
- return &BugExcerpt{
+ e := &BugExcerpt{
Id: b.Id(),
CreateLamportTime: b.CreateLamportTime(),
EditLamportTime: b.EditLamportTime(),
CreateUnixTime: b.FirstOp().GetUnixTime(),
EditUnixTime: snap.LastEditUnix(),
Status: snap.Status,
- Author: snap.Author,
Labels: snap.Labels,
CreateMetadata: b.FirstOp().AllMetadata(),
}
+
+ switch snap.Author.(type) {
+ case *identity.Identity:
+ e.AuthorId = snap.Author.Id()
+ case *identity.Bare:
+ e.LegacyAuthor = LegacyAuthorExcerpt{
+ Login: snap.Author.Login(),
+ Name: snap.Author.Name(),
+ }
+ default:
+ panic("unhandled identity type")
+ }
+
+ return e
}
-// Package initialisation used to register the type for (de)serialization
-func init() {
- gob.Register(BugExcerpt{})
+func (b *BugExcerpt) HumanId() string {
+ return bug.FormatHumanID(b.Id)
}
/*
diff --git a/cache/filter.go b/cache/filter.go
index 033df131..022a8ff2 100644
--- a/cache/filter.go
+++ b/cache/filter.go
@@ -1,11 +1,13 @@
package cache
import (
+ "strings"
+
"github.com/MichaelMure/git-bug/bug"
)
-// Filter is a functor that match a subset of bugs
-type Filter func(excerpt *BugExcerpt) bool
+// Filter is a predicate that match a subset of bugs
+type Filter func(repoCache *RepoCache, excerpt *BugExcerpt) bool
// StatusFilter return a Filter that match a bug status
func StatusFilter(query string) (Filter, error) {
@@ -14,21 +16,36 @@ func StatusFilter(query string) (Filter, error) {
return nil, err
}
- return func(excerpt *BugExcerpt) bool {
+ return func(repoCache *RepoCache, excerpt *BugExcerpt) bool {
return excerpt.Status == status
}, nil
}
// AuthorFilter return a Filter that match a bug author
func AuthorFilter(query string) Filter {
- return func(excerpt *BugExcerpt) bool {
- return excerpt.Author.Match(query)
+ return func(repoCache *RepoCache, excerpt *BugExcerpt) bool {
+ query = strings.ToLower(query)
+
+ // Normal identity
+ if excerpt.AuthorId != "" {
+ author, ok := repoCache.identitiesExcerpts[excerpt.AuthorId]
+ if !ok {
+ panic("missing identity in the cache")
+ }
+
+ return strings.Contains(strings.ToLower(author.Name), query) ||
+ strings.Contains(strings.ToLower(author.Login), query)
+ }
+
+ // Legacy identity support
+ return strings.Contains(strings.ToLower(excerpt.LegacyAuthor.Name), query) ||
+ strings.Contains(strings.ToLower(excerpt.LegacyAuthor.Login), query)
}
}
// LabelFilter return a Filter that match a label
func LabelFilter(label string) Filter {
- return func(excerpt *BugExcerpt) bool {
+ return func(repoCache *RepoCache, excerpt *BugExcerpt) bool {
for _, l := range excerpt.Labels {
if string(l) == label {
return true
@@ -40,7 +57,7 @@ func LabelFilter(label string) Filter {
// NoLabelFilter return a Filter that match the absence of labels
func NoLabelFilter() Filter {
- return func(excerpt *BugExcerpt) bool {
+ return func(repoCache *RepoCache, excerpt *BugExcerpt) bool {
return len(excerpt.Labels) == 0
}
}
@@ -54,20 +71,20 @@ type Filters struct {
}
// Match check if a bug match the set of filters
-func (f *Filters) Match(excerpt *BugExcerpt) bool {
- if match := f.orMatch(f.Status, excerpt); !match {
+func (f *Filters) Match(repoCache *RepoCache, excerpt *BugExcerpt) bool {
+ if match := f.orMatch(f.Status, repoCache, excerpt); !match {
return false
}
- if match := f.orMatch(f.Author, excerpt); !match {
+ if match := f.orMatch(f.Author, repoCache, excerpt); !match {
return false
}
- if match := f.orMatch(f.Label, excerpt); !match {
+ if match := f.orMatch(f.Label, repoCache, excerpt); !match {
return false
}
- if match := f.andMatch(f.NoFilters, excerpt); !match {
+ if match := f.andMatch(f.NoFilters, repoCache, excerpt); !match {
return false
}
@@ -75,28 +92,28 @@ func (f *Filters) Match(excerpt *BugExcerpt) bool {
}
// Check if any of the filters provided match the bug
-func (*Filters) orMatch(filters []Filter, excerpt *BugExcerpt) bool {
+func (*Filters) orMatch(filters []Filter, repoCache *RepoCache, excerpt *BugExcerpt) bool {
if len(filters) == 0 {
return true
}
match := false
for _, f := range filters {
- match = match || f(excerpt)
+ match = match || f(repoCache, excerpt)
}
return match
}
// Check if all of the filters provided match the bug
-func (*Filters) andMatch(filters []Filter, excerpt *BugExcerpt) bool {
+func (*Filters) andMatch(filters []Filter, repoCache *RepoCache, excerpt *BugExcerpt) bool {
if len(filters) == 0 {
return true
}
match := true
for _, f := range filters {
- match = match && f(excerpt)
+ match = match && f(repoCache, excerpt)
}
return match
diff --git a/cache/identity_cache.go b/cache/identity_cache.go
new file mode 100644
index 00000000..2ae55f2d
--- /dev/null
+++ b/cache/identity_cache.go
@@ -0,0 +1,43 @@
+package cache
+
+import (
+ "github.com/MichaelMure/git-bug/identity"
+)
+
+// IdentityCache is a wrapper around an Identity for caching.
+type IdentityCache struct {
+ *identity.Identity
+ repoCache *RepoCache
+}
+
+func NewIdentityCache(repoCache *RepoCache, id *identity.Identity) *IdentityCache {
+ return &IdentityCache{
+ Identity: id,
+ repoCache: repoCache,
+ }
+}
+
+func (i *IdentityCache) notifyUpdated() error {
+ return i.repoCache.identityUpdated(i.Identity.Id())
+}
+
+func (i *IdentityCache) AddVersion(version *identity.Version) error {
+ i.Identity.AddVersion(version)
+ return i.notifyUpdated()
+}
+
+func (i *IdentityCache) Commit() error {
+ err := i.Identity.Commit(i.repoCache.repo)
+ if err != nil {
+ return err
+ }
+ return i.notifyUpdated()
+}
+
+func (i *IdentityCache) CommitAsNeeded() error {
+ err := i.Identity.CommitAsNeeded(i.repoCache.repo)
+ if err != nil {
+ return err
+ }
+ return i.notifyUpdated()
+}
diff --git a/cache/identity_excerpt.go b/cache/identity_excerpt.go
new file mode 100644
index 00000000..2a13bc60
--- /dev/null
+++ b/cache/identity_excerpt.go
@@ -0,0 +1,70 @@
+package cache
+
+import (
+ "encoding/gob"
+ "fmt"
+
+ "github.com/MichaelMure/git-bug/identity"
+)
+
+// Package initialisation used to register the type for (de)serialization
+func init() {
+ gob.Register(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.
+type IdentityExcerpt struct {
+ Id string
+
+ Name string
+ Login string
+ ImmutableMetadata map[string]string
+}
+
+func NewIdentityExcerpt(i *identity.Identity) *IdentityExcerpt {
+ return &IdentityExcerpt{
+ Id: i.Id(),
+ Name: i.Name(),
+ Login: i.Login(),
+ ImmutableMetadata: i.ImmutableMetadata(),
+ }
+}
+
+func (i *IdentityExcerpt) HumanId() string {
+ return identity.FormatHumanID(i.Id)
+}
+
+// DisplayName return a non-empty string to display, representing the
+// identity, based on the non-empty values.
+func (i *IdentityExcerpt) DisplayName() string {
+ switch {
+ case i.Name == "" && i.Login != "":
+ return i.Login
+ case i.Name != "" && i.Login == "":
+ return i.Name
+ case i.Name != "" && i.Login != "":
+ return fmt.Sprintf("%s (%s)", i.Name, i.Login)
+ }
+
+ panic("invalid person data")
+}
+
+/*
+ * Sorting
+ */
+
+type IdentityById []*IdentityExcerpt
+
+func (b IdentityById) Len() int {
+ return len(b)
+}
+
+func (b IdentityById) Less(i, j int) bool {
+ return b[i].Id < b[j].Id
+}
+
+func (b IdentityById) Swap(i, j int) {
+ b[i], b[j] = b[j], b[i]
+}
diff --git a/cache/multi_repo_cache.go b/cache/multi_repo_cache.go
index ec435ff2..da1c26bd 100644
--- a/cache/multi_repo_cache.go
+++ b/cache/multi_repo_cache.go
@@ -8,6 +8,7 @@ import (
const lockfile = "lock"
+// MultiRepoCache is the root cache, holding multiple RepoCache.
type MultiRepoCache struct {
repos map[string]*RepoCache
}
diff --git a/cache/repo_cache.go b/cache/repo_cache.go
index 286e27a5..2b0fa360 100644
--- a/cache/repo_cache.go
+++ b/cache/repo_cache.go
@@ -14,27 +14,64 @@ import (
"time"
"github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/git"
"github.com/MichaelMure/git-bug/util/process"
)
-const cacheFile = "cache"
-const formatVersion = 1
+const bugCacheFile = "bug-cache"
+const identityCacheFile = "identity-cache"
+// 1: original format
+// 2: added cache for identities with a reference in the bug cache
+const formatVersion = 2
+
+type ErrInvalidCacheFormat struct {
+ message string
+}
+
+func (e ErrInvalidCacheFormat) Error() string {
+ return e.message
+}
+
+// RepoCache is a cache for a Repository. This cache has multiple functions:
+//
+// 1. After being loaded, a Bug is kept in memory in the cache, allowing for fast
+// access later.
+// 2. The cache maintain on memory and on disk a pre-digested excerpt for each bug,
+// allowing for fast querying the whole set of bugs without having to load
+// them individually.
+// 3. The cache guarantee that a single instance of a Bug is loaded at once, avoiding
+// loss of data that we could have with multiple copies in the same process.
+// 4. The same way, the cache maintain in memory a single copy of the loaded identities.
+//
+// The cache also protect the on-disk data by locking the git repository for its
+// own usage, by writing a lock file. Of course, normal git operations are not
+// affected, only git-bug related one.
type RepoCache struct {
// the underlying repo
repo repository.ClockedRepo
+
// excerpt of bugs data for all bugs
- excerpts map[string]*BugExcerpt
+ bugExcerpts map[string]*BugExcerpt
// bug loaded in memory
bugs map[string]*BugCache
+
+ // excerpt of identities data for all identities
+ identitiesExcerpts map[string]*IdentityExcerpt
+ // identities loaded in memory
+ identities map[string]*IdentityCache
+
+ // the user identity's id, if known
+ userIdentityId string
}
func NewRepoCache(r repository.ClockedRepo) (*RepoCache, error) {
c := &RepoCache{
- repo: r,
- bugs: make(map[string]*BugCache),
+ repo: r,
+ bugs: make(map[string]*BugCache),
+ identities: make(map[string]*IdentityCache),
}
err := c.lock()
@@ -46,6 +83,9 @@ func NewRepoCache(r repository.ClockedRepo) (*RepoCache, error) {
if err == nil {
return c, nil
}
+ if _, ok := err.(ErrInvalidCacheFormat); ok {
+ return nil, err
+ }
err = c.buildCache()
if err != nil {
@@ -125,14 +165,38 @@ func (c *RepoCache) bugUpdated(id string) error {
panic("missing bug in the cache")
}
- c.excerpts[id] = NewBugExcerpt(b.bug, b.Snapshot())
+ c.bugExcerpts[id] = NewBugExcerpt(b.bug, b.Snapshot())
- return c.write()
+ // we only need to write the bug cache
+ return c.writeBugCache()
}
-// load will try to read from the disk the bug cache file
+// 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 string) error {
+ i, ok := c.identities[id]
+ if !ok {
+ panic("missing identity in the cache")
+ }
+
+ c.identitiesExcerpts[id] = NewIdentityExcerpt(i.Identity)
+
+ // we only need to write the identity cache
+ return c.writeIdentityCache()
+}
+
+// load will try to read from the disk all the cache files
func (c *RepoCache) load() error {
- f, err := os.Open(cacheFilePath(c.repo))
+ err := c.loadBugCache()
+ if err != nil {
+ return err
+ }
+ return c.loadIdentityCache()
+}
+
+// load will try to read from the disk the bug cache file
+func (c *RepoCache) loadBugCache() error {
+ f, err := os.Open(bugCacheFilePath(c.repo))
if err != nil {
return err
}
@@ -149,16 +213,56 @@ func (c *RepoCache) load() error {
return err
}
- if aux.Version != 1 {
- return fmt.Errorf("unknown cache format version %v", aux.Version)
+ if aux.Version != 2 {
+ return ErrInvalidCacheFormat{
+ message: fmt.Sprintf("unknown cache format version %v", aux.Version),
+ }
}
- c.excerpts = aux.Excerpts
+ c.bugExcerpts = aux.Excerpts
return nil
}
-// write will serialize on disk the bug cache file
+// load will try to read from the disk the identity cache file
+func (c *RepoCache) loadIdentityCache() error {
+ f, err := os.Open(identityCacheFilePath(c.repo))
+ if err != nil {
+ return err
+ }
+
+ decoder := gob.NewDecoder(f)
+
+ aux := struct {
+ Version uint
+ Excerpts map[string]*IdentityExcerpt
+ }{}
+
+ err = decoder.Decode(&aux)
+ if err != nil {
+ return err
+ }
+
+ if aux.Version != 2 {
+ return ErrInvalidCacheFormat{
+ message: fmt.Sprintf("unknown cache format version %v", aux.Version),
+ }
+ }
+
+ c.identitiesExcerpts = aux.Excerpts
+ return nil
+}
+
+// write will serialize on disk all the cache files
func (c *RepoCache) write() error {
+ err := c.writeBugCache()
+ if err != nil {
+ return err
+ }
+ return c.writeIdentityCache()
+}
+
+// write will serialize on disk the bug cache file
+func (c *RepoCache) writeBugCache() error {
var data bytes.Buffer
aux := struct {
@@ -166,7 +270,7 @@ func (c *RepoCache) write() error {
Excerpts map[string]*BugExcerpt
}{
Version: formatVersion,
- Excerpts: c.excerpts,
+ Excerpts: c.bugExcerpts,
}
encoder := gob.NewEncoder(&data)
@@ -176,7 +280,7 @@ func (c *RepoCache) write() error {
return err
}
- f, err := os.Create(cacheFilePath(c.repo))
+ f, err := os.Create(bugCacheFilePath(c.repo))
if err != nil {
return err
}
@@ -189,14 +293,66 @@ func (c *RepoCache) write() error {
return f.Close()
}
-func cacheFilePath(repo repository.Repo) string {
- return path.Join(repo.GetPath(), ".git", "git-bug", cacheFile)
+// write will serialize on disk the identity cache file
+func (c *RepoCache) writeIdentityCache() error {
+ var data bytes.Buffer
+
+ aux := struct {
+ Version uint
+ Excerpts map[string]*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()
+}
+
+func bugCacheFilePath(repo repository.Repo) string {
+ return path.Join(repo.GetPath(), ".git", "git-bug", bugCacheFile)
+}
+
+func identityCacheFilePath(repo repository.Repo) string {
+ return path.Join(repo.GetPath(), ".git", "git-bug", identityCacheFile)
}
func (c *RepoCache) buildCache() error {
+ _, _ = fmt.Fprintf(os.Stderr, "Building identity cache... ")
+
+ c.identitiesExcerpts = make(map[string]*IdentityExcerpt)
+
+ allIdentities := identity.ReadAllLocalIdentities(c.repo)
+
+ for i := range allIdentities {
+ if i.Err != nil {
+ return i.Err
+ }
+
+ c.identitiesExcerpts[i.Identity.Id()] = NewIdentityExcerpt(i.Identity)
+ }
+
+ _, _ = fmt.Fprintln(os.Stderr, "Done.")
+
_, _ = fmt.Fprintf(os.Stderr, "Building bug cache... ")
- c.excerpts = make(map[string]*BugExcerpt)
+ c.bugExcerpts = make(map[string]*BugExcerpt)
allBugs := bug.ReadAllLocalBugs(c.repo)
@@ -206,7 +362,7 @@ func (c *RepoCache) buildCache() error {
}
snap := b.Bug.Compile()
- c.excerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, &snap)
+ c.bugExcerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, &snap)
}
_, _ = fmt.Fprintln(os.Stderr, "Done.")
@@ -231,13 +387,23 @@ func (c *RepoCache) ResolveBug(id string) (*BugCache, error) {
return cached, nil
}
+// ResolveBugExcerpt retrieve a BugExcerpt matching the exact given id
+func (c *RepoCache) ResolveBugExcerpt(id string) (*BugExcerpt, error) {
+ e, ok := c.bugExcerpts[id]
+ if !ok {
+ return nil, bug.ErrBugNotExist
+ }
+
+ return e, nil
+}
+
// ResolveBugPrefix retrieve a bug matching an id prefix. It fails if multiple
// bugs match.
func (c *RepoCache) ResolveBugPrefix(prefix string) (*BugCache, error) {
// preallocate but empty
matching := make([]string, 0, 5)
- for id := range c.excerpts {
+ for id := range c.bugExcerpts {
if strings.HasPrefix(id, prefix) {
matching = append(matching, id)
}
@@ -261,7 +427,7 @@ func (c *RepoCache) ResolveBugCreateMetadata(key string, value string) (*BugCach
// preallocate but empty
matching := make([]string, 0, 5)
- for id, excerpt := range c.excerpts {
+ for id, excerpt := range c.bugExcerpts {
if excerpt.CreateMetadata[key] == value {
matching = append(matching, id)
}
@@ -278,6 +444,7 @@ func (c *RepoCache) ResolveBugCreateMetadata(key string, value string) (*BugCach
return c.ResolveBug(matching[0])
}
+// QueryBugs return the id of all Bug matching the given Query
func (c *RepoCache) QueryBugs(query *Query) []string {
if query == nil {
return c.AllBugsIds()
@@ -285,8 +452,8 @@ func (c *RepoCache) QueryBugs(query *Query) []string {
var filtered []*BugExcerpt
- for _, excerpt := range c.excerpts {
- if query.Match(excerpt) {
+ for _, excerpt := range c.bugExcerpts {
+ if query.Match(c, excerpt) {
filtered = append(filtered, excerpt)
}
}
@@ -321,10 +488,10 @@ func (c *RepoCache) QueryBugs(query *Query) []string {
// AllBugsIds return all known bug ids
func (c *RepoCache) AllBugsIds() []string {
- result := make([]string, len(c.excerpts))
+ result := make([]string, len(c.bugExcerpts))
i := 0
- for _, excerpt := range c.excerpts {
+ for _, excerpt := range c.bugExcerpts {
result[i] = excerpt.Id
i++
}
@@ -332,11 +499,6 @@ func (c *RepoCache) AllBugsIds() []string {
return result
}
-// ClearAllBugs clear all bugs kept in memory
-func (c *RepoCache) ClearAllBugs() {
- c.bugs = make(map[string]*BugCache)
-}
-
// ValidLabels list valid labels
//
// Note: in the future, a proper label policy could be implemented where valid
@@ -345,7 +507,7 @@ func (c *RepoCache) ClearAllBugs() {
func (c *RepoCache) ValidLabels() []bug.Label {
set := map[bug.Label]interface{}{}
- for _, excerpt := range c.excerpts {
+ for _, excerpt := range c.bugExcerpts {
for _, l := range excerpt.Labels {
set[l] = nil
}
@@ -376,7 +538,7 @@ func (c *RepoCache) NewBug(title string, message string) (*BugCache, error) {
// NewBugWithFiles create a new bug with attached files for the message
// The new bug is written in the repository (commit)
func (c *RepoCache) NewBugWithFiles(title string, message string, files []git.Hash) (*BugCache, error) {
- author, err := bug.GetUser(c.repo)
+ author, err := c.GetUserIdentity()
if err != nil {
return nil, err
}
@@ -387,8 +549,8 @@ func (c *RepoCache) NewBugWithFiles(title string, message string, files []git.Ha
// NewBugWithFilesMeta create a new bug with attached files for the message, as
// well as metadata for the Create operation.
// The new bug is written in the repository (commit)
-func (c *RepoCache) NewBugRaw(author bug.Person, unixTime int64, title string, message string, files []git.Hash, metadata map[string]string) (*BugCache, error) {
- b, op, err := bug.CreateWithFiles(author, unixTime, title, message, files)
+func (c *RepoCache) NewBugRaw(author *IdentityCache, unixTime int64, title string, message string, files []git.Hash, metadata map[string]string) (*BugCache, error) {
+ b, op, err := bug.CreateWithFiles(author.Identity, unixTime, title, message, files)
if err != nil {
return nil, err
}
@@ -402,9 +564,14 @@ func (c *RepoCache) NewBugRaw(author bug.Person, unixTime int64, title string, m
return nil, err
}
+ if _, has := c.bugs[b.Id()]; has {
+ return nil, fmt.Errorf("bug %s already exist in the cache", b.Id())
+ }
+
cached := NewBugCache(c, b)
c.bugs[b.Id()] = cached
+ // force the write of the excerpt
err = c.bugUpdated(b.Id())
if err != nil {
return nil, err
@@ -421,6 +588,8 @@ func (c *RepoCache) Fetch(remote string) (string, error) {
// MergeAll will merge all the available remote bug
func (c *RepoCache) MergeAll(remote string) <-chan bug.MergeResult {
+ // TODO: add identities
+
out := make(chan bug.MergeResult)
// Intercept merge results to update the cache properly
@@ -441,7 +610,7 @@ func (c *RepoCache) MergeAll(remote string) <-chan bug.MergeResult {
case bug.MergeStatusNew, bug.MergeStatusUpdated:
b := result.Bug
snap := b.Compile()
- c.excerpts[id] = NewBugExcerpt(b, &snap)
+ c.bugExcerpts[id] = NewBugExcerpt(b, &snap)
}
}
@@ -524,3 +693,166 @@ func repoIsAvailable(repo repository.Repo) error {
return nil
}
+
+// ResolveIdentity retrieve an identity matching the exact given id
+func (c *RepoCache) ResolveIdentity(id string) (*IdentityCache, error) {
+ cached, ok := c.identities[id]
+ if ok {
+ return cached, nil
+ }
+
+ i, err := identity.ReadLocal(c.repo, id)
+ if err != nil {
+ return nil, err
+ }
+
+ cached = NewIdentityCache(c, i)
+ c.identities[id] = cached
+
+ return cached, nil
+}
+
+// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id
+func (c *RepoCache) ResolveIdentityExcerpt(id string) (*IdentityExcerpt, error) {
+ e, ok := c.identitiesExcerpts[id]
+ if !ok {
+ return nil, identity.ErrIdentityNotExist
+ }
+
+ return e, nil
+}
+
+// ResolveIdentityPrefix retrieve an Identity matching an id prefix.
+// It fails if multiple identities match.
+func (c *RepoCache) ResolveIdentityPrefix(prefix string) (*IdentityCache, error) {
+ // preallocate but empty
+ matching := make([]string, 0, 5)
+
+ for id := range c.identitiesExcerpts {
+ if strings.HasPrefix(id, prefix) {
+ matching = append(matching, id)
+ }
+ }
+
+ if len(matching) > 1 {
+ return nil, identity.ErrMultipleMatch{Matching: matching}
+ }
+
+ if len(matching) == 0 {
+ return nil, identity.ErrIdentityNotExist
+ }
+
+ return c.ResolveIdentity(matching[0])
+}
+
+// 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) {
+ // preallocate but empty
+ matching := make([]string, 0, 5)
+
+ for id, i := range c.identitiesExcerpts {
+ if i.ImmutableMetadata[key] == value {
+ matching = append(matching, id)
+ }
+ }
+
+ if len(matching) > 1 {
+ return nil, identity.ErrMultipleMatch{Matching: matching}
+ }
+
+ if len(matching) == 0 {
+ return nil, identity.ErrIdentityNotExist
+ }
+
+ return c.ResolveIdentity(matching[0])
+}
+
+// AllIdentityIds return all known identity ids
+func (c *RepoCache) AllIdentityIds() []string {
+ result := make([]string, len(c.identitiesExcerpts))
+
+ i := 0
+ for _, excerpt := range c.identitiesExcerpts {
+ result[i] = excerpt.Id
+ i++
+ }
+
+ return result
+}
+
+func (c *RepoCache) SetUserIdentity(i *IdentityCache) error {
+ err := identity.SetUserIdentity(c.repo, i.Identity)
+ if err != nil {
+ return err
+ }
+
+ // Make sure that everything is fine
+ if _, ok := c.identities[i.Id()]; !ok {
+ panic("SetUserIdentity while the identity is not from the cache, something is wrong")
+ }
+
+ c.userIdentityId = i.Id()
+
+ return nil
+}
+
+func (c *RepoCache) GetUserIdentity() (*IdentityCache, error) {
+ if c.userIdentityId != "" {
+ i, ok := c.identities[c.userIdentityId]
+ if ok {
+ return i, nil
+ }
+ }
+
+ i, err := identity.GetUserIdentity(c.repo)
+ if err != nil {
+ return nil, err
+ }
+
+ cached := NewIdentityCache(c, i)
+ c.identities[i.Id()] = cached
+ c.userIdentityId = i.Id()
+
+ return cached, nil
+}
+
+// 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)
+
+ for key, value := range metadata {
+ i.SetMetadata(key, value)
+ }
+
+ err := i.Commit(c.repo)
+ if err != nil {
+ return nil, err
+ }
+
+ 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
+
+ // force the write of the excerpt
+ err = c.identityUpdated(i.Id())
+ if err != nil {
+ return nil, err
+ }
+
+ return cached, nil
+}