bug_cache.go

  1package cache
  2
  3import (
  4	"fmt"
  5	"sync"
  6	"time"
  7
  8	"github.com/MichaelMure/git-bug/bug"
  9	"github.com/MichaelMure/git-bug/entity"
 10	"github.com/MichaelMure/git-bug/repository"
 11)
 12
 13var ErrNoMatchingOp = fmt.Errorf("no matching operation found")
 14
 15// BugCache is a wrapper around a Bug. It provide multiple functions:
 16//
 17// 1. Provide a higher level API to use than the raw API from Bug.
 18// 2. Maintain an up to date Snapshot available.
 19// 3. Deal with concurrency.
 20type BugCache struct {
 21	repoCache *RepoCache
 22	mu        sync.RWMutex
 23	bug       *bug.WithSnapshot
 24}
 25
 26func NewBugCache(repoCache *RepoCache, b *bug.Bug) *BugCache {
 27	return &BugCache{
 28		repoCache: repoCache,
 29		bug:       &bug.WithSnapshot{Bug: b},
 30	}
 31}
 32
 33func (c *BugCache) Snapshot() *bug.Snapshot {
 34	c.mu.RLock()
 35	defer c.mu.RUnlock()
 36	return c.bug.Snapshot()
 37}
 38
 39func (c *BugCache) Id() entity.Id {
 40	c.mu.RLock()
 41	defer c.mu.RUnlock()
 42	return c.bug.Id()
 43}
 44
 45func (c *BugCache) notifyUpdated() error {
 46	return c.repoCache.bugUpdated(c.bug.Id())
 47}
 48
 49// ResolveOperationWithMetadata will find an operation that has the matching metadata
 50func (c *BugCache) ResolveOperationWithMetadata(key string, value string) (entity.Id, error) {
 51	c.mu.RLock()
 52	defer c.mu.RUnlock()
 53	// preallocate but empty
 54	matching := make([]entity.Id, 0, 5)
 55
 56	it := bug.NewOperationIterator(c.bug)
 57	for it.Next() {
 58		op := it.Value()
 59		opValue, ok := op.GetMetadata(key)
 60		if ok && value == opValue {
 61			matching = append(matching, op.Id())
 62		}
 63	}
 64
 65	if len(matching) == 0 {
 66		return "", ErrNoMatchingOp
 67	}
 68
 69	if len(matching) > 1 {
 70		return "", bug.NewErrMultipleMatchOp(matching)
 71	}
 72
 73	return matching[0], nil
 74}
 75
 76func (c *BugCache) AddComment(message string) (*bug.AddCommentOperation, error) {
 77	return c.AddCommentWithFiles(message, nil)
 78}
 79
 80func (c *BugCache) AddCommentWithFiles(message string, files []repository.Hash) (*bug.AddCommentOperation, error) {
 81	author, err := c.repoCache.GetUserIdentity()
 82	if err != nil {
 83		return nil, err
 84	}
 85
 86	return c.AddCommentRaw(author, time.Now().Unix(), message, files, nil)
 87}
 88
 89func (c *BugCache) AddCommentRaw(author *IdentityCache, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (*bug.AddCommentOperation, error) {
 90	c.mu.Lock()
 91	op, err := bug.AddCommentWithFiles(c.bug, author.Identity, unixTime, message, files)
 92	if err != nil {
 93		c.mu.Unlock()
 94		return nil, err
 95	}
 96
 97	for key, value := range metadata {
 98		op.SetMetadata(key, value)
 99	}
100
101	c.mu.Unlock()
102
103	return op, c.notifyUpdated()
104}
105
106func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
107	author, err := c.repoCache.GetUserIdentity()
108	if err != nil {
109		return nil, nil, err
110	}
111
112	return c.ChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
113}
114
115func (c *BugCache) ChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
116	c.mu.Lock()
117	changes, op, err := bug.ChangeLabels(c.bug, author.Identity, unixTime, added, removed)
118	if err != nil {
119		c.mu.Unlock()
120		return changes, nil, err
121	}
122
123	for key, value := range metadata {
124		op.SetMetadata(key, value)
125	}
126
127	c.mu.Unlock()
128
129	err = c.notifyUpdated()
130	if err != nil {
131		return nil, nil, err
132	}
133
134	return changes, op, nil
135}
136
137func (c *BugCache) ForceChangeLabels(added []string, removed []string) (*bug.LabelChangeOperation, error) {
138	author, err := c.repoCache.GetUserIdentity()
139	if err != nil {
140		return nil, err
141	}
142
143	return c.ForceChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
144}
145
146func (c *BugCache) ForceChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
147	c.mu.Lock()
148	op, err := bug.ForceChangeLabels(c.bug, author.Identity, unixTime, added, removed)
149	if err != nil {
150		c.mu.Unlock()
151		return nil, err
152	}
153
154	for key, value := range metadata {
155		op.SetMetadata(key, value)
156	}
157
158	c.mu.Unlock()
159	err = c.notifyUpdated()
160	if err != nil {
161		return nil, err
162	}
163
164	return op, nil
165}
166
167func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
168	author, err := c.repoCache.GetUserIdentity()
169	if err != nil {
170		return nil, err
171	}
172
173	return c.OpenRaw(author, time.Now().Unix(), nil)
174}
175
176func (c *BugCache) OpenRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
177	c.mu.Lock()
178	op, err := bug.Open(c.bug, author.Identity, unixTime)
179	if err != nil {
180		c.mu.Unlock()
181		return nil, err
182	}
183
184	for key, value := range metadata {
185		op.SetMetadata(key, value)
186	}
187
188	c.mu.Unlock()
189	return op, c.notifyUpdated()
190}
191
192func (c *BugCache) Close() (*bug.SetStatusOperation, error) {
193	author, err := c.repoCache.GetUserIdentity()
194	if err != nil {
195		return nil, err
196	}
197
198	return c.CloseRaw(author, time.Now().Unix(), nil)
199}
200
201func (c *BugCache) CloseRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
202	c.mu.Lock()
203	op, err := bug.Close(c.bug, author.Identity, unixTime)
204	if err != nil {
205		c.mu.Unlock()
206		return nil, err
207	}
208
209	for key, value := range metadata {
210		op.SetMetadata(key, value)
211	}
212
213	c.mu.Unlock()
214	return op, c.notifyUpdated()
215}
216
217func (c *BugCache) SetTitle(title string) (*bug.SetTitleOperation, error) {
218	author, err := c.repoCache.GetUserIdentity()
219	if err != nil {
220		return nil, err
221	}
222
223	return c.SetTitleRaw(author, time.Now().Unix(), title, nil)
224}
225
226func (c *BugCache) SetTitleRaw(author *IdentityCache, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
227	c.mu.Lock()
228	op, err := bug.SetTitle(c.bug, author.Identity, unixTime, title)
229	if err != nil {
230		c.mu.Unlock()
231		return nil, err
232	}
233
234	for key, value := range metadata {
235		op.SetMetadata(key, value)
236	}
237
238	c.mu.Unlock()
239	return op, c.notifyUpdated()
240}
241
242func (c *BugCache) EditCreateComment(body string) (*bug.EditCommentOperation, error) {
243	author, err := c.repoCache.GetUserIdentity()
244	if err != nil {
245		return nil, err
246	}
247
248	return c.EditCreateCommentRaw(author, time.Now().Unix(), body, nil)
249}
250
251func (c *BugCache) EditCreateCommentRaw(author *IdentityCache, unixTime int64, body string, metadata map[string]string) (*bug.EditCommentOperation, error) {
252	c.mu.Lock()
253	op, err := bug.EditCreateComment(c.bug, author.Identity, unixTime, body)
254	if err != nil {
255		c.mu.Unlock()
256		return nil, err
257	}
258
259	for key, value := range metadata {
260		op.SetMetadata(key, value)
261	}
262
263	c.mu.Unlock()
264	return op, c.notifyUpdated()
265}
266
267func (c *BugCache) EditComment(target entity.Id, message string) (*bug.EditCommentOperation, error) {
268	author, err := c.repoCache.GetUserIdentity()
269	if err != nil {
270		return nil, err
271	}
272
273	return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil)
274}
275
276func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target entity.Id, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
277	c.mu.Lock()
278	op, err := bug.EditComment(c.bug, author.Identity, unixTime, target, message)
279	if err != nil {
280		c.mu.Unlock()
281		return nil, err
282	}
283
284	for key, value := range metadata {
285		op.SetMetadata(key, value)
286	}
287
288	c.mu.Unlock()
289	return op, c.notifyUpdated()
290}
291
292func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
293	author, err := c.repoCache.GetUserIdentity()
294	if err != nil {
295		return nil, err
296	}
297
298	return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
299}
300
301func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
302	c.mu.Lock()
303	op, err := bug.SetMetadata(c.bug, author.Identity, unixTime, target, newMetadata)
304	if err != nil {
305		c.mu.Unlock()
306		return nil, err
307	}
308
309	c.mu.Unlock()
310	return op, c.notifyUpdated()
311}
312
313func (c *BugCache) Commit() error {
314	c.mu.Lock()
315	err := c.bug.Commit(c.repoCache.repo)
316	if err != nil {
317		c.mu.Unlock()
318		return err
319	}
320	c.mu.Unlock()
321	return c.notifyUpdated()
322}
323
324func (c *BugCache) CommitAsNeeded() error {
325	c.mu.Lock()
326	err := c.bug.CommitAsNeeded(c.repoCache.repo)
327	if err != nil {
328		c.mu.Unlock()
329		return err
330	}
331	c.mu.Unlock()
332	return c.notifyUpdated()
333}
334
335func (c *BugCache) NeedCommit() bool {
336	c.mu.RLock()
337	defer c.mu.RUnlock()
338	return c.bug.NeedCommit()
339}