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