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