entity_actions.go

  1package dag
  2
  3import (
  4	"fmt"
  5
  6	"github.com/pkg/errors"
  7
  8	"github.com/MichaelMure/git-bug/entity"
  9	"github.com/MichaelMure/git-bug/repository"
 10)
 11
 12func ListLocalIds(typename string, repo repository.RepoData) ([]entity.Id, error) {
 13	refs, err := repo.ListRefs(fmt.Sprintf("refs/%s/", typename))
 14	if err != nil {
 15		return nil, err
 16	}
 17	return entity.RefsToIds(refs), nil
 18}
 19
 20// Fetch retrieve updates from a remote
 21// This does not change the local entity state
 22func Fetch(def Definition, repo repository.Repo, remote string) (string, error) {
 23	// "refs/<entity>/*:refs/remotes/<remote>/<entity>/*"
 24	fetchRefSpec := fmt.Sprintf("refs/%s/*:refs/remotes/%s/%s/*",
 25		def.namespace, remote, def.namespace)
 26
 27	return repo.FetchRefs(remote, fetchRefSpec)
 28}
 29
 30// Push update a remote with the local changes
 31func Push(def Definition, repo repository.Repo, remote string) (string, error) {
 32	// "refs/<entity>/*:refs/<entity>/*"
 33	refspec := fmt.Sprintf("refs/%s/*:refs/%s/*",
 34		def.namespace, def.namespace)
 35
 36	return repo.PushRefs(remote, refspec)
 37}
 38
 39// Pull will do a Fetch + MergeAll
 40// Contrary to MergeAll, this function will return an error if a merge fail.
 41func Pull(def Definition, repo repository.ClockedRepo, remote string) error {
 42	_, err := Fetch(def, repo, remote)
 43	if err != nil {
 44		return err
 45	}
 46
 47	for merge := range MergeAll(def, repo, remote) {
 48		if merge.Err != nil {
 49			return merge.Err
 50		}
 51		if merge.Status == entity.MergeStatusInvalid {
 52			return errors.Errorf("merge failure: %s", merge.Reason)
 53		}
 54	}
 55
 56	return nil
 57}
 58
 59func MergeAll(def Definition, 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
 66	go func() {
 67		defer close(out)
 68
 69		remoteRefSpec := fmt.Sprintf("refs/remotes/%s/%s/", remote, def.namespace)
 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			out <- merge(def, repo, remoteRef)
 78		}
 79	}()
 80
 81	return out
 82}
 83
 84func merge(def Definition, repo repository.ClockedRepo, remoteRef string) entity.MergeResult {
 85	id := entity.RefToId(remoteRef)
 86
 87	if err := id.Validate(); err != nil {
 88		return entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error())
 89	}
 90
 91	remoteEntity, err := read(def, repo, remoteRef)
 92	if err != nil {
 93		return entity.NewMergeInvalidStatus(id,
 94			errors.Wrapf(err, "remote %s is not readable", def.typename).Error())
 95	}
 96
 97	// Check for error in remote data
 98	if err := remoteEntity.Validate(); err != nil {
 99		return entity.NewMergeInvalidStatus(id,
100			errors.Wrapf(err, "remote %s data is invalid", def.typename).Error())
101	}
102
103	localRef := fmt.Sprintf("refs/%s/%s", def.namespace, id.String())
104
105	localExist, err := repo.RefExist(localRef)
106	if err != nil {
107		return entity.NewMergeError(err, id)
108	}
109
110	// the bug is not local yet, simply create the reference
111	if !localExist {
112		err := repo.CopyRef(remoteRef, localRef)
113		if err != nil {
114			return entity.NewMergeError(err, id)
115		}
116
117		return entity.NewMergeStatus(entity.MergeStatusNew, id, remoteEntity)
118	}
119
120	// var updated bool
121	// err = repo.MergeRef(localRef, remoteRef, func() repository.Hash {
122	// 	updated = true
123	//
124	// })
125	// if err != nil {
126	// 	return entity.NewMergeError(err, id)
127	// }
128	//
129	// if updated {
130	// 	return entity.NewMergeStatus(entity.MergeStatusUpdated, id, )
131	// } else {
132	// 	return entity.NewMergeStatus(entity.MergeStatusNothing, id, )
133	// }
134
135	localCommit, err := repo.ResolveRef(localRef)
136	if err != nil {
137		return entity.NewMergeError(err, id)
138	}
139
140	remoteCommit, err := repo.ResolveRef(remoteRef)
141	if err != nil {
142		return entity.NewMergeError(err, id)
143	}
144
145	if localCommit == remoteCommit {
146		// nothing to merge
147		return entity.NewMergeStatus(entity.MergeStatusNothing, id, remoteEntity)
148	}
149
150	// fast-forward is possible if otherRef include ref
151
152	remoteCommits, err := repo.ListCommits(remoteRef)
153	if err != nil {
154		return entity.NewMergeError(err, id)
155	}
156
157	fastForwardPossible := false
158	for _, hash := range remoteCommits {
159		if hash == localCommit {
160			fastForwardPossible = true
161			break
162		}
163	}
164
165	if fastForwardPossible {
166		err = repo.UpdateRef(localRef, remoteCommit)
167		if err != nil {
168			return entity.NewMergeError(err, id)
169		}
170		return entity.NewMergeStatus(entity.MergeStatusUpdated, id, remoteEntity)
171	}
172
173	// fast-forward is not possible, we need to create a merge commit
174	// For simplicity when reading and to have clocks that record this change, we store
175	// an empty operationPack.
176	// First step is to collect those clocks.
177
178	localEntity, err := read(def, repo, localRef)
179	if err != nil {
180		return entity.NewMergeError(err, id)
181	}
182
183	// err = localEntity.packClock.Witness(remoteEntity.packClock.Time())
184	// if err != nil {
185	// 	return entity.NewMergeError(err, id)
186	// }
187	//
188	// packTime, err := localEntity.packClock.Increment()
189	// if err != nil {
190	// 	return entity.NewMergeError(err, id)
191	// }
192
193	editTime, err := repo.Increment(fmt.Sprintf(editClockPattern, def.namespace))
194	if err != nil {
195		return entity.NewMergeError(err, id)
196	}
197
198	opp := &operationPack{
199		Operations: nil,
200		CreateTime: 0,
201		EditTime:   editTime,
202		// PackTime:   packTime,
203	}
204
205	treeHash, err := opp.Write(def, repo)
206	if err != nil {
207		return entity.NewMergeError(err, id)
208	}
209
210	// Create the merge commit with two parents
211	newHash, err := repo.StoreCommit(treeHash, localCommit, remoteCommit)
212	if err != nil {
213		return entity.NewMergeError(err, id)
214	}
215
216	// finally update the ref
217	err = repo.UpdateRef(localRef, newHash)
218	if err != nil {
219		return entity.NewMergeError(err, id)
220	}
221
222	return entity.NewMergeStatus(entity.MergeStatusUpdated, id, localEntity)
223}
224
225func Remove() error {
226	panic("")
227}