repo_cache_common.go

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