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