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