package cache import ( "fmt" "io" "strings" "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug/operations" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util" ) type Cacher interface { RegisterRepository(ref string, repo repository.Repo) RegisterDefaultRepository(repo repository.Repo) ResolveRepo(ref string) (RepoCacher, error) DefaultRepo() (RepoCacher, error) } type RepoCacher interface { Repository() repository.Repo ResolveBug(id string) (BugCacher, error) ResolveBugPrefix(prefix string) (BugCacher, error) AllBugIds() ([]string, error) ClearAllBugs() // Mutations NewBug(title string, message string) (BugCacher, error) NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) Fetch(remote string) (string, error) MergeAll(remote string) <-chan bug.MergeResult Pull(remote string, out io.Writer) error Push(remote string) (string, error) } type BugCacher interface { Snapshot() *bug.Snapshot ClearSnapshot() // Mutations AddComment(message string) error AddCommentWithFiles(message string, files []util.Hash) error ChangeLabels(added []string, removed []string) error Open() error Close() error SetTitle(title string) error Commit() error CommitAsNeeded() error } // Cacher ------------------------ type RootCache struct { repos map[string]RepoCacher } func NewCache() RootCache { return RootCache{ repos: make(map[string]RepoCacher), } } func (c *RootCache) RegisterRepository(ref string, repo repository.Repo) { c.repos[ref] = NewRepoCache(repo) } func (c *RootCache) RegisterDefaultRepository(repo repository.Repo) { c.repos[""] = NewRepoCache(repo) } func (c *RootCache) DefaultRepo() (RepoCacher, error) { if len(c.repos) != 1 { return nil, fmt.Errorf("repository is not unique") } for _, r := range c.repos { return r, nil } panic("unreachable") } func (c *RootCache) ResolveRepo(ref string) (RepoCacher, error) { r, ok := c.repos[ref] if !ok { return nil, fmt.Errorf("unknown repo") } return r, nil } // Repo ------------------------ type RepoCache struct { repo repository.Repo bugs map[string]BugCacher } func NewRepoCache(r repository.Repo) RepoCacher { return &RepoCache{ repo: r, bugs: make(map[string]BugCacher), } } func (c *RepoCache) Repository() repository.Repo { return c.repo } func (c *RepoCache) ResolveBug(id string) (BugCacher, error) { cached, ok := c.bugs[id] if ok { return cached, nil } b, err := bug.ReadLocalBug(c.repo, id) if err != nil { return nil, err } cached = NewBugCache(c.repo, b) c.bugs[id] = cached return cached, nil } func (c *RepoCache) ResolveBugPrefix(prefix string) (BugCacher, error) { // preallocate but empty matching := make([]string, 0, 5) for id := range c.bugs { if strings.HasPrefix(id, prefix) { matching = append(matching, id) } } // TODO: should check matching bug in the repo as well if len(matching) > 1 { return nil, fmt.Errorf("Multiple matching bug found:\n%s", strings.Join(matching, "\n")) } if len(matching) == 1 { b := c.bugs[matching[0]] return b, nil } b, err := bug.FindLocalBug(c.repo, prefix) if err != nil { return nil, err } cached := NewBugCache(c.repo, b) c.bugs[b.Id()] = cached return cached, nil } func (c *RepoCache) AllBugIds() ([]string, error) { return bug.ListLocalIds(c.repo) } func (c *RepoCache) ClearAllBugs() { c.bugs = make(map[string]BugCacher) } func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) { return c.NewBugWithFiles(title, message, nil) } func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) { author, err := bug.GetUser(c.repo) if err != nil { return nil, err } b, err := operations.CreateWithFiles(author, title, message, files) if err != nil { return nil, err } err = b.Commit(c.repo) if err != nil { return nil, err } cached := NewBugCache(c.repo, b) c.bugs[b.Id()] = cached return cached, nil } func (c *RepoCache) Fetch(remote string) (string, error) { return bug.Fetch(c.repo, remote) } func (c *RepoCache) MergeAll(remote string) <-chan bug.MergeResult { return bug.MergeAll(c.repo, remote) } func (c *RepoCache) Pull(remote string, out io.Writer) error { return bug.Pull(c.repo, out, remote) } func (c *RepoCache) Push(remote string) (string, error) { return bug.Push(c.repo, remote) } // Bug ------------------------ type BugCache struct { repo repository.Repo bug *bug.Bug snap *bug.Snapshot } func NewBugCache(repo repository.Repo, b *bug.Bug) BugCacher { return &BugCache{ repo: repo, bug: b, } } func (c *BugCache) Snapshot() *bug.Snapshot { if c.snap == nil { snap := c.bug.Compile() c.snap = &snap } return c.snap } func (c *BugCache) ClearSnapshot() { c.snap = nil } func (c *BugCache) AddComment(message string) error { return c.AddCommentWithFiles(message, nil) } func (c *BugCache) AddCommentWithFiles(message string, files []util.Hash) error { author, err := bug.GetUser(c.repo) if err != nil { return err } operations.CommentWithFiles(c.bug, author, message, files) // TODO: perf --> the snapshot could simply be updated with the new op c.ClearSnapshot() return nil } func (c *BugCache) ChangeLabels(added []string, removed []string) error { author, err := bug.GetUser(c.repo) if err != nil { return err } err = operations.ChangeLabels(nil, c.bug, author, added, removed) if err != nil { return err } // TODO: perf --> the snapshot could simply be updated with the new op c.ClearSnapshot() return nil } func (c *BugCache) Open() error { author, err := bug.GetUser(c.repo) if err != nil { return err } operations.Open(c.bug, author) // TODO: perf --> the snapshot could simply be updated with the new op c.ClearSnapshot() return nil } func (c *BugCache) Close() error { author, err := bug.GetUser(c.repo) if err != nil { return err } operations.Close(c.bug, author) // TODO: perf --> the snapshot could simply be updated with the new op c.ClearSnapshot() return nil } func (c *BugCache) SetTitle(title string) error { author, err := bug.GetUser(c.repo) if err != nil { return err } operations.SetTitle(c.bug, author, title) // TODO: perf --> the snapshot could simply be updated with the new op c.ClearSnapshot() return nil } func (c *BugCache) Commit() error { return c.bug.Commit(c.repo) } func (c *BugCache) CommitAsNeeded() error { if c.bug.HasPendingOp() { return c.bug.Commit(c.repo) } return nil }