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 "github.com/MichaelMure/git-bug/util"
11)
12
13type Cacher interface {
14 RegisterRepository(ref string, repo repository.Repo)
15 RegisterDefaultRepository(repo repository.Repo)
16
17 ResolveRepo(ref string) (RepoCacher, error)
18 DefaultRepo() (RepoCacher, error)
19}
20
21type RepoCacher interface {
22 Repository() repository.Repo
23 ResolveBug(id string) (BugCacher, error)
24 ResolveBugPrefix(prefix string) (BugCacher, error)
25 AllBugIds() ([]string, error)
26 ClearAllBugs()
27
28 // Mutations
29 NewBug(title string, message string) (BugCacher, error)
30 NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error)
31}
32
33type BugCacher interface {
34 Snapshot() *bug.Snapshot
35 ClearSnapshot()
36
37 // Mutations
38 AddComment(message string) error
39 AddCommentWithFiles(message string, files []util.Hash) error
40 ChangeLabels(added []string, removed []string) error
41 Open() error
42 Close() error
43 SetTitle(title string) error
44
45 Commit() error
46 CommitAsNeeded() 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
89// Repo ------------------------
90
91type RepoCache struct {
92 repo repository.Repo
93 bugs map[string]BugCacher
94}
95
96func NewRepoCache(r repository.Repo) RepoCacher {
97 return &RepoCache{
98 repo: r,
99 bugs: make(map[string]BugCacher),
100 }
101}
102
103func (c *RepoCache) Repository() repository.Repo {
104 return c.repo
105}
106
107func (c *RepoCache) ResolveBug(id string) (BugCacher, error) {
108 cached, ok := c.bugs[id]
109 if ok {
110 return cached, nil
111 }
112
113 b, err := bug.ReadLocalBug(c.repo, id)
114 if err != nil {
115 return nil, err
116 }
117
118 cached = NewBugCache(c.repo, b)
119 c.bugs[id] = cached
120
121 return cached, nil
122}
123
124func (c *RepoCache) ResolveBugPrefix(prefix string) (BugCacher, error) {
125 // preallocate but empty
126 matching := make([]string, 0, 5)
127
128 for id := range c.bugs {
129 if strings.HasPrefix(id, prefix) {
130 matching = append(matching, id)
131 }
132 }
133
134 // TODO: should check matching bug in the repo as well
135
136 if len(matching) > 1 {
137 return nil, fmt.Errorf("Multiple matching bug found:\n%s", strings.Join(matching, "\n"))
138 }
139
140 if len(matching) == 1 {
141 b := c.bugs[matching[0]]
142 return b, nil
143 }
144
145 b, err := bug.FindLocalBug(c.repo, prefix)
146
147 if err != nil {
148 return nil, err
149 }
150
151 cached := NewBugCache(c.repo, b)
152 c.bugs[b.Id()] = cached
153
154 return cached, nil
155}
156
157func (c *RepoCache) AllBugIds() ([]string, error) {
158 return bug.ListLocalIds(c.repo)
159}
160
161func (c *RepoCache) ClearAllBugs() {
162 c.bugs = make(map[string]BugCacher)
163}
164
165func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) {
166 return c.NewBugWithFiles(title, message, nil)
167}
168
169func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) {
170 author, err := bug.GetUser(c.repo)
171 if err != nil {
172 return nil, err
173 }
174
175 b, err := operations.CreateWithFiles(author, title, message, files)
176 if err != nil {
177 return nil, err
178 }
179
180 err = b.Commit(c.repo)
181 if err != nil {
182 return nil, err
183 }
184
185 cached := NewBugCache(c.repo, b)
186 c.bugs[b.Id()] = cached
187
188 return cached, nil
189}
190
191// Bug ------------------------
192
193type BugCache struct {
194 repo repository.Repo
195 bug *bug.Bug
196 snap *bug.Snapshot
197}
198
199func NewBugCache(repo repository.Repo, b *bug.Bug) BugCacher {
200 return &BugCache{
201 repo: repo,
202 bug: b,
203 }
204}
205
206func (c *BugCache) Snapshot() *bug.Snapshot {
207 if c.snap == nil {
208 snap := c.bug.Compile()
209 c.snap = &snap
210 }
211 return c.snap
212}
213
214func (c *BugCache) ClearSnapshot() {
215 c.snap = nil
216}
217
218func (c *BugCache) AddComment(message string) error {
219 return c.AddCommentWithFiles(message, nil)
220}
221
222func (c *BugCache) AddCommentWithFiles(message string, files []util.Hash) error {
223 author, err := bug.GetUser(c.repo)
224 if err != nil {
225 return err
226 }
227
228 operations.CommentWithFiles(c.bug, author, message, files)
229
230 // TODO: perf --> the snapshot could simply be updated with the new op
231 c.ClearSnapshot()
232
233 return nil
234}
235
236func (c *BugCache) ChangeLabels(added []string, removed []string) error {
237 author, err := bug.GetUser(c.repo)
238 if err != nil {
239 return err
240 }
241
242 err = operations.ChangeLabels(nil, c.bug, author, added, removed)
243 if err != nil {
244 return err
245 }
246
247 // TODO: perf --> the snapshot could simply be updated with the new op
248 c.ClearSnapshot()
249
250 return nil
251}
252
253func (c *BugCache) Open() error {
254 author, err := bug.GetUser(c.repo)
255 if err != nil {
256 return err
257 }
258
259 operations.Open(c.bug, author)
260
261 // TODO: perf --> the snapshot could simply be updated with the new op
262 c.ClearSnapshot()
263
264 return nil
265}
266
267func (c *BugCache) Close() error {
268 author, err := bug.GetUser(c.repo)
269 if err != nil {
270 return err
271 }
272
273 operations.Close(c.bug, author)
274
275 // TODO: perf --> the snapshot could simply be updated with the new op
276 c.ClearSnapshot()
277
278 return nil
279}
280
281func (c *BugCache) SetTitle(title string) error {
282 author, err := bug.GetUser(c.repo)
283 if err != nil {
284 return err
285 }
286
287 operations.SetTitle(c.bug, author, title)
288
289 // TODO: perf --> the snapshot could simply be updated with the new op
290 c.ClearSnapshot()
291
292 return nil
293}
294
295func (c *BugCache) Commit() error {
296 return c.bug.Commit(c.repo)
297}
298
299func (c *BugCache) CommitAsNeeded() error {
300 if c.bug.HasPendingOp() {
301 return c.bug.Commit(c.repo)
302 }
303 return nil
304}