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