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 !remoteBug.IsValid() {
70 out <- newMergeStatus(MergeStatusInvalid, id, nil)
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
131func (ms MergeStatus) String() string {
132 switch ms {
133 case MergeStatusNew:
134 return "new"
135 case MergeStatusInvalid:
136 return "invalid data"
137 case MergeStatusUpdated:
138 return "updated"
139 case MergeStatusNothing:
140 return "nothing to do"
141 default:
142 panic("unknown merge status")
143 }
144}
145
146type MergeResult struct {
147 // Err is set when a terminal error occur in the process
148 Err error
149
150 Id string
151 Status MergeStatus
152 Bug *Bug
153}
154
155func newMergeError(err error, id string) MergeResult {
156 return MergeResult{
157 Err: err,
158 Id: id,
159 }
160}
161
162func newMergeStatus(status MergeStatus, id string, bug *Bug) MergeResult {
163 return MergeResult{
164 Id: id,
165 Status: status,
166
167 // Bug is not set for an invalid merge result
168 Bug: bug,
169 }
170}