bug_actions.go

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