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}