cache.go

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