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