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}
47
48// Cacher ------------------------
49
50type RootCache struct {
51 repos map[string]RepoCacher
52}
53
54func NewCache() RootCache {
55 return RootCache{
56 repos: make(map[string]RepoCacher),
57 }
58}
59
60func (c *RootCache) RegisterRepository(ref string, repo repository.Repo) {
61 c.repos[ref] = NewRepoCache(repo)
62}
63
64func (c *RootCache) RegisterDefaultRepository(repo repository.Repo) {
65 c.repos[""] = NewRepoCache(repo)
66}
67
68func (c *RootCache) DefaultRepo() (RepoCacher, error) {
69 if len(c.repos) != 1 {
70 return nil, fmt.Errorf("repository is not unique")
71 }
72
73 for _, r := range c.repos {
74 return r, nil
75 }
76
77 panic("unreachable")
78}
79
80func (c *RootCache) ResolveRepo(ref string) (RepoCacher, error) {
81 r, ok := c.repos[ref]
82 if !ok {
83 return nil, fmt.Errorf("unknown repo")
84 }
85 return r, nil
86}
87
88// Repo ------------------------
89
90type RepoCache struct {
91 repo repository.Repo
92 bugs map[string]BugCacher
93}
94
95func NewRepoCache(r repository.Repo) RepoCacher {
96 return &RepoCache{
97 repo: r,
98 bugs: make(map[string]BugCacher),
99 }
100}
101
102func (c *RepoCache) Repository() repository.Repo {
103 return c.repo
104}
105
106func (c *RepoCache) ResolveBug(id string) (BugCacher, error) {
107 cached, ok := c.bugs[id]
108 if ok {
109 return cached, nil
110 }
111
112 b, err := bug.ReadLocalBug(c.repo, id)
113 if err != nil {
114 return nil, err
115 }
116
117 cached = NewBugCache(c.repo, b)
118 c.bugs[id] = cached
119
120 return cached, nil
121}
122
123func (c *RepoCache) ResolveBugPrefix(prefix string) (BugCacher, error) {
124 // preallocate but empty
125 matching := make([]string, 0, 5)
126
127 for id := range c.bugs {
128 if strings.HasPrefix(id, prefix) {
129 matching = append(matching, id)
130 }
131 }
132
133 // TODO: should check matching bug in the repo as well
134
135 if len(matching) > 1 {
136 return nil, fmt.Errorf("Multiple matching bug found:\n%s", strings.Join(matching, "\n"))
137 }
138
139 if len(matching) == 1 {
140 b := c.bugs[matching[0]]
141 return b, nil
142 }
143
144 b, err := bug.FindLocalBug(c.repo, prefix)
145
146 if err != nil {
147 return nil, err
148 }
149
150 cached := NewBugCache(c.repo, b)
151 c.bugs[b.Id()] = cached
152
153 return cached, nil
154}
155
156func (c *RepoCache) AllBugIds() ([]string, error) {
157 return bug.ListLocalIds(c.repo)
158}
159
160func (c *RepoCache) ClearAllBugs() {
161 c.bugs = make(map[string]BugCacher)
162}
163
164func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) {
165 return c.NewBugWithFiles(title, message, nil)
166}
167
168func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) {
169 author, err := bug.GetUser(c.repo)
170 if err != nil {
171 return nil, err
172 }
173
174 b, err := operations.CreateWithFiles(author, title, message, files)
175 if err != nil {
176 return nil, err
177 }
178
179 err = b.Commit(c.repo)
180 if err != nil {
181 return nil, err
182 }
183
184 cached := NewBugCache(c.repo, b)
185 c.bugs[b.Id()] = cached
186
187 return cached, nil
188}
189
190// Bug ------------------------
191
192type BugCache struct {
193 repo repository.Repo
194 bug *bug.Bug
195 snap *bug.Snapshot
196}
197
198func NewBugCache(repo repository.Repo, b *bug.Bug) BugCacher {
199 return &BugCache{
200 repo: repo,
201 bug: b,
202 }
203}
204
205func (c *BugCache) Snapshot() *bug.Snapshot {
206 if c.snap == nil {
207 snap := c.bug.Compile()
208 c.snap = &snap
209 }
210 return c.snap
211}
212
213func (c *BugCache) ClearSnapshot() {
214 c.snap = nil
215}
216
217func (c *BugCache) AddComment(message string) error {
218 return c.AddCommentWithFiles(message, nil)
219}
220
221func (c *BugCache) AddCommentWithFiles(message string, files []util.Hash) error {
222 author, err := bug.GetUser(c.repo)
223 if err != nil {
224 return err
225 }
226
227 operations.CommentWithFiles(c.bug, author, message, files)
228
229 // TODO: perf --> the snapshot could simply be updated with the new op
230 c.ClearSnapshot()
231
232 return nil
233}
234
235func (c *BugCache) ChangeLabels(added []string, removed []string) error {
236 author, err := bug.GetUser(c.repo)
237 if err != nil {
238 return err
239 }
240
241 err = operations.ChangeLabels(nil, c.bug, author, added, removed)
242 if err != nil {
243 return err
244 }
245
246 // TODO: perf --> the snapshot could simply be updated with the new op
247 c.ClearSnapshot()
248
249 return nil
250}
251
252func (c *BugCache) Open() error {
253 author, err := bug.GetUser(c.repo)
254 if err != nil {
255 return err
256 }
257
258 operations.Open(c.bug, author)
259
260 // TODO: perf --> the snapshot could simply be updated with the new op
261 c.ClearSnapshot()
262
263 return nil
264}
265
266func (c *BugCache) Close() error {
267 author, err := bug.GetUser(c.repo)
268 if err != nil {
269 return err
270 }
271
272 operations.Close(c.bug, author)
273
274 // TODO: perf --> the snapshot could simply be updated with the new op
275 c.ClearSnapshot()
276
277 return nil
278}
279
280func (c *BugCache) SetTitle(title string) error {
281 author, err := bug.GetUser(c.repo)
282 if err != nil {
283 return err
284 }
285
286 operations.SetTitle(c.bug, author, title)
287
288 // TODO: perf --> the snapshot could simply be updated with the new op
289 c.ClearSnapshot()
290
291 return nil
292}
293
294func (c *BugCache) Commit() error {
295 return c.bug.Commit(c.repo)
296}