repo_cache_common.go

  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}