cache.go

  1package cache
  2
  3import (
  4	"fmt"
  5	"strings"
  6
  7	"github.com/MichaelMure/git-bug/bug"
  8	"github.com/MichaelMure/git-bug/bug/operations"
  9	"github.com/MichaelMure/git-bug/repository"
 10	"github.com/MichaelMure/git-bug/util"
 11)
 12
 13type Cacher interface {
 14	RegisterRepository(ref string, repo repository.Repo)
 15	RegisterDefaultRepository(repo repository.Repo)
 16
 17	ResolveRepo(ref string) (RepoCacher, error)
 18	DefaultRepo() (RepoCacher, error)
 19}
 20
 21type RepoCacher interface {
 22	Repository() repository.Repo
 23	ResolveBug(id string) (BugCacher, error)
 24	ResolveBugPrefix(prefix string) (BugCacher, error)
 25	AllBugIds() ([]string, error)
 26	ClearAllBugs()
 27
 28	// Mutations
 29	NewBug(title string, message string) (BugCacher, error)
 30	NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error)
 31}
 32
 33type BugCacher interface {
 34	Snapshot() *bug.Snapshot
 35	ClearSnapshot()
 36
 37	// Mutations
 38	AddComment(message string) error
 39	AddCommentWithFiles(message string, files []util.Hash) error
 40	ChangeLabels(added []string, removed []string) error
 41	Open() error
 42	Close() error
 43	SetTitle(title string) error
 44
 45	Commit() error
 46	CommitAsNeeded() error
 47}
 48
 49// Cacher ------------------------
 50
 51type RootCache struct {
 52	repos map[string]RepoCacher
 53}
 54
 55func NewCache() RootCache {
 56	return RootCache{
 57		repos: make(map[string]RepoCacher),
 58	}
 59}
 60
 61func (c *RootCache) RegisterRepository(ref string, repo repository.Repo) {
 62	c.repos[ref] = NewRepoCache(repo)
 63}
 64
 65func (c *RootCache) RegisterDefaultRepository(repo repository.Repo) {
 66	c.repos[""] = NewRepoCache(repo)
 67}
 68
 69func (c *RootCache) DefaultRepo() (RepoCacher, error) {
 70	if len(c.repos) != 1 {
 71		return nil, fmt.Errorf("repository is not unique")
 72	}
 73
 74	for _, r := range c.repos {
 75		return r, nil
 76	}
 77
 78	panic("unreachable")
 79}
 80
 81func (c *RootCache) ResolveRepo(ref string) (RepoCacher, error) {
 82	r, ok := c.repos[ref]
 83	if !ok {
 84		return nil, fmt.Errorf("unknown repo")
 85	}
 86	return r, nil
 87}
 88
 89// Repo ------------------------
 90
 91type RepoCache struct {
 92	repo repository.Repo
 93	bugs map[string]BugCacher
 94}
 95
 96func NewRepoCache(r repository.Repo) RepoCacher {
 97	return &RepoCache{
 98		repo: r,
 99		bugs: make(map[string]BugCacher),
100	}
101}
102
103func (c *RepoCache) Repository() repository.Repo {
104	return c.repo
105}
106
107func (c *RepoCache) ResolveBug(id string) (BugCacher, error) {
108	cached, ok := c.bugs[id]
109	if ok {
110		return cached, nil
111	}
112
113	b, err := bug.ReadLocalBug(c.repo, id)
114	if err != nil {
115		return nil, err
116	}
117
118	cached = NewBugCache(c.repo, b)
119	c.bugs[id] = cached
120
121	return cached, nil
122}
123
124func (c *RepoCache) ResolveBugPrefix(prefix string) (BugCacher, error) {
125	// preallocate but empty
126	matching := make([]string, 0, 5)
127
128	for id := range c.bugs {
129		if strings.HasPrefix(id, prefix) {
130			matching = append(matching, id)
131		}
132	}
133
134	// TODO: should check matching bug in the repo as well
135
136	if len(matching) > 1 {
137		return nil, fmt.Errorf("Multiple matching bug found:\n%s", strings.Join(matching, "\n"))
138	}
139
140	if len(matching) == 1 {
141		b := c.bugs[matching[0]]
142		return b, nil
143	}
144
145	b, err := bug.FindLocalBug(c.repo, prefix)
146
147	if err != nil {
148		return nil, err
149	}
150
151	cached := NewBugCache(c.repo, b)
152	c.bugs[b.Id()] = cached
153
154	return cached, nil
155}
156
157func (c *RepoCache) AllBugIds() ([]string, error) {
158	return bug.ListLocalIds(c.repo)
159}
160
161func (c *RepoCache) ClearAllBugs() {
162	c.bugs = make(map[string]BugCacher)
163}
164
165func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) {
166	return c.NewBugWithFiles(title, message, nil)
167}
168
169func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) {
170	author, err := bug.GetUser(c.repo)
171	if err != nil {
172		return nil, err
173	}
174
175	b, err := operations.CreateWithFiles(author, title, message, files)
176	if err != nil {
177		return nil, err
178	}
179
180	err = b.Commit(c.repo)
181	if err != nil {
182		return nil, err
183	}
184
185	cached := NewBugCache(c.repo, b)
186	c.bugs[b.Id()] = cached
187
188	return cached, nil
189}
190
191// Bug ------------------------
192
193type BugCache struct {
194	repo repository.Repo
195	bug  *bug.Bug
196	snap *bug.Snapshot
197}
198
199func NewBugCache(repo repository.Repo, b *bug.Bug) BugCacher {
200	return &BugCache{
201		repo: repo,
202		bug:  b,
203	}
204}
205
206func (c *BugCache) Snapshot() *bug.Snapshot {
207	if c.snap == nil {
208		snap := c.bug.Compile()
209		c.snap = &snap
210	}
211	return c.snap
212}
213
214func (c *BugCache) ClearSnapshot() {
215	c.snap = nil
216}
217
218func (c *BugCache) AddComment(message string) error {
219	return c.AddCommentWithFiles(message, nil)
220}
221
222func (c *BugCache) AddCommentWithFiles(message string, files []util.Hash) error {
223	author, err := bug.GetUser(c.repo)
224	if err != nil {
225		return err
226	}
227
228	operations.CommentWithFiles(c.bug, author, message, files)
229
230	// TODO: perf --> the snapshot could simply be updated with the new op
231	c.ClearSnapshot()
232
233	return nil
234}
235
236func (c *BugCache) ChangeLabels(added []string, removed []string) error {
237	author, err := bug.GetUser(c.repo)
238	if err != nil {
239		return err
240	}
241
242	err = operations.ChangeLabels(nil, c.bug, author, added, removed)
243	if err != nil {
244		return err
245	}
246
247	// TODO: perf --> the snapshot could simply be updated with the new op
248	c.ClearSnapshot()
249
250	return nil
251}
252
253func (c *BugCache) Open() error {
254	author, err := bug.GetUser(c.repo)
255	if err != nil {
256		return err
257	}
258
259	operations.Open(c.bug, author)
260
261	// TODO: perf --> the snapshot could simply be updated with the new op
262	c.ClearSnapshot()
263
264	return nil
265}
266
267func (c *BugCache) Close() error {
268	author, err := bug.GetUser(c.repo)
269	if err != nil {
270		return err
271	}
272
273	operations.Close(c.bug, author)
274
275	// TODO: perf --> the snapshot could simply be updated with the new op
276	c.ClearSnapshot()
277
278	return nil
279}
280
281func (c *BugCache) SetTitle(title string) error {
282	author, err := bug.GetUser(c.repo)
283	if err != nil {
284		return err
285	}
286
287	operations.SetTitle(c.bug, author, title)
288
289	// TODO: perf --> the snapshot could simply be updated with the new op
290	c.ClearSnapshot()
291
292	return nil
293}
294
295func (c *BugCache) Commit() error {
296	return c.bug.Commit(c.repo)
297}
298
299func (c *BugCache) CommitAsNeeded() error {
300	if c.bug.HasPendingOp() {
301		return c.bug.Commit(c.repo)
302	}
303	return nil
304}