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}