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)
 11
 12type Cacher interface {
 13	RegisterRepository(ref string, repo repository.Repo)
 14	RegisterDefaultRepository(repo repository.Repo)
 15
 16	ResolveRepo(ref string) (RepoCacher, error)
 17	DefaultRepo() (RepoCacher, error)
 18
 19	// Shortcut to resolve on the default repo for convenience
 20	DefaultResolveBug(id string) (BugCacher, error)
 21	DefaultResolveBugPrefix(prefix string) (BugCacher, error)
 22}
 23
 24type RepoCacher interface {
 25	Repository() repository.Repo
 26	ResolveBug(id string) (BugCacher, error)
 27	ResolveBugPrefix(prefix string) (BugCacher, error)
 28	AllBugIds() ([]string, error)
 29	ClearAllBugs()
 30
 31	// Mutations
 32	NewBug(title string, message string) (BugCacher, error)
 33}
 34
 35type BugCacher interface {
 36	Snapshot() *bug.Snapshot
 37	ClearSnapshot()
 38
 39	// Mutations
 40	AddComment(message string) (BugCacher, error)
 41	ChangeLabels(added []string, removed []string) (BugCacher, error)
 42	Open() (BugCacher, error)
 43	Close() (BugCacher, error)
 44	SetTitle(title string) (BugCacher, error)
 45
 46	Commit() (BugCacher, 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
 89func (c *RootCache) DefaultResolveBug(id string) (BugCacher, error) {
 90	repo, err := c.DefaultRepo()
 91
 92	if err != nil {
 93		return nil, err
 94	}
 95
 96	return repo.ResolveBug(id)
 97}
 98
 99func (c *RootCache) DefaultResolveBugPrefix(prefix string) (BugCacher, error) {
100	repo, err := c.DefaultRepo()
101
102	if err != nil {
103		return nil, err
104	}
105
106	return repo.ResolveBugPrefix(prefix)
107}
108
109// Repo ------------------------
110
111type RepoCache struct {
112	repo repository.Repo
113	bugs map[string]BugCacher
114}
115
116func NewRepoCache(r repository.Repo) RepoCacher {
117	return &RepoCache{
118		repo: r,
119		bugs: make(map[string]BugCacher),
120	}
121}
122
123func (c *RepoCache) Repository() repository.Repo {
124	return c.repo
125}
126
127func (c *RepoCache) ResolveBug(id string) (BugCacher, error) {
128	cached, ok := c.bugs[id]
129	if ok {
130		return cached, nil
131	}
132
133	b, err := bug.ReadLocalBug(c.repo, id)
134	if err != nil {
135		return nil, err
136	}
137
138	cached = NewBugCache(c.repo, b)
139	c.bugs[id] = cached
140
141	return cached, nil
142}
143
144func (c *RepoCache) ResolveBugPrefix(prefix string) (BugCacher, error) {
145	// preallocate but empty
146	matching := make([]string, 0, 5)
147
148	for id := range c.bugs {
149		if strings.HasPrefix(id, prefix) {
150			matching = append(matching, id)
151		}
152	}
153
154	// TODO: should check matching bug in the repo as well
155
156	if len(matching) > 1 {
157		return nil, fmt.Errorf("Multiple matching bug found:\n%s", strings.Join(matching, "\n"))
158	}
159
160	if len(matching) == 1 {
161		b := c.bugs[matching[0]]
162		return b, nil
163	}
164
165	b, err := bug.FindLocalBug(c.repo, prefix)
166
167	if err != nil {
168		return nil, err
169	}
170
171	cached := NewBugCache(c.repo, b)
172	c.bugs[b.Id()] = cached
173
174	return cached, nil
175}
176
177func (c *RepoCache) AllBugIds() ([]string, error) {
178	return bug.ListLocalIds(c.repo)
179}
180
181func (c *RepoCache) ClearAllBugs() {
182	c.bugs = make(map[string]BugCacher)
183}
184
185func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) {
186	author, err := bug.GetUser(c.repo)
187	if err != nil {
188		return nil, err
189	}
190
191	b, err := operations.Create(author, title, message)
192	if err != nil {
193		return nil, err
194	}
195
196	err = b.Commit(c.repo)
197	if err != nil {
198		return nil, err
199	}
200
201	cached := NewBugCache(c.repo, b)
202	c.bugs[b.Id()] = cached
203
204	return cached, nil
205}
206
207// Bug ------------------------
208
209type BugCache struct {
210	repo repository.Repo
211	bug  *bug.Bug
212	snap *bug.Snapshot
213}
214
215func NewBugCache(repo repository.Repo, b *bug.Bug) BugCacher {
216	return &BugCache{
217		repo: repo,
218		bug:  b,
219	}
220}
221
222func (c *BugCache) Snapshot() *bug.Snapshot {
223	if c.snap == nil {
224		snap := c.bug.Compile()
225		c.snap = &snap
226	}
227	return c.snap
228}
229
230func (c *BugCache) ClearSnapshot() {
231	c.snap = nil
232}
233
234func (c *BugCache) AddComment(message string) (BugCacher, error) {
235	author, err := bug.GetUser(c.repo)
236	if err != nil {
237		return nil, err
238	}
239
240	operations.Comment(c.bug, author, message)
241
242	// TODO: perf --> the snapshot could simply be updated with the new op
243	c.ClearSnapshot()
244
245	return c, nil
246}
247
248func (c *BugCache) ChangeLabels(added []string, removed []string) (BugCacher, error) {
249	author, err := bug.GetUser(c.repo)
250	if err != nil {
251		return nil, err
252	}
253
254	err = operations.ChangeLabels(nil, c.bug, author, added, removed)
255	if err != nil {
256		return nil, err
257	}
258
259	// TODO: perf --> the snapshot could simply be updated with the new op
260	c.ClearSnapshot()
261
262	return c, nil
263}
264
265func (c *BugCache) Open() (BugCacher, error) {
266	author, err := bug.GetUser(c.repo)
267	if err != nil {
268		return nil, err
269	}
270
271	operations.Open(c.bug, author)
272
273	// TODO: perf --> the snapshot could simply be updated with the new op
274	c.ClearSnapshot()
275
276	return c, nil
277}
278
279func (c *BugCache) Close() (BugCacher, error) {
280	author, err := bug.GetUser(c.repo)
281	if err != nil {
282		return nil, err
283	}
284
285	operations.Close(c.bug, author)
286
287	// TODO: perf --> the snapshot could simply be updated with the new op
288	c.ClearSnapshot()
289
290	return c, nil
291}
292
293func (c *BugCache) SetTitle(title string) (BugCacher, error) {
294	author, err := bug.GetUser(c.repo)
295	if err != nil {
296		return nil, err
297	}
298
299	operations.SetTitle(c.bug, author, title)
300
301	// TODO: perf --> the snapshot could simply be updated with the new op
302	c.ClearSnapshot()
303
304	return c, nil
305}
306
307func (c *BugCache) Commit() (BugCacher, error) {
308	err := c.bug.Commit(c.repo)
309	if err != nil {
310		return nil, err
311	}
312	return c, nil
313}