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 "sync"
14 "time"
15
16 "github.com/pkg/errors"
17
18 "github.com/MichaelMure/git-bug/bug"
19 "github.com/MichaelMure/git-bug/entity"
20 "github.com/MichaelMure/git-bug/identity"
21 "github.com/MichaelMure/git-bug/repository"
22 "github.com/MichaelMure/git-bug/util/git"
23 "github.com/MichaelMure/git-bug/util/process"
24)
25
26const bugCacheFile = "bug-cache"
27const identityCacheFile = "identity-cache"
28
29// 1: original format
30// 2: added cache for identities with a reference in the bug cache
31const formatVersion = 2
32
33type ErrInvalidCacheFormat struct {
34 message string
35}
36
37func (e ErrInvalidCacheFormat) Error() string {
38 return e.message
39}
40
41var _ repository.RepoCommon = &RepoCache{}
42
43// RepoCache is a cache for a Repository. This cache has multiple functions:
44//
45// 1. After being loaded, a Bug is kept in memory in the cache, allowing for fast
46// access later.
47// 2. The cache maintain in memory and on disk a pre-digested excerpt for each bug,
48// allowing for fast querying the whole set of bugs without having to load
49// them individually.
50// 3. The cache guarantee that a single instance of a Bug is loaded at once, avoiding
51// loss of data that we could have with multiple copies in the same process.
52// 4. The same way, the cache maintain in memory a single copy of the loaded identities.
53//
54// The cache also protect the on-disk data by locking the git repository for its
55// own usage, by writing a lock file. Of course, normal git operations are not
56// affected, only git-bug related one.
57type RepoCache struct {
58 // the underlying repo
59 repo repository.ClockedRepo
60
61 // the name of the repository, as defined in the MultiRepoCache
62 name string
63
64 muBug sync.RWMutex
65 // excerpt of bugs data for all bugs
66 bugExcerpts map[entity.Id]*BugExcerpt
67 // bug loaded in memory
68 bugs map[entity.Id]*BugCache
69
70 muIdentity sync.RWMutex
71 // excerpt of identities data for all identities
72 identitiesExcerpts map[entity.Id]*IdentityExcerpt
73 // identities loaded in memory
74 identities map[entity.Id]*IdentityCache
75
76 // the user identity's id, if known
77 userIdentityId entity.Id
78}
79
80func NewRepoCache(r repository.ClockedRepo) (*RepoCache, error) {
81 return NewNamedRepoCache(r, "")
82}
83
84func NewNamedRepoCache(r repository.ClockedRepo, name string) (*RepoCache, error) {
85 c := &RepoCache{
86 repo: r,
87 name: name,
88 bugs: make(map[entity.Id]*BugCache),
89 identities: make(map[entity.Id]*IdentityCache),
90 }
91
92 err := c.lock()
93 if err != nil {
94 return &RepoCache{}, err
95 }
96
97 err = c.load()
98 if err == nil {
99 return c, nil
100 }
101 if _, ok := err.(ErrInvalidCacheFormat); ok {
102 return nil, err
103 }
104
105 err = c.buildCache()
106 if err != nil {
107 return nil, err
108 }
109
110 return c, c.write()
111}
112
113func (c *RepoCache) Name() string {
114 return c.name
115}
116
117// LocalConfig give access to the repository scoped configuration
118func (c *RepoCache) LocalConfig() repository.Config {
119 return c.repo.LocalConfig()
120}
121
122// GlobalConfig give access to the git global configuration
123func (c *RepoCache) GlobalConfig() repository.Config {
124 return c.repo.GlobalConfig()
125}
126
127// GetPath returns the path to the repo.
128func (c *RepoCache) GetPath() string {
129 return c.repo.GetPath()
130}
131
132// GetCoreEditor returns the name of the editor that the user has used to configure git.
133func (c *RepoCache) GetCoreEditor() (string, error) {
134 return c.repo.GetCoreEditor()
135}
136
137// GetRemotes returns the configured remotes repositories.
138func (c *RepoCache) GetRemotes() (map[string]string, error) {
139 return c.repo.GetRemotes()
140}
141
142// GetUserName returns the name the the user has used to configure git
143func (c *RepoCache) GetUserName() (string, error) {
144 return c.repo.GetUserName()
145}
146
147// GetUserEmail returns the email address that the user has used to configure git.
148func (c *RepoCache) GetUserEmail() (string, error) {
149 return c.repo.GetUserEmail()
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.muBug.Lock()
176 defer c.muBug.Unlock()
177 c.muIdentity.Lock()
178 defer c.muIdentity.Unlock()
179
180 c.identities = make(map[entity.Id]*IdentityCache)
181 c.identitiesExcerpts = nil
182 c.bugs = make(map[entity.Id]*BugCache)
183 c.bugExcerpts = nil
184
185 lockPath := repoLockFilePath(c.repo)
186 return os.Remove(lockPath)
187}
188
189// bugUpdated is a callback to trigger when the excerpt of a bug changed,
190// that is each time a bug is updated
191func (c *RepoCache) bugUpdated(id entity.Id) error {
192 c.muBug.Lock()
193
194 b, ok := c.bugs[id]
195 if !ok {
196 c.muBug.Unlock()
197 panic("missing bug in the cache")
198 }
199
200 c.bugExcerpts[id] = NewBugExcerpt(b.bug, b.Snapshot())
201 c.muBug.Unlock()
202
203 // we only need to write the bug cache
204 return c.writeBugCache()
205}
206
207// identityUpdated is a callback to trigger when the excerpt of an identity
208// changed, that is each time an identity is updated
209func (c *RepoCache) identityUpdated(id entity.Id) error {
210 c.muIdentity.Lock()
211
212 i, ok := c.identities[id]
213 if !ok {
214 c.muIdentity.Unlock()
215 panic("missing identity in the cache")
216 }
217
218 c.identitiesExcerpts[id] = NewIdentityExcerpt(i.Identity)
219 c.muIdentity.Unlock()
220
221 // we only need to write the identity cache
222 return c.writeIdentityCache()
223}
224
225// load will try to read from the disk all the cache files
226func (c *RepoCache) load() error {
227 err := c.loadBugCache()
228 if err != nil {
229 return err
230 }
231 return c.loadIdentityCache()
232}
233
234// load will try to read from the disk the bug cache file
235func (c *RepoCache) loadBugCache() error {
236 c.muBug.Lock()
237 defer c.muBug.Unlock()
238
239 f, err := os.Open(bugCacheFilePath(c.repo))
240 if err != nil {
241 return err
242 }
243
244 decoder := gob.NewDecoder(f)
245
246 aux := struct {
247 Version uint
248 Excerpts map[entity.Id]*BugExcerpt
249 }{}
250
251 err = decoder.Decode(&aux)
252 if err != nil {
253 return err
254 }
255
256 if aux.Version != 2 {
257 return ErrInvalidCacheFormat{
258 message: fmt.Sprintf("unknown cache format version %v", aux.Version),
259 }
260 }
261
262 c.bugExcerpts = aux.Excerpts
263 return nil
264}
265
266// load will try to read from the disk the identity cache file
267func (c *RepoCache) loadIdentityCache() error {
268 c.muIdentity.Lock()
269 defer c.muIdentity.Unlock()
270
271 f, err := os.Open(identityCacheFilePath(c.repo))
272 if err != nil {
273 return err
274 }
275
276 decoder := gob.NewDecoder(f)
277
278 aux := struct {
279 Version uint
280 Excerpts map[entity.Id]*IdentityExcerpt
281 }{}
282
283 err = decoder.Decode(&aux)
284 if err != nil {
285 return err
286 }
287
288 if aux.Version != 2 {
289 return ErrInvalidCacheFormat{
290 message: fmt.Sprintf("unknown cache format version %v", aux.Version),
291 }
292 }
293
294 c.identitiesExcerpts = aux.Excerpts
295 return nil
296}
297
298// write will serialize on disk all the cache files
299func (c *RepoCache) write() error {
300 err := c.writeBugCache()
301 if err != nil {
302 return err
303 }
304 return c.writeIdentityCache()
305}
306
307// write will serialize on disk the bug cache file
308func (c *RepoCache) writeBugCache() error {
309 c.muBug.RLock()
310 defer c.muBug.RUnlock()
311
312 var data bytes.Buffer
313
314 aux := struct {
315 Version uint
316 Excerpts map[entity.Id]*BugExcerpt
317 }{
318 Version: formatVersion,
319 Excerpts: c.bugExcerpts,
320 }
321
322 encoder := gob.NewEncoder(&data)
323
324 err := encoder.Encode(aux)
325 if err != nil {
326 return err
327 }
328
329 f, err := os.Create(bugCacheFilePath(c.repo))
330 if err != nil {
331 return err
332 }
333
334 _, err = f.Write(data.Bytes())
335 if err != nil {
336 return err
337 }
338
339 return f.Close()
340}
341
342// write will serialize on disk the identity cache file
343func (c *RepoCache) writeIdentityCache() error {
344 c.muIdentity.RLock()
345 defer c.muIdentity.RUnlock()
346
347 var data bytes.Buffer
348
349 aux := struct {
350 Version uint
351 Excerpts map[entity.Id]*IdentityExcerpt
352 }{
353 Version: formatVersion,
354 Excerpts: c.identitiesExcerpts,
355 }
356
357 encoder := gob.NewEncoder(&data)
358
359 err := encoder.Encode(aux)
360 if err != nil {
361 return err
362 }
363
364 f, err := os.Create(identityCacheFilePath(c.repo))
365 if err != nil {
366 return err
367 }
368
369 _, err = f.Write(data.Bytes())
370 if err != nil {
371 return err
372 }
373
374 return f.Close()
375}
376
377func bugCacheFilePath(repo repository.Repo) string {
378 return path.Join(repo.GetPath(), "git-bug", bugCacheFile)
379}
380
381func identityCacheFilePath(repo repository.Repo) string {
382 return path.Join(repo.GetPath(), "git-bug", identityCacheFile)
383}
384
385func (c *RepoCache) buildCache() error {
386 c.muBug.Lock()
387 defer c.muBug.Unlock()
388 c.muIdentity.Lock()
389 defer c.muIdentity.Unlock()
390
391 _, _ = fmt.Fprintf(os.Stderr, "Building identity cache... ")
392
393 c.identitiesExcerpts = make(map[entity.Id]*IdentityExcerpt)
394
395 allIdentities := identity.ReadAllLocalIdentities(c.repo)
396
397 for i := range allIdentities {
398 if i.Err != nil {
399 return i.Err
400 }
401
402 c.identitiesExcerpts[i.Identity.Id()] = NewIdentityExcerpt(i.Identity)
403 }
404
405 _, _ = fmt.Fprintln(os.Stderr, "Done.")
406
407 _, _ = fmt.Fprintf(os.Stderr, "Building bug cache... ")
408
409 c.bugExcerpts = make(map[entity.Id]*BugExcerpt)
410
411 allBugs := bug.ReadAllLocalBugs(c.repo)
412
413 for b := range allBugs {
414 if b.Err != nil {
415 return b.Err
416 }
417
418 snap := b.Bug.Compile()
419 c.bugExcerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, &snap)
420 }
421
422 _, _ = fmt.Fprintln(os.Stderr, "Done.")
423 return nil
424}
425
426// ResolveBugExcerpt retrieve a BugExcerpt matching the exact given id
427func (c *RepoCache) ResolveBugExcerpt(id entity.Id) (*BugExcerpt, error) {
428 c.muBug.RLock()
429 defer c.muBug.RUnlock()
430
431 e, ok := c.bugExcerpts[id]
432 if !ok {
433 return nil, bug.ErrBugNotExist
434 }
435
436 return e, nil
437}
438
439// ResolveBug retrieve a bug matching the exact given id
440func (c *RepoCache) ResolveBug(id entity.Id) (*BugCache, error) {
441 c.muBug.RLock()
442 cached, ok := c.bugs[id]
443 c.muBug.RUnlock()
444 if ok {
445 return cached, nil
446 }
447
448 b, err := bug.ReadLocalBug(c.repo, id)
449 if err != nil {
450 return nil, err
451 }
452
453 cached = NewBugCache(c, b)
454
455 c.muBug.Lock()
456 c.bugs[id] = cached
457 c.muBug.Unlock()
458
459 return cached, nil
460}
461
462// ResolveBugExcerptPrefix retrieve a BugExcerpt matching an id prefix. It fails if multiple
463// bugs match.
464func (c *RepoCache) ResolveBugExcerptPrefix(prefix string) (*BugExcerpt, error) {
465 return c.ResolveBugExcerptMatcher(func(excerpt *BugExcerpt) bool {
466 return excerpt.Id.HasPrefix(prefix)
467 })
468}
469
470// ResolveBugPrefix retrieve a bug matching an id prefix. It fails if multiple
471// bugs match.
472func (c *RepoCache) ResolveBugPrefix(prefix string) (*BugCache, error) {
473 return c.ResolveBugMatcher(func(excerpt *BugExcerpt) bool {
474 return excerpt.Id.HasPrefix(prefix)
475 })
476}
477
478// ResolveBugCreateMetadata retrieve a bug that has the exact given metadata on
479// its Create operation, that is, the first operation. It fails if multiple bugs
480// match.
481func (c *RepoCache) ResolveBugCreateMetadata(key string, value string) (*BugCache, error) {
482 return c.ResolveBugMatcher(func(excerpt *BugExcerpt) bool {
483 return excerpt.CreateMetadata[key] == value
484 })
485}
486
487func (c *RepoCache) ResolveBugExcerptMatcher(f func(*BugExcerpt) bool) (*BugExcerpt, error) {
488 id, err := c.resolveBugMatcher(f)
489 if err != nil {
490 return nil, err
491 }
492 return c.ResolveBugExcerpt(id)
493}
494
495func (c *RepoCache) ResolveBugMatcher(f func(*BugExcerpt) bool) (*BugCache, error) {
496 id, err := c.resolveBugMatcher(f)
497 if err != nil {
498 return nil, err
499 }
500 return c.ResolveBug(id)
501}
502
503func (c *RepoCache) resolveBugMatcher(f func(*BugExcerpt) bool) (entity.Id, error) {
504 c.muBug.RLock()
505 defer c.muBug.RUnlock()
506
507 // preallocate but empty
508 matching := make([]entity.Id, 0, 5)
509
510 for _, excerpt := range c.bugExcerpts {
511 if f(excerpt) {
512 matching = append(matching, excerpt.Id)
513 }
514 }
515
516 if len(matching) > 1 {
517 return entity.UnsetId, bug.NewErrMultipleMatchBug(matching)
518 }
519
520 if len(matching) == 0 {
521 return entity.UnsetId, bug.ErrBugNotExist
522 }
523
524 return matching[0], nil
525}
526
527// QueryBugs return the id of all Bug matching the given Query
528func (c *RepoCache) QueryBugs(query *Query) []entity.Id {
529 c.muBug.RLock()
530 defer c.muBug.RUnlock()
531
532 if query == nil {
533 return c.AllBugsIds()
534 }
535
536 var filtered []*BugExcerpt
537
538 for _, excerpt := range c.bugExcerpts {
539 if query.Match(excerpt, c) {
540 filtered = append(filtered, excerpt)
541 }
542 }
543
544 var sorter sort.Interface
545
546 switch query.OrderBy {
547 case OrderById:
548 sorter = BugsById(filtered)
549 case OrderByCreation:
550 sorter = BugsByCreationTime(filtered)
551 case OrderByEdit:
552 sorter = BugsByEditTime(filtered)
553 default:
554 panic("missing sort type")
555 }
556
557 if query.OrderDirection == OrderDescending {
558 sorter = sort.Reverse(sorter)
559 }
560
561 sort.Sort(sorter)
562
563 result := make([]entity.Id, len(filtered))
564
565 for i, val := range filtered {
566 result[i] = val.Id
567 }
568
569 return result
570}
571
572// AllBugsIds return all known bug ids
573func (c *RepoCache) AllBugsIds() []entity.Id {
574 c.muBug.RLock()
575 defer c.muBug.RUnlock()
576
577 result := make([]entity.Id, len(c.bugExcerpts))
578
579 i := 0
580 for _, excerpt := range c.bugExcerpts {
581 result[i] = excerpt.Id
582 i++
583 }
584
585 return result
586}
587
588// ValidLabels list valid labels
589//
590// Note: in the future, a proper label policy could be implemented where valid
591// labels are defined in a configuration file. Until that, the default behavior
592// is to return the list of labels already used.
593func (c *RepoCache) ValidLabels() []bug.Label {
594 c.muBug.RLock()
595 defer c.muBug.RUnlock()
596
597 set := map[bug.Label]interface{}{}
598
599 for _, excerpt := range c.bugExcerpts {
600 for _, l := range excerpt.Labels {
601 set[l] = nil
602 }
603 }
604
605 result := make([]bug.Label, len(set))
606
607 i := 0
608 for l := range set {
609 result[i] = l
610 i++
611 }
612
613 // Sort
614 sort.Slice(result, func(i, j int) bool {
615 return string(result[i]) < string(result[j])
616 })
617
618 return result
619}
620
621// NewBug create a new bug
622// The new bug is written in the repository (commit)
623func (c *RepoCache) NewBug(title string, message string) (*BugCache, *bug.CreateOperation, error) {
624 return c.NewBugWithFiles(title, message, nil)
625}
626
627// NewBugWithFiles create a new bug with attached files for the message
628// The new bug is written in the repository (commit)
629func (c *RepoCache) NewBugWithFiles(title string, message string, files []git.Hash) (*BugCache, *bug.CreateOperation, error) {
630 author, err := c.GetUserIdentity()
631 if err != nil {
632 return nil, nil, err
633 }
634
635 return c.NewBugRaw(author, time.Now().Unix(), title, message, files, nil)
636}
637
638// NewBugWithFilesMeta create a new bug with attached files for the message, as
639// well as metadata for the Create operation.
640// The new bug is written in the repository (commit)
641func (c *RepoCache) NewBugRaw(author *IdentityCache, unixTime int64, title string, message string, files []git.Hash, metadata map[string]string) (*BugCache, *bug.CreateOperation, error) {
642 b, op, err := bug.CreateWithFiles(author.Identity, unixTime, title, message, files)
643 if err != nil {
644 return nil, nil, err
645 }
646
647 for key, value := range metadata {
648 op.SetMetadata(key, value)
649 }
650
651 err = b.Commit(c.repo)
652 if err != nil {
653 return nil, nil, err
654 }
655
656 c.muBug.Lock()
657 if _, has := c.bugs[b.Id()]; has {
658 c.muBug.Unlock()
659 return nil, nil, fmt.Errorf("bug %s already exist in the cache", b.Id())
660 }
661
662 cached := NewBugCache(c, b)
663 c.bugs[b.Id()] = cached
664 c.muBug.Unlock()
665
666 // force the write of the excerpt
667 err = c.bugUpdated(b.Id())
668 if err != nil {
669 return nil, nil, err
670 }
671
672 return cached, op, nil
673}
674
675// Fetch retrieve updates from a remote
676// This does not change the local bugs or identities state
677func (c *RepoCache) Fetch(remote string) (string, error) {
678 stdout1, err := identity.Fetch(c.repo, remote)
679 if err != nil {
680 return stdout1, err
681 }
682
683 stdout2, err := bug.Fetch(c.repo, remote)
684 if err != nil {
685 return stdout2, err
686 }
687
688 return stdout1 + stdout2, nil
689}
690
691// MergeAll will merge all the available remote bug and identities
692func (c *RepoCache) MergeAll(remote string) <-chan entity.MergeResult {
693 out := make(chan entity.MergeResult)
694
695 // Intercept merge results to update the cache properly
696 go func() {
697 defer close(out)
698
699 results := identity.MergeAll(c.repo, remote)
700 for result := range results {
701 out <- result
702
703 if result.Err != nil {
704 continue
705 }
706
707 switch result.Status {
708 case entity.MergeStatusNew, entity.MergeStatusUpdated:
709 i := result.Entity.(*identity.Identity)
710 c.muIdentity.Lock()
711 c.identitiesExcerpts[result.Id] = NewIdentityExcerpt(i)
712 c.muIdentity.Unlock()
713 }
714 }
715
716 results = bug.MergeAll(c.repo, remote)
717 for result := range results {
718 out <- result
719
720 if result.Err != nil {
721 continue
722 }
723
724 switch result.Status {
725 case entity.MergeStatusNew, entity.MergeStatusUpdated:
726 b := result.Entity.(*bug.Bug)
727 snap := b.Compile()
728 c.muBug.Lock()
729 c.bugExcerpts[result.Id] = NewBugExcerpt(b, &snap)
730 c.muBug.Unlock()
731 }
732 }
733
734 err := c.write()
735
736 // No easy way out here ..
737 if err != nil {
738 panic(err)
739 }
740 }()
741
742 return out
743}
744
745// Push update a remote with the local changes
746func (c *RepoCache) Push(remote string) (string, error) {
747 stdout1, err := identity.Push(c.repo, remote)
748 if err != nil {
749 return stdout1, err
750 }
751
752 stdout2, err := bug.Push(c.repo, remote)
753 if err != nil {
754 return stdout2, err
755 }
756
757 return stdout1 + stdout2, nil
758}
759
760// Pull will do a Fetch + MergeAll
761// This function will return an error if a merge fail
762func (c *RepoCache) Pull(remote string) error {
763 _, err := c.Fetch(remote)
764 if err != nil {
765 return err
766 }
767
768 for merge := range c.MergeAll(remote) {
769 if merge.Err != nil {
770 return merge.Err
771 }
772 if merge.Status == entity.MergeStatusInvalid {
773 return errors.Errorf("merge failure: %s", merge.Reason)
774 }
775 }
776
777 return nil
778}
779
780func repoLockFilePath(repo repository.Repo) string {
781 return path.Join(repo.GetPath(), "git-bug", lockfile)
782}
783
784// repoIsAvailable check is the given repository is locked by a Cache.
785// Note: this is a smart function that will cleanup the lock file if the
786// corresponding process is not there anymore.
787// If no error is returned, the repo is free to edit.
788func repoIsAvailable(repo repository.Repo) error {
789 lockPath := repoLockFilePath(repo)
790
791 // Todo: this leave way for a racey access to the repo between the test
792 // if the file exist and the actual write. It's probably not a problem in
793 // practice because using a repository will be done from user interaction
794 // or in a context where a single instance of git-bug is already guaranteed
795 // (say, a server with the web UI running). But still, that might be nice to
796 // have a mutex or something to guard that.
797
798 // Todo: this will fail if somehow the filesystem is shared with another
799 // computer. Should add a configuration that prevent the cleaning of the
800 // lock file
801
802 f, err := os.Open(lockPath)
803
804 if err != nil && !os.IsNotExist(err) {
805 return err
806 }
807
808 if err == nil {
809 // lock file already exist
810 buf, err := ioutil.ReadAll(io.LimitReader(f, 10))
811 if err != nil {
812 return err
813 }
814 if len(buf) == 10 {
815 return fmt.Errorf("the lock file should be < 10 bytes")
816 }
817
818 pid, err := strconv.Atoi(string(buf))
819 if err != nil {
820 return err
821 }
822
823 if process.IsRunning(pid) {
824 return fmt.Errorf("the repository you want to access is already locked by the process pid %d", pid)
825 }
826
827 // The lock file is just laying there after a crash, clean it
828
829 fmt.Println("A lock file is present but the corresponding process is not, removing it.")
830 err = f.Close()
831 if err != nil {
832 return err
833 }
834
835 err = os.Remove(lockPath)
836 if err != nil {
837 return err
838 }
839 }
840
841 return nil
842}
843
844// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id
845func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) {
846 c.muIdentity.RLock()
847 defer c.muIdentity.RUnlock()
848
849 e, ok := c.identitiesExcerpts[id]
850 if !ok {
851 return nil, identity.ErrIdentityNotExist
852 }
853
854 return e, nil
855}
856
857// ResolveIdentity retrieve an identity matching the exact given id
858func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) {
859 c.muIdentity.RLock()
860 cached, ok := c.identities[id]
861 c.muIdentity.RUnlock()
862 if ok {
863 return cached, nil
864 }
865
866 i, err := identity.ReadLocal(c.repo, id)
867 if err != nil {
868 return nil, err
869 }
870
871 cached = NewIdentityCache(c, i)
872
873 c.muIdentity.Lock()
874 c.identities[id] = cached
875 c.muIdentity.Unlock()
876
877 return cached, nil
878}
879
880// ResolveIdentityExcerptPrefix retrieve a IdentityExcerpt matching an id prefix.
881// It fails if multiple identities match.
882func (c *RepoCache) ResolveIdentityExcerptPrefix(prefix string) (*IdentityExcerpt, error) {
883 return c.ResolveIdentityExcerptMatcher(func(excerpt *IdentityExcerpt) bool {
884 return excerpt.Id.HasPrefix(prefix)
885 })
886}
887
888// ResolveIdentityPrefix retrieve an Identity matching an id prefix.
889// It fails if multiple identities match.
890func (c *RepoCache) ResolveIdentityPrefix(prefix string) (*IdentityCache, error) {
891 return c.ResolveIdentityMatcher(func(excerpt *IdentityExcerpt) bool {
892 return excerpt.Id.HasPrefix(prefix)
893 })
894}
895
896// ResolveIdentityImmutableMetadata retrieve an Identity that has the exact given metadata on
897// one of it's version. If multiple version have the same key, the first defined take precedence.
898func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) (*IdentityCache, error) {
899 return c.ResolveIdentityMatcher(func(excerpt *IdentityExcerpt) bool {
900 return excerpt.ImmutableMetadata[key] == value
901 })
902}
903
904func (c *RepoCache) ResolveIdentityExcerptMatcher(f func(*IdentityExcerpt) bool) (*IdentityExcerpt, error) {
905 id, err := c.resolveIdentityMatcher(f)
906 if err != nil {
907 return nil, err
908 }
909 return c.ResolveIdentityExcerpt(id)
910}
911
912func (c *RepoCache) ResolveIdentityMatcher(f func(*IdentityExcerpt) bool) (*IdentityCache, error) {
913 id, err := c.resolveIdentityMatcher(f)
914 if err != nil {
915 return nil, err
916 }
917 return c.ResolveIdentity(id)
918}
919
920func (c *RepoCache) resolveIdentityMatcher(f func(*IdentityExcerpt) bool) (entity.Id, error) {
921 c.muIdentity.RLock()
922 defer c.muIdentity.RUnlock()
923
924 // preallocate but empty
925 matching := make([]entity.Id, 0, 5)
926
927 for _, excerpt := range c.identitiesExcerpts {
928 if f(excerpt) {
929 matching = append(matching, excerpt.Id)
930 }
931 }
932
933 if len(matching) > 1 {
934 return entity.UnsetId, identity.NewErrMultipleMatch(matching)
935 }
936
937 if len(matching) == 0 {
938 return entity.UnsetId, identity.ErrIdentityNotExist
939 }
940
941 return matching[0], nil
942}
943
944// AllIdentityIds return all known identity ids
945func (c *RepoCache) AllIdentityIds() []entity.Id {
946 c.muIdentity.RLock()
947 defer c.muIdentity.RUnlock()
948
949 result := make([]entity.Id, len(c.identitiesExcerpts))
950
951 i := 0
952 for _, excerpt := range c.identitiesExcerpts {
953 result[i] = excerpt.Id
954 i++
955 }
956
957 return result
958}
959
960func (c *RepoCache) SetUserIdentity(i *IdentityCache) error {
961 err := identity.SetUserIdentity(c.repo, i.Identity)
962 if err != nil {
963 return err
964 }
965
966 c.muIdentity.RLock()
967 defer c.muIdentity.RUnlock()
968
969 // Make sure that everything is fine
970 if _, ok := c.identities[i.Id()]; !ok {
971 panic("SetUserIdentity while the identity is not from the cache, something is wrong")
972 }
973
974 c.userIdentityId = i.Id()
975
976 return nil
977}
978
979func (c *RepoCache) GetUserIdentity() (*IdentityCache, error) {
980 if c.userIdentityId != "" {
981 i, ok := c.identities[c.userIdentityId]
982 if ok {
983 return i, nil
984 }
985 }
986
987 c.muIdentity.Lock()
988 defer c.muIdentity.Unlock()
989
990 i, err := identity.GetUserIdentity(c.repo)
991 if err != nil {
992 return nil, err
993 }
994
995 cached := NewIdentityCache(c, i)
996 c.identities[i.Id()] = cached
997 c.userIdentityId = i.Id()
998
999 return cached, nil
1000}
1001
1002func (c *RepoCache) GetUserIdentityExcerpt() (*IdentityExcerpt, error) {
1003 if c.userIdentityId == "" {
1004 id, err := identity.GetUserIdentityId(c.repo)
1005 if err != nil {
1006 return nil, err
1007 }
1008 c.userIdentityId = id
1009 }
1010
1011 c.muIdentity.RLock()
1012 defer c.muIdentity.RUnlock()
1013
1014 excerpt, ok := c.identitiesExcerpts[c.userIdentityId]
1015 if !ok {
1016 return nil, fmt.Errorf("cache: missing identity excerpt %v", c.userIdentityId)
1017 }
1018 return excerpt, nil
1019}
1020
1021func (c *RepoCache) IsUserIdentitySet() (bool, error) {
1022 return identity.IsUserIdentitySet(c.repo)
1023}
1024
1025func (c *RepoCache) NewIdentityFromGitUser() (*IdentityCache, error) {
1026 return c.NewIdentityFromGitUserRaw(nil)
1027}
1028
1029func (c *RepoCache) NewIdentityFromGitUserRaw(metadata map[string]string) (*IdentityCache, error) {
1030 i, err := identity.NewFromGitUser(c.repo)
1031 if err != nil {
1032 return nil, err
1033 }
1034 return c.finishIdentity(i, metadata)
1035}
1036
1037// NewIdentity create a new identity
1038// The new identity is written in the repository (commit)
1039func (c *RepoCache) NewIdentity(name string, email string) (*IdentityCache, error) {
1040 return c.NewIdentityRaw(name, email, "", nil)
1041}
1042
1043// NewIdentityFull create a new identity
1044// The new identity is written in the repository (commit)
1045func (c *RepoCache) NewIdentityFull(name string, email string, avatarUrl string) (*IdentityCache, error) {
1046 return c.NewIdentityRaw(name, email, avatarUrl, nil)
1047}
1048
1049func (c *RepoCache) NewIdentityRaw(name string, email string, avatarUrl string, metadata map[string]string) (*IdentityCache, error) {
1050 i := identity.NewIdentityFull(name, email, avatarUrl)
1051 return c.finishIdentity(i, metadata)
1052}
1053
1054func (c *RepoCache) finishIdentity(i *identity.Identity, metadata map[string]string) (*IdentityCache, error) {
1055 for key, value := range metadata {
1056 i.SetMetadata(key, value)
1057 }
1058
1059 err := i.Commit(c.repo)
1060 if err != nil {
1061 return nil, err
1062 }
1063
1064 c.muIdentity.Lock()
1065 if _, has := c.identities[i.Id()]; has {
1066 return nil, fmt.Errorf("identity %s already exist in the cache", i.Id())
1067 }
1068
1069 cached := NewIdentityCache(c, i)
1070 c.identities[i.Id()] = cached
1071 c.muIdentity.Unlock()
1072
1073 // force the write of the excerpt
1074 err = c.identityUpdated(i.Id())
1075 if err != nil {
1076 return nil, err
1077 }
1078
1079 return cached, nil
1080}