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