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// 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}