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