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			refSplit := strings.Split(remoteRef, "/")
 69			id := entity.Id(refSplit[len(refSplit)-1])
 70
 71			if err := id.Validate(); err != nil {
 72				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error())
 73				continue
 74			}
 75
 76			remoteBug, err := readBug(repo, remoteRef)
 77
 78			if err != nil {
 79				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote bug is not readable").Error())
 80				continue
 81			}
 82
 83			// Check for error in remote data
 84			if err := remoteBug.Validate(); err != nil {
 85				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote bug is invalid").Error())
 86				continue
 87			}
 88
 89			localRef := bugsRefPattern + remoteBug.Id().String()
 90			localExist, err := repo.RefExist(localRef)
 91
 92			if err != nil {
 93				out <- entity.NewMergeError(err, id)
 94				continue
 95			}
 96
 97			// the bug is not local yet, simply create the reference
 98			if !localExist {
 99				err := repo.CopyRef(remoteRef, localRef)
100
101				if err != nil {
102					out <- entity.NewMergeError(err, id)
103					return
104				}
105
106				out <- entity.NewMergeStatus(entity.MergeStatusNew, id, remoteBug)
107				continue
108			}
109
110			localBug, err := readBug(repo, localRef)
111
112			if err != nil {
113				out <- entity.NewMergeError(errors.Wrap(err, "local bug is not readable"), id)
114				return
115			}
116
117			updated, err := localBug.Merge(repo, remoteBug)
118
119			if err != nil {
120				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
121				return
122			}
123
124			if updated {
125				out <- entity.NewMergeStatus(entity.MergeStatusUpdated, id, localBug)
126			} else {
127				out <- entity.NewMergeStatus(entity.MergeStatusNothing, id, localBug)
128			}
129		}
130	}()
131
132	return out
133}