bug_actions.go

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