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