1package identity
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/MichaelMure/git-bug/repository"
8 "github.com/pkg/errors"
9)
10
11// Fetch retrieve updates from a remote
12// This does not change the local identities state
13func Fetch(repo repository.Repo, remote string) (string, error) {
14 remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
15 fetchRefSpec := fmt.Sprintf("%s*:%s*", identityRefPattern, remoteRefSpec)
16
17 return repo.FetchRefs(remote, fetchRefSpec)
18}
19
20// Push update a remote with the local changes
21func Push(repo repository.Repo, remote string) (string, error) {
22 return repo.PushRefs(remote, identityRefPattern+"*")
23}
24
25// Pull will do a Fetch + MergeAll
26// This function will return an error if a merge fail
27func Pull(repo repository.ClockedRepo, remote string) error {
28 _, err := Fetch(repo, remote)
29 if err != nil {
30 return err
31 }
32
33 for merge := range MergeAll(repo, remote) {
34 if merge.Err != nil {
35 return merge.Err
36 }
37 if merge.Status == MergeStatusInvalid {
38 return errors.Errorf("merge failure: %s", merge.Reason)
39 }
40 }
41
42 return nil
43}
44
45// MergeAll will merge all the available remote identity
46func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
47 out := make(chan MergeResult)
48
49 go func() {
50 defer close(out)
51
52 remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
53 remoteRefs, err := repo.ListRefs(remoteRefSpec)
54
55 if err != nil {
56 out <- MergeResult{Err: err}
57 return
58 }
59
60 for _, remoteRef := range remoteRefs {
61 refSplitted := strings.Split(remoteRef, "/")
62 id := refSplitted[len(refSplitted)-1]
63
64 remoteIdentity, err := read(repo, remoteRef)
65
66 if err != nil {
67 out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote identity is not readable").Error())
68 continue
69 }
70
71 // Check for error in remote data
72 if err := remoteIdentity.Validate(); err != nil {
73 out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote identity is invalid").Error())
74 continue
75 }
76
77 localRef := identityRefPattern + remoteIdentity.Id()
78 localExist, err := repo.RefExist(localRef)
79
80 if err != nil {
81 out <- newMergeError(err, id)
82 continue
83 }
84
85 // the identity is not local yet, simply create the reference
86 if !localExist {
87 err := repo.CopyRef(remoteRef, localRef)
88
89 if err != nil {
90 out <- newMergeError(err, id)
91 return
92 }
93
94 out <- newMergeStatus(MergeStatusNew, id, remoteIdentity)
95 continue
96 }
97
98 localIdentity, err := read(repo, localRef)
99
100 if err != nil {
101 out <- newMergeError(errors.Wrap(err, "local identity is not readable"), id)
102 return
103 }
104
105 updated, err := localIdentity.Merge(repo, remoteIdentity)
106
107 if err != nil {
108 out <- newMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
109 return
110 }
111
112 if updated {
113 out <- newMergeStatus(MergeStatusUpdated, id, localIdentity)
114 } else {
115 out <- newMergeStatus(MergeStatusNothing, id, localIdentity)
116 }
117 }
118 }()
119
120 return out
121}
122
123// MergeStatus represent the result of a merge operation of a bug
124type MergeStatus int
125
126const (
127 _ MergeStatus = iota
128 MergeStatusNew
129 MergeStatusInvalid
130 MergeStatusUpdated
131 MergeStatusNothing
132)
133
134// Todo: share a generalized MergeResult with the bug package ?
135type MergeResult struct {
136 // Err is set when a terminal error occur in the process
137 Err error
138
139 Id string
140 Status MergeStatus
141
142 // Only set for invalid status
143 Reason string
144
145 // Not set for invalid status
146 Identity *Identity
147}
148
149func (mr MergeResult) String() string {
150 switch mr.Status {
151 case MergeStatusNew:
152 return "new"
153 case MergeStatusInvalid:
154 return fmt.Sprintf("invalid data: %s", mr.Reason)
155 case MergeStatusUpdated:
156 return "updated"
157 case MergeStatusNothing:
158 return "nothing to do"
159 default:
160 panic("unknown merge status")
161 }
162}
163
164func newMergeError(err error, id string) MergeResult {
165 return MergeResult{
166 Err: err,
167 Id: id,
168 }
169}
170
171func newMergeStatus(status MergeStatus, id string, identity *Identity) MergeResult {
172 return MergeResult{
173 Id: id,
174 Status: status,
175
176 // Identity is not set for an invalid merge result
177 Identity: identity,
178 }
179}
180
181func newMergeInvalidStatus(id string, reason string) MergeResult {
182 return MergeResult{
183 Id: id,
184 Status: MergeStatusInvalid,
185 Reason: reason,
186 }
187}