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	Commit(bug BugCacher) error
 31
 32	// Mutations
 33
 34	NewBug(title string, message string) (BugCacher, error)
 35
 36	AddComment(repoRef *string, prefix string, message string) (BugCacher, error)
 37	ChangeLabels(repoRef *string, prefix string, added []string, removed []string) (BugCacher, error)
 38	Open(repoRef *string, prefix string) (BugCacher, error)
 39	Close(repoRef *string, prefix string) (BugCacher, error)
 40	SetTitle(repoRef *string, prefix string, title string) (BugCacher, error)
 41}
 42
 43type BugCacher interface {
 44	Snapshot() *bug.Snapshot
 45	ClearSnapshot()
 46	bug() *bug.Bug
 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(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(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) Commit(bug BugCacher) error {
186	err := bug.bug().Commit(c.repo)
187	if err != nil {
188		return err
189	}
190	return nil
191}
192
193func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) {
194	author, err := bug.GetUser(c.repo)
195	if err != nil {
196		return nil, err
197	}
198
199	b, err := operations.Create(author, title, message)
200	if err != nil {
201		return nil, err
202	}
203
204	err = b.Commit(c.repo)
205	if err != nil {
206		return nil, err
207	}
208
209	cached := NewBugCache(b)
210	c.bugs[b.Id()] = cached
211
212	return cached, nil
213}
214
215func (c *RepoCache) AddComment(repoRef *string, prefix string, message string) (BugCacher, error) {
216	author, err := bug.GetUser(c.repo)
217	if err != nil {
218		return nil, err
219	}
220
221	cached, err := c.ResolveBugPrefix(prefix)
222	if err != nil {
223		return nil, err
224	}
225
226	operations.Comment(cached.bug(), author, message)
227
228	// TODO: perf --> the snapshot could simply be updated with the new op
229	cached.ClearSnapshot()
230
231	return cached, nil
232}
233
234func (c *RepoCache) ChangeLabels(repoRef *string, prefix string, added []string, removed []string) (BugCacher, error) {
235	author, err := bug.GetUser(c.repo)
236	if err != nil {
237		return nil, err
238	}
239
240	cached, err := c.ResolveBugPrefix(prefix)
241	if err != nil {
242		return nil, err
243	}
244
245	err = operations.ChangeLabels(nil, cached.bug(), author, added, removed)
246	if err != nil {
247		return nil, err
248	}
249
250	// TODO: perf --> the snapshot could simply be updated with the new op
251	cached.ClearSnapshot()
252
253	return cached, nil
254}
255
256func (c *RepoCache) Open(repoRef *string, prefix string) (BugCacher, error) {
257	author, err := bug.GetUser(c.repo)
258	if err != nil {
259		return nil, err
260	}
261
262	cached, err := c.ResolveBugPrefix(prefix)
263	if err != nil {
264		return nil, err
265	}
266
267	operations.Open(cached.bug(), author)
268
269	// TODO: perf --> the snapshot could simply be updated with the new op
270	cached.ClearSnapshot()
271
272	return cached, nil
273}
274
275func (c *RepoCache) Close(repoRef *string, prefix string) (BugCacher, error) {
276	author, err := bug.GetUser(c.repo)
277	if err != nil {
278		return nil, err
279	}
280
281	cached, err := c.ResolveBugPrefix(prefix)
282	if err != nil {
283		return nil, err
284	}
285
286	operations.Close(cached.bug(), author)
287
288	// TODO: perf --> the snapshot could simply be updated with the new op
289	cached.ClearSnapshot()
290
291	return cached, nil
292}
293
294func (c *RepoCache) SetTitle(repoRef *string, prefix string, title string) (BugCacher, error) {
295	author, err := bug.GetUser(c.repo)
296	if err != nil {
297		return nil, err
298	}
299
300	cached, err := c.ResolveBugPrefix(prefix)
301	if err != nil {
302		return nil, err
303	}
304
305	operations.SetTitle(cached.bug(), author, title)
306
307	// TODO: perf --> the snapshot could simply be updated with the new op
308	cached.ClearSnapshot()
309
310	return cached, nil
311}
312
313// Bug ------------------------
314
315type BugCache struct {
316	b    *bug.Bug
317	snap *bug.Snapshot
318}
319
320func NewBugCache(b *bug.Bug) BugCacher {
321	return &BugCache{
322		b: b,
323	}
324}
325
326func (c *BugCache) Snapshot() *bug.Snapshot {
327	if c.snap == nil {
328		snap := c.b.Compile()
329		c.snap = &snap
330	}
331	return c.snap
332}
333
334func (c *BugCache) ClearSnapshot() {
335	c.snap = nil
336}
337
338func (c *BugCache) bug() *bug.Bug {
339	return c.b
340}