1package bug
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/MichaelMure/git-bug/repository"
8)
9
10const MsgMergeNew = "new"
11const MsgMergeInvalid = "invalid data"
12const MsgMergeUpdated = "updated"
13const MsgMergeNothing = "nothing to do"
14
15// Fetch retrieve update from a remote
16// This does not change the local bugs state
17func Fetch(repo repository.Repo, remote string) (string, error) {
18 remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
19 fetchRefSpec := fmt.Sprintf("%s*:%s*", bugsRefPattern, remoteRefSpec)
20
21 return repo.FetchRefs(remote, fetchRefSpec)
22}
23
24// Push update a remote with the local changes
25func Push(repo repository.Repo, remote string) (string, error) {
26 return repo.PushRefs(remote, bugsRefPattern+"*")
27}
28
29// Pull will do a Fetch + MergeAll
30// This function won't give details on the underlying process. If you need more
31// use Fetch and MergeAll separately.
32func Pull(repo repository.Repo, remote string) error {
33 _, err := Fetch(repo, remote)
34 if err != nil {
35 return err
36 }
37
38 for merge := range MergeAll(repo, remote) {
39 if merge.Err != nil {
40 return merge.Err
41 }
42 }
43
44 return nil
45}
46
47type MergeResult struct {
48 // Err is set when a terminal error occur in the process
49 Err error
50
51 Id string
52 Status string
53 Bug *Bug
54}
55
56func newMergeError(err error, id string) MergeResult {
57 return MergeResult{
58 Err: err,
59 Id: id,
60 }
61}
62
63func newMergeStatus(status string, id string, bug *Bug) MergeResult {
64 return MergeResult{
65 Id: id,
66 Status: status,
67
68 // Bug is not set for an invalid merge result
69 Bug: bug,
70 }
71}
72
73// MergeAll will merge all the available remote bug
74func MergeAll(repo repository.Repo, remote string) <-chan MergeResult {
75 out := make(chan MergeResult)
76
77 go func() {
78 defer close(out)
79
80 remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
81 remoteRefs, err := repo.ListRefs(remoteRefSpec)
82
83 if err != nil {
84 out <- MergeResult{Err: err}
85 return
86 }
87
88 for _, remoteRef := range remoteRefs {
89 refSplitted := strings.Split(remoteRef, "/")
90 id := refSplitted[len(refSplitted)-1]
91
92 remoteBug, err := readBug(repo, remoteRef)
93
94 if err != nil {
95 out <- newMergeError(err, id)
96 continue
97 }
98
99 // Check for error in remote data
100 if !remoteBug.IsValid() {
101 out <- newMergeStatus(MsgMergeInvalid, id, nil)
102 continue
103 }
104
105 localRef := bugsRefPattern + remoteBug.Id()
106 localExist, err := repo.RefExist(localRef)
107
108 if err != nil {
109 out <- newMergeError(err, id)
110 continue
111 }
112
113 // the bug is not local yet, simply create the reference
114 if !localExist {
115 err := repo.CopyRef(remoteRef, localRef)
116
117 if err != nil {
118 out <- newMergeError(err, id)
119 return
120 }
121
122 out <- newMergeStatus(MsgMergeNew, id, remoteBug)
123 continue
124 }
125
126 localBug, err := readBug(repo, localRef)
127
128 if err != nil {
129 out <- newMergeError(err, id)
130 return
131 }
132
133 updated, err := localBug.Merge(repo, remoteBug)
134
135 if err != nil {
136 out <- newMergeError(err, id)
137 return
138 }
139
140 if updated {
141 out <- newMergeStatus(MsgMergeUpdated, id, localBug)
142 } else {
143 out <- newMergeStatus(MsgMergeNothing, id, localBug)
144 }
145 }
146 }()
147
148 return out
149}