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