bug_actions.go

  1package bug
  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/identity"
 11	"github.com/MichaelMure/git-bug/repository"
 12)
 13
 14// Fetch retrieve updates from a remote
 15// This does not change the local bugs state
 16func Fetch(repo repository.Repo, remote string) (string, error) {
 17	// "refs/bugs/*:refs/remotes/<remote>>/bugs/*"
 18	remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
 19	fetchRefSpec := fmt.Sprintf("%s*:%s*", bugsRefPattern, remoteRefSpec)
 20
 21	return repo.FetchRefs(remote, fetchRefSpec)
 22}
 23
 24// Push update a remote with the local changes
 25func Push(repo repository.Repo, remote string) (string, error) {
 26	// "refs/bugs/*:refs/bugs/*"
 27	refspec := fmt.Sprintf("%s*:%s*", bugsRefPattern, bugsRefPattern)
 28
 29	return repo.PushRefs(remote, refspec)
 30}
 31
 32// Pull will do a Fetch + MergeAll
 33// This function will return an error if a merge fail
 34func Pull(repo repository.ClockedRepo, remote string) error {
 35	_, err := Fetch(repo, remote)
 36	if err != nil {
 37		return err
 38	}
 39
 40	for merge := range MergeAll(repo, remote) {
 41		if merge.Err != nil {
 42			return merge.Err
 43		}
 44		if merge.Status == entity.MergeStatusInvalid {
 45			return errors.Errorf("merge failure: %s", merge.Reason)
 46		}
 47	}
 48
 49	return nil
 50}
 51
 52// MergeAll will merge all the available remote bug:
 53//
 54// - If the remote has new commit, the local bug is updated to match the same history
 55//   (fast-forward update)
 56// - if the local bug has new commits but the remote don't, nothing is changed
 57// - if both local and remote bug have new commits (that is, we have a concurrent edition),
 58//   new local commits are rewritten at the head of the remote history (that is, a rebase)
 59func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeResult {
 60	out := make(chan entity.MergeResult)
 61
 62	// no caching for the merge, we load everything from git even if that means multiple
 63	// copy of the same entity in memory. The cache layer will intercept the results to
 64	// invalidate entities if necessary.
 65	identityResolver := identity.NewSimpleResolver(repo)
 66
 67	go func() {
 68		defer close(out)
 69
 70		remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
 71		remoteRefs, err := repo.ListRefs(remoteRefSpec)
 72		if err != nil {
 73			out <- entity.MergeResult{Err: err}
 74			return
 75		}
 76
 77		for _, remoteRef := range remoteRefs {
 78			refSplit := strings.Split(remoteRef, "/")
 79			id := entity.Id(refSplit[len(refSplit)-1])
 80
 81			if err := id.Validate(); err != nil {
 82				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error())
 83				continue
 84			}
 85
 86			remoteBug, err := read(repo, identityResolver, remoteRef)
 87
 88			if err != nil {
 89				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote bug is not readable").Error())
 90				continue
 91			}
 92
 93			// Check for error in remote data
 94			if err := remoteBug.Validate(); err != nil {
 95				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote bug is invalid").Error())
 96				continue
 97			}
 98
 99			localRef := bugsRefPattern + remoteBug.Id().String()
100			localExist, err := repo.RefExist(localRef)
101
102			if err != nil {
103				out <- entity.NewMergeError(err, id)
104				continue
105			}
106
107			// the bug is not local yet, simply create the reference
108			if !localExist {
109				err := repo.CopyRef(remoteRef, localRef)
110
111				if err != nil {
112					out <- entity.NewMergeError(err, id)
113					return
114				}
115
116				out <- entity.NewMergeNewStatus(id, remoteBug)
117				continue
118			}
119
120			localBug, err := read(repo, identityResolver, localRef)
121
122			if err != nil {
123				out <- entity.NewMergeError(errors.Wrap(err, "local bug is not readable"), id)
124				return
125			}
126
127			updated, err := localBug.Merge(repo, remoteBug)
128
129			if err != nil {
130				out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
131				return
132			}
133
134			if updated {
135				out <- entity.NewMergeUpdatedStatus(id, localBug)
136			} else {
137				out <- entity.NewMergeNothingStatus(id)
138			}
139		}
140	}()
141
142	return out
143}