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