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	// "refs/identities/*:refs/remotes/<remote>/identities/*"
 17	remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
 18	fetchRefSpec := fmt.Sprintf("%s*:%s*", identityRefPattern, remoteRefSpec)
 19
 20	return repo.FetchRefs(remote, fetchRefSpec)
 21}
 22
 23// Push update a remote with the local changes
 24func Push(repo repository.Repo, remote string) (string, error) {
 25	// "refs/identities/*:refs/identities/*"
 26	refspec := fmt.Sprintf("%s*:%s*", identityRefPattern, identityRefPattern)
 27
 28	return repo.PushRefs(remote, refspec)
 29}
 30
 31// Pull will do a Fetch + MergeAll
 32// This function will return an error if a merge fail
 33func Pull(repo repository.ClockedRepo, remote string) error {
 34	_, err := Fetch(repo, remote)
 35	if err != nil {
 36		return err
 37	}
 38
 39	for merge := range MergeAll(repo, remote) {
 40		if merge.Err != nil {
 41			return merge.Err
 42		}
 43		if merge.Status == entity.MergeStatusInvalid {
 44			return errors.Errorf("merge failure: %s", merge.Reason)
 45		}
 46	}
 47
 48	return nil
 49}
 50
 51// MergeAll will merge all the available remote identity
 52func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeResult {
 53	out := make(chan entity.MergeResult)
 54
 55	go func() {
 56		defer close(out)
 57
 58		remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
 59		remoteRefs, err := repo.ListRefs(remoteRefSpec)
 60
 61		if err != nil {
 62			out <- entity.MergeResult{Err: err}
 63			return
 64		}
 65
 66		for _, remoteRef := range remoteRefs {
 67			refSplit := strings.Split(remoteRef, "/")
 68			id := entity.Id(refSplit[len(refSplit)-1])
 69
 70			if err := id.Validate(); err != nil {
 71				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error())
 72				continue
 73			}
 74
 75			remoteIdentity, err := read(repo, remoteRef)
 76
 77			if err != nil {
 78				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote identity is not readable").Error())
 79				continue
 80			}
 81
 82			// Check for error in remote data
 83			if err := remoteIdentity.Validate(); err != nil {
 84				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote identity is invalid").Error())
 85				continue
 86			}
 87
 88			localRef := identityRefPattern + remoteIdentity.Id().String()
 89			localExist, err := repo.RefExist(localRef)
 90
 91			if err != nil {
 92				out <- entity.NewMergeError(err, id)
 93				continue
 94			}
 95
 96			// the identity is not local yet, simply create the reference
 97			if !localExist {
 98				err := repo.CopyRef(remoteRef, localRef)
 99
100				if err != nil {
101					out <- entity.NewMergeError(err, id)
102					return
103				}
104
105				out <- entity.NewMergeStatus(entity.MergeStatusNew, id, remoteIdentity)
106				continue
107			}
108
109			localIdentity, err := read(repo, localRef)
110
111			if err != nil {
112				out <- entity.NewMergeError(errors.Wrap(err, "local identity is not readable"), id)
113				return
114			}
115
116			updated, err := localIdentity.Merge(repo, remoteIdentity)
117
118			if err != nil {
119				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
120				return
121			}
122
123			if updated {
124				out <- entity.NewMergeStatus(entity.MergeStatusUpdated, id, localIdentity)
125			} else {
126				out <- entity.NewMergeStatus(entity.MergeStatusNothing, id, localIdentity)
127			}
128		}
129	}()
130
131	return out
132}