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