1package cache
2
3import (
4 "io"
5 "strings"
6 "sync"
7
8 "github.com/pkg/errors"
9
10 "github.com/git-bug/git-bug/entities/identity"
11 "github.com/git-bug/git-bug/entity"
12 "github.com/git-bug/git-bug/repository"
13 "github.com/git-bug/git-bug/util/multierr"
14)
15
16// Name returns the registered name of this repository, or empty string for
17// the default (unnamed) repository.
18func (c *RepoCache) Name() string {
19 if c.name == defaultRepoName {
20 return ""
21 }
22 return c.name
23}
24
25// IsDefaultRepo reports whether this is an unnamed (single-repo) repository.
26func (c *RepoCache) IsDefaultRepo() bool {
27 return c.name == defaultRepoName
28}
29
30// BrowseRepo returns the underlying RepoBrowse implementation.
31func (c *RepoCache) BrowseRepo() repository.RepoBrowse {
32 return c.repo
33}
34
35// LocalConfig give access to the repository scoped configuration
36func (c *RepoCache) LocalConfig() repository.Config {
37 return c.repo.LocalConfig()
38}
39
40// GlobalConfig give access to the global scoped configuration
41func (c *RepoCache) GlobalConfig() repository.Config {
42 return c.repo.GlobalConfig()
43}
44
45// AnyConfig give access to a merged local/global configuration
46func (c *RepoCache) AnyConfig() repository.ConfigRead {
47 return c.repo.AnyConfig()
48}
49
50// Keyring give access to a user-wide storage for secrets
51func (c *RepoCache) Keyring() repository.Keyring {
52 return c.repo.Keyring()
53}
54
55// GetUserName returns the name the user has used to configure git
56func (c *RepoCache) GetUserName() (string, error) {
57 return c.repo.GetUserName()
58}
59
60// GetUserEmail returns the email address that the user has used to configure git.
61func (c *RepoCache) GetUserEmail() (string, error) {
62 return c.repo.GetUserEmail()
63}
64
65// GetCoreEditor returns the name of the editor that the user has used to configure git.
66func (c *RepoCache) GetCoreEditor() (string, error) {
67 return c.repo.GetCoreEditor()
68}
69
70// GetRemotes returns the configured remotes repositories.
71func (c *RepoCache) GetRemotes() (map[string]string, error) {
72 return c.repo.GetRemotes()
73}
74
75// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug
76func (c *RepoCache) LocalStorage() repository.LocalStorage {
77 return c.repo.LocalStorage()
78}
79
80// ReadData will attempt to read arbitrary data from the given hash
81func (c *RepoCache) ReadData(hash repository.Hash) ([]byte, error) {
82 return c.repo.ReadData(hash)
83}
84
85// BlobAtPath returns the raw content, byte size, and git object hash of the
86// file at the given path within the tree of the given ref.
87func (c *RepoCache) BlobAtPath(ref, path string) (io.ReadCloser, int64, repository.Hash, error) {
88 return c.repo.BlobAtPath(ref, path)
89}
90
91
92// StoreData will store arbitrary data and return the corresponding hash
93func (c *RepoCache) StoreData(data []byte) (repository.Hash, error) {
94 return c.repo.StoreData(data)
95}
96
97// Fetch retrieve updates from a remote
98// This does not change the local bugs or identities state
99func (c *RepoCache) Fetch(remote string) (string, error) {
100 prefixes := make([]string, len(c.subcaches))
101 for i, subcache := range c.subcaches {
102 prefixes[i] = subcache.GetNamespace()
103 }
104
105 // fetch everything at once, to have a single auth step if required.
106 return c.repo.FetchRefs(remote, prefixes...)
107}
108
109// RemoveAll deletes all entities from the cache and the disk.
110func (c *RepoCache) RemoveAll() error {
111 var errWait multierr.ErrWaitGroup
112 for _, mgmt := range c.subcaches {
113 errWait.Go(mgmt.RemoveAll)
114 }
115 return errWait.Wait()
116}
117
118// MergeAll will merge all the available remote bug and identities
119func (c *RepoCache) MergeAll(remote string) <-chan entity.MergeResult {
120 out := make(chan entity.MergeResult)
121
122 dependency := [][]cacheMgmt{
123 {c.identities},
124 {c.bugs},
125 }
126
127 // run MergeAll according to entities dependencies and merge the results
128 go func() {
129 defer close(out)
130
131 for _, subcaches := range dependency {
132 var wg sync.WaitGroup
133 for _, subcache := range subcaches {
134 wg.Add(1)
135 go func(subcache cacheMgmt) {
136 for res := range subcache.MergeAll(remote) {
137 out <- res
138 }
139 wg.Done()
140 }(subcache)
141 }
142 wg.Wait()
143 }
144 }()
145
146 return out
147}
148
149// Push update a remote with the local changes
150func (c *RepoCache) Push(remote string) (string, error) {
151 prefixes := make([]string, len(c.subcaches))
152 for i, subcache := range c.subcaches {
153 prefixes[i] = subcache.GetNamespace()
154 }
155
156 // push everything at once, to have a single auth step if required
157 return c.repo.PushRefs(remote, prefixes...)
158}
159
160// Pull will do a Fetch + MergeAll
161// This function will return an error if a merge fail
162func (c *RepoCache) Pull(remote string) error {
163 _, err := c.Fetch(remote)
164 if err != nil {
165 return err
166 }
167
168 for merge := range c.MergeAll(remote) {
169 if merge.Err != nil {
170 return merge.Err
171 }
172 if merge.Status == entity.MergeStatusInvalid {
173 return errors.Errorf("merge failure: %s", merge.Reason)
174 }
175 }
176
177 return nil
178}
179
180func (c *RepoCache) SetUserIdentity(i *IdentityCache) error {
181 c.muUserIdentity.RLock()
182 defer c.muUserIdentity.RUnlock()
183
184 // Make sure that everything is fine
185 if _, err := c.identities.Resolve(i.Id()); err != nil {
186 panic("SetUserIdentity while the identity is not from the cache, something is wrong")
187 }
188
189 err := identity.SetUserIdentity(c.repo, i.Identity)
190 if err != nil {
191 return err
192 }
193
194 c.userIdentityId = i.Id()
195
196 return nil
197}
198
199func (c *RepoCache) ClearUserIdentity() error {
200 c.muUserIdentity.Lock()
201 defer c.muUserIdentity.Unlock()
202
203 err := identity.ClearUserIdentity(c.repo)
204 if err != nil {
205 return err
206 }
207
208 c.userIdentityId = ""
209 return nil
210}
211
212func (c *RepoCache) GetUserIdentity() (*IdentityCache, error) {
213 c.muUserIdentity.RLock()
214 if c.userIdentityId != "" {
215 defer c.muUserIdentity.RUnlock()
216 return c.identities.Resolve(c.userIdentityId)
217 }
218 c.muUserIdentity.RUnlock()
219
220 c.muUserIdentity.Lock()
221 defer c.muUserIdentity.Unlock()
222
223 i, err := identity.GetUserIdentityId(c.repo)
224 if err != nil {
225 return nil, err
226 }
227
228 c.userIdentityId = i
229
230 return c.identities.Resolve(i)
231}
232
233func (c *RepoCache) GetUserIdentityExcerpt() (*IdentityExcerpt, error) {
234 c.muUserIdentity.RLock()
235 if c.userIdentityId != "" {
236 defer c.muUserIdentity.RUnlock()
237 return c.identities.ResolveExcerpt(c.userIdentityId)
238 }
239 c.muUserIdentity.RUnlock()
240
241 c.muUserIdentity.Lock()
242 defer c.muUserIdentity.Unlock()
243
244 i, err := identity.GetUserIdentityId(c.repo)
245 if err != nil {
246 return nil, err
247 }
248
249 c.userIdentityId = i
250
251 return c.identities.ResolveExcerpt(i)
252}
253
254func (c *RepoCache) IsUserIdentitySet() (bool, error) {
255 return identity.IsUserIdentitySet(c.repo)
256}
257
258// SyncLocalRefs updates the cache for each ref that was updated externally
259// (e.g. after a git push). Each ref is matched against the subcaches by
260// namespace and the corresponding entity is re-read from git.
261func (c *RepoCache) SyncLocalRefs(refs []string) error {
262 for _, ref := range refs {
263 id := entity.RefToId(ref)
264 for _, subcache := range c.subcaches {
265 ns := subcache.GetNamespace()
266 if strings.Contains(ref, "/"+ns+"/") {
267 if err := subcache.SyncLocalRef(id); err != nil {
268 return err
269 }
270 break
271 }
272 }
273 }
274 return nil
275}