1package cache
2
3import (
4 "bytes"
5 "encoding/gob"
6 "fmt"
7 "io"
8 "os"
9 "path"
10 "strings"
11
12 "github.com/MichaelMure/git-bug/bug"
13 "github.com/MichaelMure/git-bug/bug/operations"
14 "github.com/MichaelMure/git-bug/repository"
15 "github.com/MichaelMure/git-bug/util"
16)
17
18type RepoCache struct {
19 repo repository.Repo
20 excerpts map[string]BugExcerpt
21 bugs map[string]*BugCache
22}
23
24func NewRepoCache(r repository.Repo) (*RepoCache, error) {
25 c := &RepoCache{
26 repo: r,
27 bugs: make(map[string]*BugCache),
28 }
29
30 err := c.loadExcerpts()
31
32 if err == nil {
33 return c, nil
34 }
35
36 c.buildAllExcerpt()
37
38 return c, c.writeExcerpts()
39}
40
41func (c *RepoCache) Repository() repository.Repo {
42 return c.repo
43}
44
45// bugUpdated is a callback to trigger when the excerpt of a bug changed,
46// that is each time a bug is updated
47func (c *RepoCache) bugUpdated(id string) error {
48 b, ok := c.bugs[id]
49 if !ok {
50 panic("missing bug in the cache")
51 }
52
53 c.excerpts[id] = NewBugExcerpt(b.bug, b.Snapshot())
54
55 return c.writeExcerpts()
56}
57
58// loadExcerpts will try to read from the disk the bug excerpt file
59func (c *RepoCache) loadExcerpts() error {
60 excerptsPath := repoExcerptsFilePath(c.repo)
61
62 f, err := os.Open(excerptsPath)
63 if err != nil {
64 return err
65 }
66
67 decoder := gob.NewDecoder(f)
68
69 var excerpts map[string]BugExcerpt
70
71 err = decoder.Decode(&excerpts)
72 if err != nil {
73 return err
74 }
75
76 c.excerpts = excerpts
77 return nil
78}
79
80// writeExcerpts will serialize on disk the BugExcerpt array
81func (c *RepoCache) writeExcerpts() error {
82 var data bytes.Buffer
83
84 encoder := gob.NewEncoder(&data)
85
86 err := encoder.Encode(c.excerpts)
87 if err != nil {
88 return err
89 }
90
91 excerptsPath := repoExcerptsFilePath(c.repo)
92
93 f, err := os.Create(excerptsPath)
94 if err != nil {
95 return err
96 }
97
98 _, err = f.Write(data.Bytes())
99 if err != nil {
100 return err
101 }
102
103 return f.Close()
104}
105
106func repoExcerptsFilePath(repo repository.Repo) string {
107 return path.Join(repo.GetPath(), ".git", "git-bug", excerptsFile)
108}
109
110func (c *RepoCache) buildAllExcerpt() {
111 c.excerpts = make(map[string]BugExcerpt)
112
113 allBugs := bug.ReadAllLocalBugs(c.repo)
114
115 for b := range allBugs {
116 snap := b.Bug.Compile()
117 c.excerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, &snap)
118 }
119}
120
121func (c *RepoCache) ResolveBug(id string) (*BugCache, error) {
122 cached, ok := c.bugs[id]
123 if ok {
124 return cached, nil
125 }
126
127 b, err := bug.ReadLocalBug(c.repo, id)
128 if err != nil {
129 return nil, err
130 }
131
132 cached = NewBugCache(c, b)
133 c.bugs[id] = cached
134
135 return cached, nil
136}
137
138func (c *RepoCache) ResolveBugPrefix(prefix string) (*BugCache, error) {
139 // preallocate but empty
140 matching := make([]string, 0, 5)
141
142 for id := range c.bugs {
143 if strings.HasPrefix(id, prefix) {
144 matching = append(matching, id)
145 }
146 }
147
148 // TODO: should check matching bug in the repo as well
149
150 if len(matching) > 1 {
151 return nil, fmt.Errorf("Multiple matching bug found:\n%s", strings.Join(matching, "\n"))
152 }
153
154 if len(matching) == 1 {
155 b := c.bugs[matching[0]]
156 return b, nil
157 }
158
159 b, err := bug.FindLocalBug(c.repo, prefix)
160
161 if err != nil {
162 return nil, err
163 }
164
165 cached := NewBugCache(c, b)
166 c.bugs[b.Id()] = cached
167
168 return cached, nil
169}
170
171func (c *RepoCache) AllBugIds() ([]string, error) {
172 return bug.ListLocalIds(c.repo)
173}
174
175func (c *RepoCache) ClearAllBugs() {
176 c.bugs = make(map[string]*BugCache)
177}
178
179func (c *RepoCache) NewBug(title string, message string) (*BugCache, error) {
180 return c.NewBugWithFiles(title, message, nil)
181}
182
183func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.Hash) (*BugCache, error) {
184 author, err := bug.GetUser(c.repo)
185 if err != nil {
186 return nil, err
187 }
188
189 b, err := operations.CreateWithFiles(author, title, message, files)
190 if err != nil {
191 return nil, err
192 }
193
194 err = b.Commit(c.repo)
195 if err != nil {
196 return nil, err
197 }
198
199 cached := NewBugCache(c, b)
200 c.bugs[b.Id()] = cached
201
202 return cached, nil
203}
204
205func (c *RepoCache) Fetch(remote string) (string, error) {
206 return bug.Fetch(c.repo, remote)
207}
208
209func (c *RepoCache) MergeAll(remote string) <-chan bug.MergeResult {
210 return bug.MergeAll(c.repo, remote)
211}
212
213func (c *RepoCache) Pull(remote string, out io.Writer) error {
214 return bug.Pull(c.repo, out, remote)
215}
216
217func (c *RepoCache) Push(remote string) (string, error) {
218 return bug.Push(c.repo, remote)
219}