1package bug
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/MichaelMure/git-bug/entity"
8 "github.com/MichaelMure/git-bug/identity"
9 "github.com/MichaelMure/git-bug/repository"
10 "github.com/pkg/errors"
11)
12
13// Fetch retrieve updates from a remote
14// This does not change the local bugs state
15func Fetch(repo repository.Repo, remote string) (string, error) {
16 // "refs/bugs/*:refs/remotes/<remote>>/bugs/*"
17 remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
18 fetchRefSpec := fmt.Sprintf("%s*:%s*", bugsRefPattern, remoteRefSpec)
19
20 return repo.FetchRefs(remote, fetchRefSpec)
21}
22
23// Push update a remote with the local changes
24func Push(repo repository.Repo, remote string) (string, error) {
25 // "refs/bugs/*:refs/bugs/*"
26 refspec := fmt.Sprintf("%s*:%s*", bugsRefPattern, bugsRefPattern)
27
28 return repo.PushRefs(remote, refspec)
29}
30
31// Pull will do a Fetch + MergeAll
32// This function will return an error if a merge fail
33func Pull(repo repository.ClockedRepo, remote string) error {
34 _, err := Fetch(repo, remote)
35 if err != nil {
36 return err
37 }
38
39 for merge := range MergeAll(repo, remote) {
40 if merge.Err != nil {
41 return merge.Err
42 }
43 if merge.Status == entity.MergeStatusInvalid {
44 return errors.Errorf("merge failure: %s", merge.Reason)
45 }
46 }
47
48 return nil
49}
50
51// MergeAll will merge all the available remote bug:
52//
53// - If the remote has new commit, the local bug is updated to match the same history
54// (fast-forward update)
55// - if the local bug has new commits but the remote don't, nothing is changed
56// - if both local and remote bug have new commits (that is, we have a concurrent edition),
57// new local commits are rewritten at the head of the remote history (that is, a rebase)
58func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeResult {
59 out := make(chan entity.MergeResult)
60
61 // no caching for the merge, we load everything from git even if that means multiple
62 // copy of the same entity in memory. The cache layer will intercept the results to
63 // invalidate entities if necessary.
64 identityResolver := identity.NewSimpleResolver(repo)
65
66 go func() {
67 defer close(out)
68
69 remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
70 remoteRefs, err := repo.ListRefs(remoteRefSpec)
71 if err != nil {
72 out <- entity.MergeResult{Err: err}
73 return
74 }
75
76 for _, remoteRef := range remoteRefs {
77 refSplit := strings.Split(remoteRef, "/")
78 id := entity.Id(refSplit[len(refSplit)-1])
79
80 if err := id.Validate(); err != nil {
81 out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error())
82 continue
83 }
84
85 remoteBug, err := read(repo, identityResolver, remoteRef)
86
87 if err != nil {
88 out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote bug is not readable").Error())
89 continue
90 }
91
92 // Check for error in remote data
93 if err := remoteBug.Validate(); err != nil {
94 out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote bug is invalid").Error())
95 continue
96 }
97
98 localRef := bugsRefPattern + remoteBug.Id().String()
99 localExist, err := repo.RefExist(localRef)
100
101 if err != nil {
102 out <- entity.NewMergeError(err, id)
103 continue
104 }
105
106 // the bug is not local yet, simply create the reference
107 if !localExist {
108 err := repo.CopyRef(remoteRef, localRef)
109
110 if err != nil {
111 out <- entity.NewMergeError(err, id)
112 return
113 }
114
115 out <- entity.NewMergeNewStatus(id, remoteBug)
116 continue
117 }
118
119 localBug, err := read(repo, identityResolver, localRef)
120
121 if err != nil {
122 out <- entity.NewMergeError(errors.Wrap(err, "local bug is not readable"), id)
123 return
124 }
125
126 updated, err := localBug.Merge(repo, remoteBug)
127
128 if err != nil {
129 out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
130 return
131 }
132
133 if updated {
134 out <- entity.NewMergeUpdatedStatus(id, localBug)
135 } else {
136 out <- entity.NewMergeNothingStatus(id)
137 }
138 }
139 }()
140
141 return out
142}