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", "git-bug", bugCacheFile)
354}
355
356func identityCacheFilePath(repo repository.Repo) string {
357 return path.Join(repo.GetPath(), ".git", "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", "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}