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