bug_actions.go

  1package bug
  2
  3import (
  4	"fmt"
  5	"strings"
  6
  7	"github.com/MichaelMure/git-bug/repository"
  8	"github.com/pkg/errors"
  9)
 10
 11// Fetch retrieve update from a remote
 12// This does not change the local bugs state
 13func Fetch(repo repository.Repo, remote string) (string, error) {
 14	remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
 15	fetchRefSpec := fmt.Sprintf("%s*:%s*", bugsRefPattern, remoteRefSpec)
 16
 17	return repo.FetchRefs(remote, fetchRefSpec)
 18}
 19
 20// Push update a remote with the local changes
 21func Push(repo repository.Repo, remote string) (string, error) {
 22	return repo.PushRefs(remote, bugsRefPattern+"*")
 23}
 24
 25// Pull will do a Fetch + MergeAll
 26// This function won't give details on the underlying process. If you need more
 27// use Fetch and MergeAll separately.
 28func Pull(repo repository.ClockedRepo, remote string) error {
 29	_, err := Fetch(repo, remote)
 30	if err != nil {
 31		return err
 32	}
 33
 34	for merge := range MergeAll(repo, remote) {
 35		if merge.Err != nil {
 36			return merge.Err
 37		}
 38		if merge.Status == MergeStatusInvalid {
 39			// Not awesome: simply output the merge failure here as this function
 40			// is only used in tests for now.
 41			fmt.Println(merge)
 42		}
 43	}
 44
 45	return nil
 46}
 47
 48// MergeAll will merge all the available remote bug
 49func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
 50	out := make(chan MergeResult)
 51
 52	go func() {
 53		defer close(out)
 54
 55		remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
 56		remoteRefs, err := repo.ListRefs(remoteRefSpec)
 57
 58		if err != nil {
 59			out <- MergeResult{Err: err}
 60			return
 61		}
 62
 63		for _, remoteRef := range remoteRefs {
 64			refSplitted := strings.Split(remoteRef, "/")
 65			id := refSplitted[len(refSplitted)-1]
 66
 67			remoteBug, err := readBug(repo, remoteRef)
 68
 69			if err != nil {
 70				out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote bug is not readable").Error())
 71				continue
 72			}
 73
 74			// Check for error in remote data
 75			if err := remoteBug.Validate(); err != nil {
 76				out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote bug is invalid").Error())
 77				continue
 78			}
 79
 80			localRef := bugsRefPattern + remoteBug.Id()
 81			localExist, err := repo.RefExist(localRef)
 82
 83			if err != nil {
 84				out <- newMergeError(err, id)
 85				continue
 86			}
 87
 88			// the bug is not local yet, simply create the reference
 89			if !localExist {
 90				err := repo.CopyRef(remoteRef, localRef)
 91
 92				if err != nil {
 93					out <- newMergeError(err, id)
 94					return
 95				}
 96
 97				out <- newMergeStatus(MergeStatusNew, id, remoteBug)
 98				continue
 99			}
100
101			localBug, err := readBug(repo, localRef)
102
103			if err != nil {
104				out <- newMergeError(errors.Wrap(err, "local bug is not readable"), id)
105				return
106			}
107
108			updated, err := localBug.Merge(repo, remoteBug)
109
110			if err != nil {
111				out <- newMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
112				return
113			}
114
115			if updated {
116				out <- newMergeStatus(MergeStatusUpdated, id, localBug)
117			} else {
118				out <- newMergeStatus(MergeStatusNothing, id, localBug)
119			}
120		}
121	}()
122
123	return out
124}
125
126// MergeStatus represent the result of a merge operation of a bug
127type MergeStatus int
128
129const (
130	_ MergeStatus = iota
131	MergeStatusNew
132	MergeStatusInvalid
133	MergeStatusUpdated
134	MergeStatusNothing
135)
136
137type MergeResult struct {
138	// Err is set when a terminal error occur in the process
139	Err error
140
141	Id     string
142	Status MergeStatus
143
144	// Only set for invalid status
145	Reason string
146
147	// Not set for invalid status
148	Bug *Bug
149}
150
151func (mr MergeResult) String() string {
152	switch mr.Status {
153	case MergeStatusNew:
154		return "new"
155	case MergeStatusInvalid:
156		return fmt.Sprintf("invalid data: %s", mr.Reason)
157	case MergeStatusUpdated:
158		return "updated"
159	case MergeStatusNothing:
160		return "nothing to do"
161	default:
162		panic("unknown merge status")
163	}
164}
165
166func newMergeError(err error, id string) MergeResult {
167	return MergeResult{
168		Err: err,
169		Id:  id,
170	}
171}
172
173func newMergeStatus(status MergeStatus, id string, bug *Bug) MergeResult {
174	return MergeResult{
175		Id:     id,
176		Status: status,
177
178		// Bug is not set for an invalid merge result
179		Bug: bug,
180	}
181}
182
183func newMergeInvalidStatus(id string, reason string) MergeResult {
184	return MergeResult{
185		Id:     id,
186		Status: MergeStatusInvalid,
187		Reason: reason,
188	}
189}