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	}
 39
 40	return nil
 41}
 42
 43// MergeAll will merge all the available remote bug
 44func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
 45	out := make(chan MergeResult)
 46
 47	go func() {
 48		defer close(out)
 49
 50		remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
 51		remoteRefs, err := repo.ListRefs(remoteRefSpec)
 52
 53		if err != nil {
 54			out <- MergeResult{Err: err}
 55			return
 56		}
 57
 58		for _, remoteRef := range remoteRefs {
 59			refSplitted := strings.Split(remoteRef, "/")
 60			id := refSplitted[len(refSplitted)-1]
 61
 62			remoteBug, err := readBug(repo, remoteRef)
 63
 64			if err != nil {
 65				out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote bug is not readable").Error())
 66				continue
 67			}
 68
 69			// Check for error in remote data
 70			if err := remoteBug.Validate(); err != nil {
 71				out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote bug is invalid").Error())
 72				continue
 73			}
 74
 75			localRef := bugsRefPattern + remoteBug.Id()
 76			localExist, err := repo.RefExist(localRef)
 77
 78			if err != nil {
 79				out <- newMergeError(err, id)
 80				continue
 81			}
 82
 83			// the bug is not local yet, simply create the reference
 84			if !localExist {
 85				err := repo.CopyRef(remoteRef, localRef)
 86
 87				if err != nil {
 88					out <- newMergeError(err, id)
 89					return
 90				}
 91
 92				out <- newMergeStatus(MergeStatusNew, id, remoteBug)
 93				continue
 94			}
 95
 96			localBug, err := readBug(repo, localRef)
 97
 98			if err != nil {
 99				out <- newMergeError(errors.Wrap(err, "local bug is not readable"), id)
100				return
101			}
102
103			updated, err := localBug.Merge(repo, remoteBug)
104
105			if err != nil {
106				out <- newMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
107				return
108			}
109
110			if updated {
111				out <- newMergeStatus(MergeStatusUpdated, id, localBug)
112			} else {
113				out <- newMergeStatus(MergeStatusNothing, id, localBug)
114			}
115		}
116	}()
117
118	return out
119}
120
121// MergeStatus represent the result of a merge operation of a bug
122type MergeStatus int
123
124const (
125	_ MergeStatus = iota
126	MergeStatusNew
127	MergeStatusInvalid
128	MergeStatusUpdated
129	MergeStatusNothing
130)
131
132type MergeResult struct {
133	// Err is set when a terminal error occur in the process
134	Err error
135
136	Id     string
137	Status MergeStatus
138
139	// Only set for invalid status
140	Reason string
141
142	// Not set for invalid status
143	Bug *Bug
144}
145
146func (mr MergeResult) String() string {
147	switch mr.Status {
148	case MergeStatusNew:
149		return "new"
150	case MergeStatusInvalid:
151		return fmt.Sprintf("invalid data: %s", mr.Reason)
152	case MergeStatusUpdated:
153		return "updated"
154	case MergeStatusNothing:
155		return "nothing to do"
156	default:
157		panic("unknown merge status")
158	}
159}
160
161func newMergeError(err error, id string) MergeResult {
162	return MergeResult{
163		Err: err,
164		Id:  id,
165	}
166}
167
168func newMergeStatus(status MergeStatus, id string, bug *Bug) MergeResult {
169	return MergeResult{
170		Id:     id,
171		Status: status,
172
173		// Bug is not set for an invalid merge result
174		Bug: bug,
175	}
176}
177
178func newMergeInvalidStatus(id string, reason string) MergeResult {
179	return MergeResult{
180		Id:     id,
181		Status: MergeStatusInvalid,
182		Reason: reason,
183	}
184}