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
31 // Mutations
32 NewBug(title string, message string) (BugCacher, error)
33}
34
35type BugCacher interface {
36 Snapshot() *bug.Snapshot
37 ClearSnapshot()
38
39 // Mutations
40 AddComment(message string) (BugCacher, error)
41 ChangeLabels(added []string, removed []string) (BugCacher, error)
42 Open() (BugCacher, error)
43 Close() (BugCacher, error)
44 SetTitle(title string) (BugCacher, error)
45
46 Commit() (BugCacher, error)
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(c.repo, 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(c.repo, 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) NewBug(title string, message string) (BugCacher, error) {
186 author, err := bug.GetUser(c.repo)
187 if err != nil {
188 return nil, err
189 }
190
191 b, err := operations.Create(author, title, message)
192 if err != nil {
193 return nil, err
194 }
195
196 err = b.Commit(c.repo)
197 if err != nil {
198 return nil, err
199 }
200
201 cached := NewBugCache(c.repo, b)
202 c.bugs[b.Id()] = cached
203
204 return cached, nil
205}
206
207// Bug ------------------------
208
209type BugCache struct {
210 repo repository.Repo
211 bug *bug.Bug
212 snap *bug.Snapshot
213}
214
215func NewBugCache(repo repository.Repo, b *bug.Bug) BugCacher {
216 return &BugCache{
217 repo: repo,
218 bug: b,
219 }
220}
221
222func (c *BugCache) Snapshot() *bug.Snapshot {
223 if c.snap == nil {
224 snap := c.bug.Compile()
225 c.snap = &snap
226 }
227 return c.snap
228}
229
230func (c *BugCache) ClearSnapshot() {
231 c.snap = nil
232}
233
234func (c *BugCache) AddComment(message string) (BugCacher, error) {
235 author, err := bug.GetUser(c.repo)
236 if err != nil {
237 return nil, err
238 }
239
240 operations.Comment(c.bug, author, message)
241
242 // TODO: perf --> the snapshot could simply be updated with the new op
243 c.ClearSnapshot()
244
245 return c, nil
246}
247
248func (c *BugCache) ChangeLabels(added []string, removed []string) (BugCacher, error) {
249 author, err := bug.GetUser(c.repo)
250 if err != nil {
251 return nil, err
252 }
253
254 err = operations.ChangeLabels(nil, c.bug, author, added, removed)
255 if err != nil {
256 return nil, err
257 }
258
259 // TODO: perf --> the snapshot could simply be updated with the new op
260 c.ClearSnapshot()
261
262 return c, nil
263}
264
265func (c *BugCache) Open() (BugCacher, error) {
266 author, err := bug.GetUser(c.repo)
267 if err != nil {
268 return nil, err
269 }
270
271 operations.Open(c.bug, author)
272
273 // TODO: perf --> the snapshot could simply be updated with the new op
274 c.ClearSnapshot()
275
276 return c, nil
277}
278
279func (c *BugCache) Close() (BugCacher, error) {
280 author, err := bug.GetUser(c.repo)
281 if err != nil {
282 return nil, err
283 }
284
285 operations.Close(c.bug, author)
286
287 // TODO: perf --> the snapshot could simply be updated with the new op
288 c.ClearSnapshot()
289
290 return c, nil
291}
292
293func (c *BugCache) SetTitle(title string) (BugCacher, error) {
294 author, err := bug.GetUser(c.repo)
295 if err != nil {
296 return nil, err
297 }
298
299 operations.SetTitle(c.bug, author, title)
300
301 // TODO: perf --> the snapshot could simply be updated with the new op
302 c.ClearSnapshot()
303
304 return c, nil
305}
306
307func (c *BugCache) Commit() (BugCacher, error) {
308 err := c.bug.Commit(c.repo)
309 if err != nil {
310 return nil, err
311 }
312 return c, nil
313}