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	defer c.mu.Unlock()
 92	op, err := bug.AddCommentWithFiles(c.bug, author.Identity, unixTime, message, files)
 93	if err != nil {
 94		return nil, err
 95	}
 96
 97	for key, value := range metadata {
 98		op.SetMetadata(key, value)
 99	}
100
101	return op, c.notifyUpdated()
102}
103
104func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
105	author, err := c.repoCache.GetUserIdentity()
106	if err != nil {
107		return nil, nil, err
108	}
109
110	return c.ChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
111}
112
113func (c *BugCache) ChangeLabelsRaw(author *IdentityCache, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
114	c.mu.Lock()
115	defer c.mu.Unlock()
116	changes, op, err := bug.ChangeLabels(c.bug, author.Identity, unixTime, added, removed)
117	if err != nil {
118		return changes, nil, err
119	}
120
121	for key, value := range metadata {
122		op.SetMetadata(key, value)
123	}
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	defer c.mu.Unlock()
145	op, err := bug.ForceChangeLabels(c.bug, author.Identity, unixTime, added, removed)
146	if err != nil {
147		return nil, err
148	}
149
150	for key, value := range metadata {
151		op.SetMetadata(key, value)
152	}
153
154	err = c.notifyUpdated()
155	if err != nil {
156		return nil, err
157	}
158
159	return op, nil
160}
161
162func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
163	author, err := c.repoCache.GetUserIdentity()
164	if err != nil {
165		return nil, err
166	}
167
168	return c.OpenRaw(author, time.Now().Unix(), nil)
169}
170
171func (c *BugCache) OpenRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
172	c.mu.Lock()
173	defer c.mu.Unlock()
174	op, err := bug.Open(c.bug, author.Identity, unixTime)
175	if err != nil {
176		return nil, err
177	}
178
179	for key, value := range metadata {
180		op.SetMetadata(key, value)
181	}
182
183	return op, c.notifyUpdated()
184}
185
186func (c *BugCache) Close() (*bug.SetStatusOperation, error) {
187	author, err := c.repoCache.GetUserIdentity()
188	if err != nil {
189		return nil, err
190	}
191
192	return c.CloseRaw(author, time.Now().Unix(), nil)
193}
194
195func (c *BugCache) CloseRaw(author *IdentityCache, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
196	c.mu.Lock()
197	defer c.mu.Unlock()
198	op, err := bug.Close(c.bug, author.Identity, unixTime)
199	if err != nil {
200		return nil, err
201	}
202
203	for key, value := range metadata {
204		op.SetMetadata(key, value)
205	}
206
207	return op, c.notifyUpdated()
208}
209
210func (c *BugCache) SetTitle(title string) (*bug.SetTitleOperation, error) {
211	author, err := c.repoCache.GetUserIdentity()
212	if err != nil {
213		return nil, err
214	}
215
216	return c.SetTitleRaw(author, time.Now().Unix(), title, nil)
217}
218
219func (c *BugCache) SetTitleRaw(author *IdentityCache, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
220	c.mu.Lock()
221	defer c.mu.Unlock()
222	op, err := bug.SetTitle(c.bug, author.Identity, unixTime, title)
223	if err != nil {
224		return nil, err
225	}
226
227	for key, value := range metadata {
228		op.SetMetadata(key, value)
229	}
230
231	return op, c.notifyUpdated()
232}
233
234func (c *BugCache) EditCreateComment(body string) (*bug.EditCommentOperation, error) {
235	author, err := c.repoCache.GetUserIdentity()
236	if err != nil {
237		return nil, err
238	}
239
240	return c.EditCreateCommentRaw(author, time.Now().Unix(), body, nil)
241}
242
243func (c *BugCache) EditCreateCommentRaw(author *IdentityCache, unixTime int64, body string, metadata map[string]string) (*bug.EditCommentOperation, error) {
244	c.mu.Lock()
245	defer c.mu.Unlock()
246	op, err := bug.EditCreateComment(c.bug, author.Identity, unixTime, body)
247	if err != nil {
248		return nil, err
249	}
250
251	for key, value := range metadata {
252		op.SetMetadata(key, value)
253	}
254
255	return op, c.notifyUpdated()
256}
257
258func (c *BugCache) EditComment(target entity.Id, message string) (*bug.EditCommentOperation, error) {
259	author, err := c.repoCache.GetUserIdentity()
260	if err != nil {
261		return nil, err
262	}
263
264	return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil)
265}
266
267func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target entity.Id, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
268	c.mu.Lock()
269	defer c.mu.Unlock()
270	op, err := bug.EditComment(c.bug, author.Identity, unixTime, target, message)
271	if err != nil {
272		return nil, err
273	}
274
275	for key, value := range metadata {
276		op.SetMetadata(key, value)
277	}
278
279	return op, c.notifyUpdated()
280}
281
282func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
283	author, err := c.repoCache.GetUserIdentity()
284	if err != nil {
285		return nil, err
286	}
287
288	return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
289}
290
291func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
292	c.mu.Lock()
293	defer c.mu.Unlock()
294	op, err := bug.SetMetadata(c.bug, author.Identity, unixTime, target, newMetadata)
295	if err != nil {
296		return nil, err
297	}
298
299	return op, c.notifyUpdated()
300}
301
302func (c *BugCache) Commit() error {
303	c.mu.Lock()
304	defer c.mu.Unlock()
305	err := c.bug.Commit(c.repoCache.repo)
306	if err != nil {
307		return err
308	}
309	return c.notifyUpdated()
310}
311
312func (c *BugCache) CommitAsNeeded() error {
313	c.mu.Lock()
314	defer c.mu.Unlock()
315	err := c.bug.CommitAsNeeded(c.repoCache.repo)
316	if err != nil {
317		return err
318	}
319	return c.notifyUpdated()
320}
321
322func (c *BugCache) NeedCommit() bool {
323	c.mu.RLock()
324	defer c.mu.RUnlock()
325	return c.bug.NeedCommit()
326}