identity_actions.go

  1package identity
  2
  3import (
  4	"fmt"
  5	"strings"
  6
  7	"github.com/MichaelMure/git-bug/repository"
  8	"github.com/pkg/errors"
  9)
 10
 11// Fetch retrieve updates from a remote
 12// This does not change the local identities state
 13func Fetch(repo repository.Repo, remote string) (string, error) {
 14	remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
 15	fetchRefSpec := fmt.Sprintf("%s*:%s*", identityRefPattern, remoteRefSpec)
 16
 17	return repo.FetchRefs(remote, fetchRefSpec)
 18}
 19
 20// Push update a remote with the local changes
 21func Push(repo repository.Repo, remote string) (string, error) {
 22	return repo.PushRefs(remote, identityRefPattern+"*")
 23}
 24
 25// Pull will do a Fetch + MergeAll
 26// This function will return an error if a merge fail
 27func Pull(repo repository.ClockedRepo, remote string) error {
 28	_, err := Fetch(repo, remote)
 29	if err != nil {
 30		return err
 31	}
 32
 33	for merge := range MergeAll(repo, remote) {
 34		if merge.Err != nil {
 35			return merge.Err
 36		}
 37		if merge.Status == MergeStatusInvalid {
 38			return errors.Errorf("merge failure: %s", merge.Reason)
 39		}
 40	}
 41
 42	return nil
 43}
 44
 45// MergeAll will merge all the available remote identity
 46func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
 47	out := make(chan MergeResult)
 48
 49	go func() {
 50		defer close(out)
 51
 52		remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
 53		remoteRefs, err := repo.ListRefs(remoteRefSpec)
 54
 55		if err != nil {
 56			out <- MergeResult{Err: err}
 57			return
 58		}
 59
 60		for _, remoteRef := range remoteRefs {
 61			refSplitted := strings.Split(remoteRef, "/")
 62			id := refSplitted[len(refSplitted)-1]
 63
 64			remoteIdentity, err := read(repo, remoteRef)
 65
 66			if err != nil {
 67				out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote identity is not readable").Error())
 68				continue
 69			}
 70
 71			// Check for error in remote data
 72			if err := remoteIdentity.Validate(); err != nil {
 73				out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote identity is invalid").Error())
 74				continue
 75			}
 76
 77			localRef := identityRefPattern + remoteIdentity.Id()
 78			localExist, err := repo.RefExist(localRef)
 79
 80			if err != nil {
 81				out <- newMergeError(err, id)
 82				continue
 83			}
 84
 85			// the identity is not local yet, simply create the reference
 86			if !localExist {
 87				err := repo.CopyRef(remoteRef, localRef)
 88
 89				if err != nil {
 90					out <- newMergeError(err, id)
 91					return
 92				}
 93
 94				out <- newMergeStatus(MergeStatusNew, id, remoteIdentity)
 95				continue
 96			}
 97
 98			localIdentity, err := read(repo, localRef)
 99
100			if err != nil {
101				out <- newMergeError(errors.Wrap(err, "local identity is not readable"), id)
102				return
103			}
104
105			updated, err := localIdentity.Merge(repo, remoteIdentity)
106
107			if err != nil {
108				out <- newMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
109				return
110			}
111
112			if updated {
113				out <- newMergeStatus(MergeStatusUpdated, id, localIdentity)
114			} else {
115				out <- newMergeStatus(MergeStatusNothing, id, localIdentity)
116			}
117		}
118	}()
119
120	return out
121}
122
123// MergeStatus represent the result of a merge operation of a bug
124type MergeStatus int
125
126const (
127	_ MergeStatus = iota
128	MergeStatusNew
129	MergeStatusInvalid
130	MergeStatusUpdated
131	MergeStatusNothing
132)
133
134// Todo: share a generalized MergeResult with the bug package ?
135type MergeResult struct {
136	// Err is set when a terminal error occur in the process
137	Err error
138
139	Id     string
140	Status MergeStatus
141
142	// Only set for invalid status
143	Reason string
144
145	// Not set for invalid status
146	Identity *Identity
147}
148
149func (mr MergeResult) String() string {
150	switch mr.Status {
151	case MergeStatusNew:
152		return "new"
153	case MergeStatusInvalid:
154		return fmt.Sprintf("invalid data: %s", mr.Reason)
155	case MergeStatusUpdated:
156		return "updated"
157	case MergeStatusNothing:
158		return "nothing to do"
159	default:
160		panic("unknown merge status")
161	}
162}
163
164func newMergeError(err error, id string) MergeResult {
165	return MergeResult{
166		Err: err,
167		Id:  id,
168	}
169}
170
171func newMergeStatus(status MergeStatus, id string, identity *Identity) MergeResult {
172	return MergeResult{
173		Id:     id,
174		Status: status,
175
176		// Identity is not set for an invalid merge result
177		Identity: identity,
178	}
179}
180
181func newMergeInvalidStatus(id string, reason string) MergeResult {
182	return MergeResult{
183		Id:     id,
184		Status: MergeStatusInvalid,
185		Reason: reason,
186	}
187}