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