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