1package identity
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/MichaelMure/git-bug/entity"
8 "github.com/MichaelMure/git-bug/repository"
9 "github.com/pkg/errors"
10)
11
12// Fetch retrieve updates from a remote
13// This does not change the local identities state
14func Fetch(repo repository.Repo, remote string) (string, error) {
15 remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
16 fetchRefSpec := fmt.Sprintf("%s*:%s*", identityRefPattern, remoteRefSpec)
17
18 return repo.FetchRefs(remote, fetchRefSpec)
19}
20
21// Push update a remote with the local changes
22func Push(repo repository.Repo, remote string) (string, error) {
23 return repo.PushRefs(remote, identityRefPattern+"*")
24}
25
26// Pull will do a Fetch + MergeAll
27// This function will return an error if a merge fail
28func Pull(repo repository.ClockedRepo, remote string) error {
29 _, err := Fetch(repo, remote)
30 if err != nil {
31 return err
32 }
33
34 for merge := range MergeAll(repo, remote) {
35 if merge.Err != nil {
36 return merge.Err
37 }
38 if merge.Status == entity.MergeStatusInvalid {
39 return errors.Errorf("merge failure: %s", merge.Reason)
40 }
41 }
42
43 return nil
44}
45
46// MergeAll will merge all the available remote identity
47func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeResult {
48 out := make(chan entity.MergeResult)
49
50 go func() {
51 defer close(out)
52
53 remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
54 remoteRefs, err := repo.ListRefs(remoteRefSpec)
55
56 if err != nil {
57 out <- entity.MergeResult{Err: err}
58 return
59 }
60
61 for _, remoteRef := range remoteRefs {
62 refSplit := strings.Split(remoteRef, "/")
63 id := entity.Id(refSplit[len(refSplit)-1])
64
65 if err := id.Validate(); err != nil {
66 out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error())
67 continue
68 }
69
70 remoteIdentity, err := read(repo, remoteRef)
71
72 if err != nil {
73 out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote identity is not readable").Error())
74 continue
75 }
76
77 // Check for error in remote data
78 if err := remoteIdentity.Validate(); err != nil {
79 out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote identity is invalid").Error())
80 continue
81 }
82
83 localRef := identityRefPattern + remoteIdentity.Id().String()
84 localExist, err := repo.RefExist(localRef)
85
86 if err != nil {
87 out <- entity.NewMergeError(err, id)
88 continue
89 }
90
91 // the identity is not local yet, simply create the reference
92 if !localExist {
93 err := repo.CopyRef(remoteRef, localRef)
94
95 if err != nil {
96 out <- entity.NewMergeError(err, id)
97 return
98 }
99
100 out <- entity.NewMergeStatus(entity.MergeStatusNew, id, remoteIdentity)
101 continue
102 }
103
104 localIdentity, err := read(repo, localRef)
105
106 if err != nil {
107 out <- entity.NewMergeError(errors.Wrap(err, "local identity is not readable"), id)
108 return
109 }
110
111 updated, err := localIdentity.Merge(repo, remoteIdentity)
112
113 if err != nil {
114 out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
115 return
116 }
117
118 if updated {
119 out <- entity.NewMergeStatus(entity.MergeStatusUpdated, id, localIdentity)
120 } else {
121 out <- entity.NewMergeStatus(entity.MergeStatusNothing, id, localIdentity)
122 }
123 }
124 }()
125
126 return out
127}