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