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