repo_cache.go

  1package cache
  2
  3import (
  4	"bytes"
  5	"encoding/gob"
  6	"fmt"
  7	"io"
  8	"io/ioutil"
  9	"os"
 10	"path"
 11	"sort"
 12	"strconv"
 13	"time"
 14
 15	"github.com/pkg/errors"
 16
 17	"github.com/MichaelMure/git-bug/bug"
 18	"github.com/MichaelMure/git-bug/entity"
 19	"github.com/MichaelMure/git-bug/identity"
 20	"github.com/MichaelMure/git-bug/repository"
 21	"github.com/MichaelMure/git-bug/util/git"
 22	"github.com/MichaelMure/git-bug/util/process"
 23)
 24
 25const bugCacheFile = "bug-cache"
 26const identityCacheFile = "identity-cache"
 27
 28// 1: original format
 29// 2: added cache for identities with a reference in the bug cache
 30const formatVersion = 2
 31
 32type ErrInvalidCacheFormat struct {
 33	message string
 34}
 35
 36func (e ErrInvalidCacheFormat) Error() string {
 37	return e.message
 38}
 39
 40var _ repository.RepoCommon = &RepoCache{}
 41
 42// RepoCache is a cache for a Repository. This cache has multiple functions:
 43//
 44// 1. After being loaded, a Bug is kept in memory in the cache, allowing for fast
 45// 		access later.
 46// 2. The cache maintain in memory and on disk a pre-digested excerpt for each bug,
 47// 		allowing for fast querying the whole set of bugs without having to load
 48//		them individually.
 49// 3. The cache guarantee that a single instance of a Bug is loaded at once, avoiding
 50// 		loss of data that we could have with multiple copies in the same process.
 51// 4. The same way, the cache maintain in memory a single copy of the loaded identities.
 52//
 53// The cache also protect the on-disk data by locking the git repository for its
 54// own usage, by writing a lock file. Of course, normal git operations are not
 55// affected, only git-bug related one.
 56type RepoCache struct {
 57	// the underlying repo
 58	repo repository.ClockedRepo
 59
 60	// excerpt of bugs data for all bugs
 61	bugExcerpts map[entity.Id]*BugExcerpt
 62	// bug loaded in memory
 63	bugs map[entity.Id]*BugCache
 64
 65	// excerpt of identities data for all identities
 66	identitiesExcerpts map[entity.Id]*IdentityExcerpt
 67	// identities loaded in memory
 68	identities map[entity.Id]*IdentityCache
 69
 70	// the user identity's id, if known
 71	userIdentityId entity.Id
 72}
 73
 74func NewRepoCache(r repository.ClockedRepo) (*RepoCache, error) {
 75	c := &RepoCache{
 76		repo:       r,
 77		bugs:       make(map[entity.Id]*BugCache),
 78		identities: make(map[entity.Id]*IdentityCache),
 79	}
 80
 81	err := c.lock()
 82	if err != nil {
 83		return &RepoCache{}, err
 84	}
 85
 86	err = c.load()
 87	if err == nil {
 88		return c, nil
 89	}
 90	if _, ok := err.(ErrInvalidCacheFormat); ok {
 91		return nil, err
 92	}
 93
 94	err = c.buildCache()
 95	if err != nil {
 96		return nil, err
 97	}
 98
 99	return c, c.write()
100}
101
102// GetPath returns the path to the repo.
103func (c *RepoCache) GetPath() string {
104	return c.repo.GetPath()
105}
106
107// GetCoreEditor returns the name of the editor that the user has used to configure git.
108func (c *RepoCache) GetCoreEditor() (string, error) {
109	return c.repo.GetCoreEditor()
110}
111
112// GetRemotes returns the configured remotes repositories.
113func (c *RepoCache) GetRemotes() (map[string]string, error) {
114	return c.repo.GetRemotes()
115}
116
117// GetUserName returns the name the the user has used to configure git
118func (c *RepoCache) GetUserName() (string, error) {
119	return c.repo.GetUserName()
120}
121
122// GetUserEmail returns the email address that the user has used to configure git.
123func (c *RepoCache) GetUserEmail() (string, error) {
124	return c.repo.GetUserEmail()
125}
126
127// StoreConfig store a single key/value pair in the config of the repo
128func (c *RepoCache) StoreConfig(key string, value string) error {
129	return c.repo.StoreConfig(key, value)
130}
131
132// ReadConfigs read all key/value pair matching the key prefix
133func (c *RepoCache) ReadConfigs(keyPrefix string) (map[string]string, error) {
134	return c.repo.ReadConfigs(keyPrefix)
135}
136
137// ReadConfigBool read a single boolean value from the config
138func (c *RepoCache) ReadConfigBool(key string) (bool, error) {
139	return c.repo.ReadConfigBool(key)
140}
141
142// ReadConfigBool read a single string value from the config
143func (c *RepoCache) ReadConfigString(key string) (string, error) {
144	return c.repo.ReadConfigString(key)
145}
146
147// RmConfigs remove all key/value pair matching the key prefix
148func (c *RepoCache) RmConfigs(keyPrefix string) error {
149	return c.repo.RmConfigs(keyPrefix)
150}
151
152func (c *RepoCache) lock() error {
153	lockPath := repoLockFilePath(c.repo)
154
155	err := repoIsAvailable(c.repo)
156	if err != nil {
157		return err
158	}
159
160	f, err := os.Create(lockPath)
161	if err != nil {
162		return err
163	}
164
165	pid := fmt.Sprintf("%d", os.Getpid())
166	_, err = f.WriteString(pid)
167	if err != nil {
168		return err
169	}
170
171	return f.Close()
172}
173
174func (c *RepoCache) Close() error {
175	for id := range c.identities {
176		delete(c.identities, id)
177	}
178	for id := range c.identitiesExcerpts {
179		delete(c.identitiesExcerpts, id)
180	}
181	for id := range c.bugs {
182		delete(c.bugs, id)
183	}
184	for id := range c.bugExcerpts {
185		delete(c.bugExcerpts, id)
186	}
187
188	lockPath := repoLockFilePath(c.repo)
189	return os.Remove(lockPath)
190}
191
192// bugUpdated is a callback to trigger when the excerpt of a bug changed,
193// that is each time a bug is updated
194func (c *RepoCache) bugUpdated(id entity.Id) error {
195	b, ok := c.bugs[id]
196	if !ok {
197		panic("missing bug in the cache")
198	}
199
200	c.bugExcerpts[id] = NewBugExcerpt(b.bug, b.Snapshot())
201
202	// we only need to write the bug cache
203	return c.writeBugCache()
204}
205
206// identityUpdated is a callback to trigger when the excerpt of an identity
207// changed, that is each time an identity is updated
208func (c *RepoCache) identityUpdated(id entity.Id) error {
209	i, ok := c.identities[id]
210	if !ok {
211		panic("missing identity in the cache")
212	}
213
214	c.identitiesExcerpts[id] = NewIdentityExcerpt(i.Identity)
215
216	// we only need to write the identity cache
217	return c.writeIdentityCache()
218}
219
220// load will try to read from the disk all the cache files
221func (c *RepoCache) load() error {
222	err := c.loadBugCache()
223	if err != nil {
224		return err
225	}
226	return c.loadIdentityCache()
227}
228
229// load will try to read from the disk the bug cache file
230func (c *RepoCache) loadBugCache() error {
231	f, err := os.Open(bugCacheFilePath(c.repo))
232	if err != nil {
233		return err
234	}
235
236	decoder := gob.NewDecoder(f)
237
238	aux := struct {
239		Version  uint
240		Excerpts map[entity.Id]*BugExcerpt
241	}{}
242
243	err = decoder.Decode(&aux)
244	if err != nil {
245		return err
246	}
247
248	if aux.Version != 2 {
249		return ErrInvalidCacheFormat{
250			message: fmt.Sprintf("unknown cache format version %v", aux.Version),
251		}
252	}
253
254	c.bugExcerpts = aux.Excerpts
255	return nil
256}
257
258// load will try to read from the disk the identity cache file
259func (c *RepoCache) loadIdentityCache() error {
260	f, err := os.Open(identityCacheFilePath(c.repo))
261	if err != nil {
262		return err
263	}
264
265	decoder := gob.NewDecoder(f)
266
267	aux := struct {
268		Version  uint
269		Excerpts map[entity.Id]*IdentityExcerpt
270	}{}
271
272	err = decoder.Decode(&aux)
273	if err != nil {
274		return err
275	}
276
277	if aux.Version != 2 {
278		return ErrInvalidCacheFormat{
279			message: fmt.Sprintf("unknown cache format version %v", aux.Version),
280		}
281	}
282
283	c.identitiesExcerpts = aux.Excerpts
284	return nil
285}
286
287// write will serialize on disk all the cache files
288func (c *RepoCache) write() error {
289	err := c.writeBugCache()
290	if err != nil {
291		return err
292	}
293	return c.writeIdentityCache()
294}
295
296// write will serialize on disk the bug cache file
297func (c *RepoCache) writeBugCache() error {
298	var data bytes.Buffer
299
300	aux := struct {
301		Version  uint
302		Excerpts map[entity.Id]*BugExcerpt
303	}{
304		Version:  formatVersion,
305		Excerpts: c.bugExcerpts,
306	}
307
308	encoder := gob.NewEncoder(&data)
309
310	err := encoder.Encode(aux)
311	if err != nil {
312		return err
313	}
314
315	f, err := os.Create(bugCacheFilePath(c.repo))
316	if err != nil {
317		return err
318	}
319
320	_, err = f.Write(data.Bytes())
321	if err != nil {
322		return err
323	}
324
325	return f.Close()
326}
327
328// write will serialize on disk the identity cache file
329func (c *RepoCache) writeIdentityCache() error {
330	var data bytes.Buffer
331
332	aux := struct {
333		Version  uint
334		Excerpts map[entity.Id]*IdentityExcerpt
335	}{
336		Version:  formatVersion,
337		Excerpts: c.identitiesExcerpts,
338	}
339
340	encoder := gob.NewEncoder(&data)
341
342	err := encoder.Encode(aux)
343	if err != nil {
344		return err
345	}
346
347	f, err := os.Create(identityCacheFilePath(c.repo))
348	if err != nil {
349		return err
350	}
351
352	_, err = f.Write(data.Bytes())
353	if err != nil {
354		return err
355	}
356
357	return f.Close()
358}
359
360func bugCacheFilePath(repo repository.Repo) string {
361	return path.Join(repo.GetPath(), ".git", "git-bug", bugCacheFile)
362}
363
364func identityCacheFilePath(repo repository.Repo) string {
365	return path.Join(repo.GetPath(), ".git", "git-bug", identityCacheFile)
366}
367
368func (c *RepoCache) buildCache() error {
369	_, _ = fmt.Fprintf(os.Stderr, "Building identity cache... ")
370
371	c.identitiesExcerpts = make(map[entity.Id]*IdentityExcerpt)
372
373	allIdentities := identity.ReadAllLocalIdentities(c.repo)
374
375	for i := range allIdentities {
376		if i.Err != nil {
377			return i.Err
378		}
379
380		c.identitiesExcerpts[i.Identity.Id()] = NewIdentityExcerpt(i.Identity)
381	}
382
383	_, _ = fmt.Fprintln(os.Stderr, "Done.")
384
385	_, _ = fmt.Fprintf(os.Stderr, "Building bug cache... ")
386
387	c.bugExcerpts = make(map[entity.Id]*BugExcerpt)
388
389	allBugs := bug.ReadAllLocalBugs(c.repo)
390
391	for b := range allBugs {
392		if b.Err != nil {
393			return b.Err
394		}
395
396		snap := b.Bug.Compile()
397		c.bugExcerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, &snap)
398	}
399
400	_, _ = fmt.Fprintln(os.Stderr, "Done.")
401	return nil
402}
403
404// ResolveBug retrieve a bug matching the exact given id
405func (c *RepoCache) ResolveBug(id entity.Id) (*BugCache, error) {
406	cached, ok := c.bugs[id]
407	if ok {
408		return cached, nil
409	}
410
411	b, err := bug.ReadLocalBug(c.repo, id)
412	if err != nil {
413		return nil, err
414	}
415
416	cached = NewBugCache(c, b)
417	c.bugs[id] = cached
418
419	return cached, nil
420}
421
422// ResolveBugExcerpt retrieve a BugExcerpt matching the exact given id
423func (c *RepoCache) ResolveBugExcerpt(id entity.Id) (*BugExcerpt, error) {
424	e, ok := c.bugExcerpts[id]
425	if !ok {
426		return nil, bug.ErrBugNotExist
427	}
428
429	return e, nil
430}
431
432// ResolveBugPrefix retrieve a bug matching an id prefix. It fails if multiple
433// bugs match.
434func (c *RepoCache) ResolveBugPrefix(prefix string) (*BugCache, error) {
435	// preallocate but empty
436	matching := make([]entity.Id, 0, 5)
437
438	for id := range c.bugExcerpts {
439		if id.HasPrefix(prefix) {
440			matching = append(matching, id)
441		}
442	}
443
444	if len(matching) > 1 {
445		return nil, bug.NewErrMultipleMatchBug(matching)
446	}
447
448	if len(matching) == 0 {
449		return nil, bug.ErrBugNotExist
450	}
451
452	return c.ResolveBug(matching[0])
453}
454
455// ResolveBugCreateMetadata retrieve a bug that has the exact given metadata on
456// its Create operation, that is, the first operation. It fails if multiple bugs
457// match.
458func (c *RepoCache) ResolveBugCreateMetadata(key string, value string) (*BugCache, error) {
459	// preallocate but empty
460	matching := make([]entity.Id, 0, 5)
461
462	for id, excerpt := range c.bugExcerpts {
463		if excerpt.CreateMetadata[key] == value {
464			matching = append(matching, id)
465		}
466	}
467
468	if len(matching) > 1 {
469		return nil, bug.NewErrMultipleMatchBug(matching)
470	}
471
472	if len(matching) == 0 {
473		return nil, bug.ErrBugNotExist
474	}
475
476	return c.ResolveBug(matching[0])
477}
478
479// QueryBugs return the id of all Bug matching the given Query
480func (c *RepoCache) QueryBugs(query *Query) []entity.Id {
481	if query == nil {
482		return c.AllBugsIds()
483	}
484
485	var filtered []*BugExcerpt
486
487	for _, excerpt := range c.bugExcerpts {
488		if query.Match(c, excerpt) {
489			filtered = append(filtered, excerpt)
490		}
491	}
492
493	var sorter sort.Interface
494
495	switch query.OrderBy {
496	case OrderById:
497		sorter = BugsById(filtered)
498	case OrderByCreation:
499		sorter = BugsByCreationTime(filtered)
500	case OrderByEdit:
501		sorter = BugsByEditTime(filtered)
502	default:
503		panic("missing sort type")
504	}
505
506	if query.OrderDirection == OrderDescending {
507		sorter = sort.Reverse(sorter)
508	}
509
510	sort.Sort(sorter)
511
512	result := make([]entity.Id, len(filtered))
513
514	for i, val := range filtered {
515		result[i] = val.Id
516	}
517
518	return result
519}
520
521// AllBugsIds return all known bug ids
522func (c *RepoCache) AllBugsIds() []entity.Id {
523	result := make([]entity.Id, len(c.bugExcerpts))
524
525	i := 0
526	for _, excerpt := range c.bugExcerpts {
527		result[i] = excerpt.Id
528		i++
529	}
530
531	return result
532}
533
534// ValidLabels list valid labels
535//
536// Note: in the future, a proper label policy could be implemented where valid
537// labels are defined in a configuration file. Until that, the default behavior
538// is to return the list of labels already used.
539func (c *RepoCache) ValidLabels() []bug.Label {
540	set := map[bug.Label]interface{}{}
541
542	for _, excerpt := range c.bugExcerpts {
543		for _, l := range excerpt.Labels {
544			set[l] = nil
545		}
546	}
547
548	result := make([]bug.Label, len(set))
549
550	i := 0
551	for l := range set {
552		result[i] = l
553		i++
554	}
555
556	// Sort
557	sort.Slice(result, func(i, j int) bool {
558		return string(result[i]) < string(result[j])
559	})
560
561	return result
562}
563
564// NewBug create a new bug
565// The new bug is written in the repository (commit)
566func (c *RepoCache) NewBug(title string, message string) (*BugCache, *bug.CreateOperation, error) {
567	return c.NewBugWithFiles(title, message, nil)
568}
569
570// NewBugWithFiles create a new bug with attached files for the message
571// The new bug is written in the repository (commit)
572func (c *RepoCache) NewBugWithFiles(title string, message string, files []git.Hash) (*BugCache, *bug.CreateOperation, error) {
573	author, err := c.GetUserIdentity()
574	if err != nil {
575		return nil, nil, err
576	}
577
578	return c.NewBugRaw(author, time.Now().Unix(), title, message, files, nil)
579}
580
581// NewBugWithFilesMeta create a new bug with attached files for the message, as
582// well as metadata for the Create operation.
583// The new bug is written in the repository (commit)
584func (c *RepoCache) NewBugRaw(author *IdentityCache, unixTime int64, title string, message string, files []git.Hash, metadata map[string]string) (*BugCache, *bug.CreateOperation, error) {
585	b, op, err := bug.CreateWithFiles(author.Identity, unixTime, title, message, files)
586	if err != nil {
587		return nil, nil, err
588	}
589
590	for key, value := range metadata {
591		op.SetMetadata(key, value)
592	}
593
594	err = b.Commit(c.repo)
595	if err != nil {
596		return nil, nil, err
597	}
598
599	if _, has := c.bugs[b.Id()]; has {
600		return nil, nil, fmt.Errorf("bug %s already exist in the cache", b.Id())
601	}
602
603	cached := NewBugCache(c, b)
604	c.bugs[b.Id()] = cached
605
606	// force the write of the excerpt
607	err = c.bugUpdated(b.Id())
608	if err != nil {
609		return nil, nil, err
610	}
611
612	return cached, op, nil
613}
614
615// Fetch retrieve updates from a remote
616// This does not change the local bugs or identities state
617func (c *RepoCache) Fetch(remote string) (string, error) {
618	stdout1, err := identity.Fetch(c.repo, remote)
619	if err != nil {
620		return stdout1, err
621	}
622
623	stdout2, err := bug.Fetch(c.repo, remote)
624	if err != nil {
625		return stdout2, err
626	}
627
628	return stdout1 + stdout2, nil
629}
630
631// MergeAll will merge all the available remote bug and identities
632func (c *RepoCache) MergeAll(remote string) <-chan entity.MergeResult {
633	out := make(chan entity.MergeResult)
634
635	// Intercept merge results to update the cache properly
636	go func() {
637		defer close(out)
638
639		results := identity.MergeAll(c.repo, remote)
640		for result := range results {
641			out <- result
642
643			if result.Err != nil {
644				continue
645			}
646
647			switch result.Status {
648			case entity.MergeStatusNew, entity.MergeStatusUpdated:
649				i := result.Entity.(*identity.Identity)
650				c.identitiesExcerpts[result.Id] = NewIdentityExcerpt(i)
651			}
652		}
653
654		results = bug.MergeAll(c.repo, remote)
655		for result := range results {
656			out <- result
657
658			if result.Err != nil {
659				continue
660			}
661
662			switch result.Status {
663			case entity.MergeStatusNew, entity.MergeStatusUpdated:
664				b := result.Entity.(*bug.Bug)
665				snap := b.Compile()
666				c.bugExcerpts[result.Id] = NewBugExcerpt(b, &snap)
667			}
668		}
669
670		err := c.write()
671
672		// No easy way out here ..
673		if err != nil {
674			panic(err)
675		}
676	}()
677
678	return out
679}
680
681// Push update a remote with the local changes
682func (c *RepoCache) Push(remote string) (string, error) {
683	stdout1, err := identity.Push(c.repo, remote)
684	if err != nil {
685		return stdout1, err
686	}
687
688	stdout2, err := bug.Push(c.repo, remote)
689	if err != nil {
690		return stdout2, err
691	}
692
693	return stdout1 + stdout2, nil
694}
695
696// Pull will do a Fetch + MergeAll
697// This function will return an error if a merge fail
698func (c *RepoCache) Pull(remote string) error {
699	_, err := c.Fetch(remote)
700	if err != nil {
701		return err
702	}
703
704	for merge := range c.MergeAll(remote) {
705		if merge.Err != nil {
706			return merge.Err
707		}
708		if merge.Status == entity.MergeStatusInvalid {
709			return errors.Errorf("merge failure: %s", merge.Reason)
710		}
711	}
712
713	return nil
714}
715
716func repoLockFilePath(repo repository.Repo) string {
717	return path.Join(repo.GetPath(), ".git", "git-bug", lockfile)
718}
719
720// repoIsAvailable check is the given repository is locked by a Cache.
721// Note: this is a smart function that will cleanup the lock file if the
722// corresponding process is not there anymore.
723// If no error is returned, the repo is free to edit.
724func repoIsAvailable(repo repository.Repo) error {
725	lockPath := repoLockFilePath(repo)
726
727	// Todo: this leave way for a racey access to the repo between the test
728	// if the file exist and the actual write. It's probably not a problem in
729	// practice because using a repository will be done from user interaction
730	// or in a context where a single instance of git-bug is already guaranteed
731	// (say, a server with the web UI running). But still, that might be nice to
732	// have a mutex or something to guard that.
733
734	// Todo: this will fail if somehow the filesystem is shared with another
735	// computer. Should add a configuration that prevent the cleaning of the
736	// lock file
737
738	f, err := os.Open(lockPath)
739
740	if err != nil && !os.IsNotExist(err) {
741		return err
742	}
743
744	if err == nil {
745		// lock file already exist
746		buf, err := ioutil.ReadAll(io.LimitReader(f, 10))
747		if err != nil {
748			return err
749		}
750		if len(buf) == 10 {
751			return fmt.Errorf("the lock file should be < 10 bytes")
752		}
753
754		pid, err := strconv.Atoi(string(buf))
755		if err != nil {
756			return err
757		}
758
759		if process.IsRunning(pid) {
760			return fmt.Errorf("the repository you want to access is already locked by the process pid %d", pid)
761		}
762
763		// The lock file is just laying there after a crash, clean it
764
765		fmt.Println("A lock file is present but the corresponding process is not, removing it.")
766		err = f.Close()
767		if err != nil {
768			return err
769		}
770
771		err = os.Remove(lockPath)
772		if err != nil {
773			return err
774		}
775	}
776
777	return nil
778}
779
780// ResolveIdentity retrieve an identity matching the exact given id
781func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) {
782	cached, ok := c.identities[id]
783	if ok {
784		return cached, nil
785	}
786
787	i, err := identity.ReadLocal(c.repo, id)
788	if err != nil {
789		return nil, err
790	}
791
792	cached = NewIdentityCache(c, i)
793	c.identities[id] = cached
794
795	return cached, nil
796}
797
798// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id
799func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) {
800	e, ok := c.identitiesExcerpts[id]
801	if !ok {
802		return nil, identity.ErrIdentityNotExist
803	}
804
805	return e, nil
806}
807
808// ResolveIdentityPrefix retrieve an Identity matching an id prefix.
809// It fails if multiple identities match.
810func (c *RepoCache) ResolveIdentityPrefix(prefix string) (*IdentityCache, error) {
811	// preallocate but empty
812	matching := make([]entity.Id, 0, 5)
813
814	for id := range c.identitiesExcerpts {
815		if id.HasPrefix(prefix) {
816			matching = append(matching, id)
817		}
818	}
819
820	if len(matching) > 1 {
821		return nil, identity.NewErrMultipleMatch(matching)
822	}
823
824	if len(matching) == 0 {
825		return nil, identity.ErrIdentityNotExist
826	}
827
828	return c.ResolveIdentity(matching[0])
829}
830
831// ResolveIdentityImmutableMetadata retrieve an Identity that has the exact given metadata on
832// one of it's version. If multiple version have the same key, the first defined take precedence.
833func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) (*IdentityCache, error) {
834	// preallocate but empty
835	matching := make([]entity.Id, 0, 5)
836
837	for id, i := range c.identitiesExcerpts {
838		if i.ImmutableMetadata[key] == value {
839			matching = append(matching, id)
840		}
841	}
842
843	if len(matching) > 1 {
844		return nil, identity.NewErrMultipleMatch(matching)
845	}
846
847	if len(matching) == 0 {
848		return nil, identity.ErrIdentityNotExist
849	}
850
851	return c.ResolveIdentity(matching[0])
852}
853
854// AllIdentityIds return all known identity ids
855func (c *RepoCache) AllIdentityIds() []entity.Id {
856	result := make([]entity.Id, len(c.identitiesExcerpts))
857
858	i := 0
859	for _, excerpt := range c.identitiesExcerpts {
860		result[i] = excerpt.Id
861		i++
862	}
863
864	return result
865}
866
867func (c *RepoCache) SetUserIdentity(i *IdentityCache) error {
868	err := identity.SetUserIdentity(c.repo, i.Identity)
869	if err != nil {
870		return err
871	}
872
873	// Make sure that everything is fine
874	if _, ok := c.identities[i.Id()]; !ok {
875		panic("SetUserIdentity while the identity is not from the cache, something is wrong")
876	}
877
878	c.userIdentityId = i.Id()
879
880	return nil
881}
882
883func (c *RepoCache) GetUserIdentity() (*IdentityCache, error) {
884	if c.userIdentityId != "" {
885		i, ok := c.identities[c.userIdentityId]
886		if ok {
887			return i, nil
888		}
889	}
890
891	i, err := identity.GetUserIdentity(c.repo)
892	if err != nil {
893		return nil, err
894	}
895
896	cached := NewIdentityCache(c, i)
897	c.identities[i.Id()] = cached
898	c.userIdentityId = i.Id()
899
900	return cached, nil
901}
902
903// NewIdentity create a new identity
904// The new identity is written in the repository (commit)
905func (c *RepoCache) NewIdentity(name string, email string) (*IdentityCache, error) {
906	return c.NewIdentityRaw(name, email, "", "", nil)
907}
908
909// NewIdentityFull create a new identity
910// The new identity is written in the repository (commit)
911func (c *RepoCache) NewIdentityFull(name string, email string, login string, avatarUrl string) (*IdentityCache, error) {
912	return c.NewIdentityRaw(name, email, login, avatarUrl, nil)
913}
914
915func (c *RepoCache) NewIdentityRaw(name string, email string, login string, avatarUrl string, metadata map[string]string) (*IdentityCache, error) {
916	i := identity.NewIdentityFull(name, email, login, avatarUrl)
917
918	for key, value := range metadata {
919		i.SetMetadata(key, value)
920	}
921
922	err := i.Commit(c.repo)
923	if err != nil {
924		return nil, err
925	}
926
927	if _, has := c.identities[i.Id()]; has {
928		return nil, fmt.Errorf("identity %s already exist in the cache", i.Id())
929	}
930
931	cached := NewIdentityCache(c, i)
932	c.identities[i.Id()] = cached
933
934	// force the write of the excerpt
935	err = c.identityUpdated(i.Id())
936	if err != nil {
937		return nil, err
938	}
939
940	return cached, nil
941}