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