1package identity
  2
  3import (
  4	"fmt"
  5	"strings"
  6
  7	"github.com/pkg/errors"
  8
  9	"github.com/MichaelMure/git-bug/entity"
 10	"github.com/MichaelMure/git-bug/repository"
 11)
 12
 13// Fetch retrieve updates from a remote
 14// This does not change the local identities state
 15func Fetch(repo repository.Repo, remote string) (string, error) {
 16	remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
 17	fetchRefSpec := fmt.Sprintf("%s*:%s*", identityRefPattern, remoteRefSpec)
 18
 19	return repo.FetchRefs(remote, fetchRefSpec)
 20}
 21
 22// Push update a remote with the local changes
 23func Push(repo repository.Repo, remote string) (string, error) {
 24	return repo.PushRefs(remote, identityRefPattern+"*")
 25}
 26
 27// Pull will do a Fetch + MergeAll
 28// This function will return an error if a merge fail
 29func Pull(repo repository.ClockedRepo, remote string) error {
 30	_, err := Fetch(repo, remote)
 31	if err != nil {
 32		return err
 33	}
 34
 35	for merge := range MergeAll(repo, remote) {
 36		if merge.Err != nil {
 37			return merge.Err
 38		}
 39		if merge.Status == entity.MergeStatusInvalid {
 40			return errors.Errorf("merge failure: %s", merge.Reason)
 41		}
 42	}
 43
 44	return nil
 45}
 46
 47// MergeAll will merge all the available remote identity
 48func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeResult {
 49	out := make(chan entity.MergeResult)
 50
 51	go func() {
 52		defer close(out)
 53
 54		remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
 55		remoteRefs, err := repo.ListRefs(remoteRefSpec)
 56
 57		if err != nil {
 58			out <- entity.MergeResult{Err: err}
 59			return
 60		}
 61
 62		for _, remoteRef := range remoteRefs {
 63			refSplit := strings.Split(remoteRef, "/")
 64			id := entity.Id(refSplit[len(refSplit)-1])
 65
 66			if err := id.Validate(); err != nil {
 67				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error())
 68				continue
 69			}
 70
 71			remoteIdentity, err := read(repo, remoteRef)
 72
 73			if err != nil {
 74				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote identity is not readable").Error())
 75				continue
 76			}
 77
 78			// Check for error in remote data
 79			if err := remoteIdentity.Validate(); err != nil {
 80				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote identity is invalid").Error())
 81				continue
 82			}
 83
 84			localRef := identityRefPattern + remoteIdentity.Id().String()
 85			localExist, err := repo.RefExist(localRef)
 86
 87			if err != nil {
 88				out <- entity.NewMergeError(err, id)
 89				continue
 90			}
 91
 92			// the identity 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, remoteIdentity)
102				continue
103			}
104
105			localIdentity, err := read(repo, localRef)
106
107			if err != nil {
108				out <- entity.NewMergeError(errors.Wrap(err, "local identity is not readable"), id)
109				return
110			}
111
112			updated, err := localIdentity.Merge(repo, remoteIdentity)
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, localIdentity)
121			} else {
122				out <- entity.NewMergeStatus(entity.MergeStatusNothing, id, localIdentity)
123			}
124		}
125	}()
126
127	return out
128}