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