1package identity
  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 identities state
 14func Fetch(repo repository.Repo, remote string) (string, error) {
 15	remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
 16	fetchRefSpec := fmt.Sprintf("%s*:%s*", identityRefPattern, 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, identityRefPattern+"*")
 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 identity
 47func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeResult {
 48	out := make(chan entity.MergeResult)
 49
 50	go func() {
 51		defer close(out)
 52
 53		remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
 54		remoteRefs, err := repo.ListRefs(remoteRefSpec)
 55
 56		if err != nil {
 57			out <- entity.MergeResult{Err: err}
 58			return
 59		}
 60
 61		for _, remoteRef := range remoteRefs {
 62			refSplit := strings.Split(remoteRef, "/")
 63			id := entity.Id(refSplit[len(refSplit)-1])
 64
 65			if err := id.Validate(); err != nil {
 66				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error())
 67				continue
 68			}
 69
 70			remoteIdentity, err := read(repo, remoteRef)
 71
 72			if err != nil {
 73				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote identity is not readable").Error())
 74				continue
 75			}
 76
 77			// Check for error in remote data
 78			if err := remoteIdentity.Validate(); err != nil {
 79				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote identity is invalid").Error())
 80				continue
 81			}
 82
 83			localRef := identityRefPattern + remoteIdentity.Id().String()
 84			localExist, err := repo.RefExist(localRef)
 85
 86			if err != nil {
 87				out <- entity.NewMergeError(err, id)
 88				continue
 89			}
 90
 91			// the identity is not local yet, simply create the reference
 92			if !localExist {
 93				err := repo.CopyRef(remoteRef, localRef)
 94
 95				if err != nil {
 96					out <- entity.NewMergeError(err, id)
 97					return
 98				}
 99
100				out <- entity.NewMergeStatus(entity.MergeStatusNew, id, remoteIdentity)
101				continue
102			}
103
104			localIdentity, err := read(repo, localRef)
105
106			if err != nil {
107				out <- entity.NewMergeError(errors.Wrap(err, "local identity is not readable"), id)
108				return
109			}
110
111			updated, err := localIdentity.Merge(repo, remoteIdentity)
112
113			if err != nil {
114				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
115				return
116			}
117
118			if updated {
119				out <- entity.NewMergeStatus(entity.MergeStatusUpdated, id, localIdentity)
120			} else {
121				out <- entity.NewMergeStatus(entity.MergeStatusNothing, id, localIdentity)
122			}
123		}
124	}()
125
126	return out
127}