aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--entity/dag/entity_actions.go14
-rw-r--r--entity/dag/entity_actions_test.go214
2 files changed, 200 insertions, 28 deletions
diff --git a/entity/dag/entity_actions.go b/entity/dag/entity_actions.go
index edc47d52..fe912557 100644
--- a/entity/dag/entity_actions.go
+++ b/entity/dag/entity_actions.go
@@ -6,6 +6,7 @@ import (
"github.com/pkg/errors"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
)
@@ -31,13 +32,13 @@ func Push(def Definition, repo repository.Repo, remote string) (string, error) {
// Pull will do a Fetch + MergeAll
// Contrary to MergeAll, this function will return an error if a merge fail.
-func Pull(def Definition, repo repository.ClockedRepo, remote string) error {
+func Pull(def Definition, repo repository.ClockedRepo, remote string, author identity.Interface) error {
_, err := Fetch(def, repo, remote)
if err != nil {
return err
}
- for merge := range MergeAll(def, repo, remote) {
+ for merge := range MergeAll(def, repo, remote, author) {
if merge.Err != nil {
return merge.Err
}
@@ -64,7 +65,7 @@ func Pull(def Definition, repo repository.ClockedRepo, remote string) error {
// 5. if both local and remote Entity have new commits (that is, we have a concurrent edition),
// a merge commit with an empty operationPack is created to join both branch and form a DAG.
// --> emit entity.MergeStatusUpdated
-func MergeAll(def Definition, repo repository.ClockedRepo, remote string) <-chan entity.MergeResult {
+func MergeAll(def Definition, repo repository.ClockedRepo, remote string, author identity.Interface) <-chan entity.MergeResult {
out := make(chan entity.MergeResult)
go func() {
@@ -78,7 +79,7 @@ func MergeAll(def Definition, repo repository.ClockedRepo, remote string) <-chan
}
for _, remoteRef := range remoteRefs {
- out <- merge(def, repo, remoteRef)
+ out <- merge(def, repo, remoteRef, author)
}
}()
@@ -87,7 +88,7 @@ func MergeAll(def Definition, repo repository.ClockedRepo, remote string) <-chan
// merge perform a merge to make sure a local Entity is up to date.
// See MergeAll for more details.
-func merge(def Definition, repo repository.ClockedRepo, remoteRef string) entity.MergeResult {
+func merge(def Definition, repo repository.ClockedRepo, remoteRef string, author identity.Interface) entity.MergeResult {
id := entity.RefToId(remoteRef)
if err := id.Validate(); err != nil {
@@ -153,7 +154,7 @@ func merge(def Definition, repo repository.ClockedRepo, remoteRef string) entity
}
for _, hash := range localCommits {
- if hash == localCommit {
+ if hash == remoteCommit {
return entity.NewMergeNothingStatus(id)
}
}
@@ -215,6 +216,7 @@ func merge(def Definition, repo repository.ClockedRepo, remoteRef string) entity
}
opp := &operationPack{
+ Author: author,
Operations: nil,
CreateTime: 0,
EditTime: editTime,
diff --git a/entity/dag/entity_actions_test.go b/entity/dag/entity_actions_test.go
index d7717056..78baf41f 100644
--- a/entity/dag/entity_actions_test.go
+++ b/entity/dag/entity_actions_test.go
@@ -2,6 +2,7 @@ package dag
import (
"sort"
+ "strings"
"testing"
"github.com/stretchr/testify/require"
@@ -36,7 +37,7 @@ func TestPushPull(t *testing.T) {
_, err = Push(def, repoA, "remote")
require.NoError(t, err)
- err = Pull(def, repoB, "remote")
+ err = Pull(def, repoB, "remote", id1)
require.NoError(t, err)
entities := allEntities(t, ReadAll(def, repoB))
@@ -52,7 +53,7 @@ func TestPushPull(t *testing.T) {
_, err = Push(def, repoB, "remote")
require.NoError(t, err)
- err = Pull(def, repoA, "remote")
+ err = Pull(def, repoA, "remote", id1)
require.NoError(t, err)
entities = allEntities(t, ReadAll(def, repoB))
@@ -86,7 +87,7 @@ func TestListLocalIds(t *testing.T) {
listLocalIds(t, def, repoA, 2)
listLocalIds(t, def, repoB, 0)
- err = Pull(def, repoB, "remote")
+ err = Pull(def, repoB, "remote", id1)
require.NoError(t, err)
listLocalIds(t, def, repoA, 2)
@@ -132,6 +133,78 @@ func assertMergeResults(t *testing.T, expected []entity.MergeResult, results <-c
}
}
+func assertEqualRefs(t *testing.T, repoA, repoB repository.RepoData, prefix string) {
+ t.Helper()
+
+ refsA, err := repoA.ListRefs("")
+ require.NoError(t, err)
+
+ var refsAFiltered []string
+ for _, ref := range refsA {
+ if strings.HasPrefix(ref, prefix) {
+ refsAFiltered = append(refsAFiltered, ref)
+ }
+ }
+
+ refsB, err := repoB.ListRefs("")
+ require.NoError(t, err)
+
+ var refsBFiltered []string
+ for _, ref := range refsB {
+ if strings.HasPrefix(ref, prefix) {
+ refsBFiltered = append(refsBFiltered, ref)
+ }
+ }
+
+ require.NotEmpty(t, refsAFiltered)
+ require.Equal(t, refsAFiltered, refsBFiltered)
+
+ for _, ref := range refsAFiltered {
+ commitA, err := repoA.ResolveRef(ref)
+ require.NoError(t, err)
+ commitB, err := repoB.ResolveRef(ref)
+ require.NoError(t, err)
+
+ require.Equal(t, commitA, commitB)
+ }
+}
+
+func assertNotEqualRefs(t *testing.T, repoA, repoB repository.RepoData, prefix string) {
+ t.Helper()
+
+ refsA, err := repoA.ListRefs("")
+ require.NoError(t, err)
+
+ var refsAFiltered []string
+ for _, ref := range refsA {
+ if strings.HasPrefix(ref, prefix) {
+ refsAFiltered = append(refsAFiltered, ref)
+ }
+ }
+
+ refsB, err := repoB.ListRefs("")
+ require.NoError(t, err)
+
+ var refsBFiltered []string
+ for _, ref := range refsB {
+ if strings.HasPrefix(ref, prefix) {
+ refsBFiltered = append(refsBFiltered, ref)
+ }
+ }
+
+ require.NotEmpty(t, refsAFiltered)
+ require.Equal(t, refsAFiltered, refsBFiltered)
+
+ for _, ref := range refsAFiltered {
+ commitA, err := repoA.ResolveRef(ref)
+ require.NoError(t, err)
+ commitB, err := repoB.ResolveRef(ref)
+ require.NoError(t, err)
+
+ require.NotEqual(t, commitA, commitB)
+ }
+}
+
func TestMerge(t *testing.T) {
repoA, repoB, remote, id1, id2, def := makeTestContextRemote(t)
defer repository.CleanupTestRepos(repoA, repoB, remote)
@@ -140,14 +213,14 @@ func TestMerge(t *testing.T) {
// if the remote Entity doesn't exist locally, it's created
// 2 entities in repoA + push to remote
- e1 := New(def)
- e1.Append(newOp1(id1, "foo"))
- err := e1.Commit(repoA)
+ e1A := New(def)
+ e1A.Append(newOp1(id1, "foo"))
+ err := e1A.Commit(repoA)
require.NoError(t, err)
- e2 := New(def)
- e2.Append(newOp2(id2, "bar"))
- err = e2.Commit(repoA)
+ e2A := New(def)
+ e2A.Append(newOp2(id2, "bar"))
+ err = e2A.Commit(repoA)
require.NoError(t, err)
_, err = Push(def, repoA, "remote")
@@ -158,60 +231,157 @@ func TestMerge(t *testing.T) {
_, err = Fetch(def, repoB, "remote")
require.NoError(t, err)
- results := MergeAll(def, repoB, "remote")
+ results := MergeAll(def, repoB, "remote", id1)
assertMergeResults(t, []entity.MergeResult{
{
- Id: e1.Id(),
+ Id: e1A.Id(),
Status: entity.MergeStatusNew,
},
{
- Id: e2.Id(),
+ Id: e2A.Id(),
Status: entity.MergeStatusNew,
},
}, results)
+ assertEqualRefs(t, repoA, repoB, "refs/"+def.namespace)
+
// SCENARIO 2
// if the remote and local Entity have the same state, nothing is changed
- results = MergeAll(def, repoB, "remote")
+ results = MergeAll(def, repoB, "remote", id1)
assertMergeResults(t, []entity.MergeResult{
{
- Id: e1.Id(),
+ Id: e1A.Id(),
Status: entity.MergeStatusNothing,
},
{
- Id: e2.Id(),
+ Id: e2A.Id(),
Status: entity.MergeStatusNothing,
},
}, results)
+ assertEqualRefs(t, repoA, repoB, "refs/"+def.namespace)
+
// SCENARIO 3
// if the local Entity has new commits but the remote don't, nothing is changed
- e1.Append(newOp1(id1, "barbar"))
- err = e1.Commit(repoA)
+ e1A.Append(newOp1(id1, "barbar"))
+ err = e1A.Commit(repoA)
require.NoError(t, err)
- e2.Append(newOp2(id2, "barbarbar"))
- err = e2.Commit(repoA)
+ e2A.Append(newOp2(id2, "barbarbar"))
+ err = e2A.Commit(repoA)
require.NoError(t, err)
- results = MergeAll(def, repoA, "remote")
+ results = MergeAll(def, repoA, "remote", id1)
assertMergeResults(t, []entity.MergeResult{
{
- Id: e1.Id(),
+ Id: e1A.Id(),
Status: entity.MergeStatusNothing,
},
{
- Id: e2.Id(),
+ Id: e2A.Id(),
Status: entity.MergeStatusNothing,
},
}, results)
+ assertNotEqualRefs(t, repoA, repoB, "refs/"+def.namespace)
+
// SCENARIO 4
// if the remote has new commit, the local bug is updated to match the same history
// (fast-forward update)
+
+ _, err = Push(def, repoA, "remote")
+ require.NoError(t, err)
+
+ _, err = Fetch(def, repoB, "remote")
+ require.NoError(t, err)
+
+ results = MergeAll(def, repoB, "remote", id1)
+
+ assertMergeResults(t, []entity.MergeResult{
+ {
+ Id: e1A.Id(),
+ Status: entity.MergeStatusUpdated,
+ },
+ {
+ Id: e2A.Id(),
+ Status: entity.MergeStatusUpdated,
+ },
+ }, results)
+
+ assertEqualRefs(t, repoA, repoB, "refs/"+def.namespace)
+
+ // SCENARIO 5
+ // if both local and remote Entity have new commits (that is, we have a concurrent edition),
+ // a merge commit with an empty operationPack is created to join both branch and form a DAG.
+
+ e1A.Append(newOp1(id1, "barbarfoo"))
+ err = e1A.Commit(repoA)
+ require.NoError(t, err)
+
+ e2A.Append(newOp2(id2, "barbarbarfoo"))
+ err = e2A.Commit(repoA)
+ require.NoError(t, err)
+
+ e1B, err := Read(def, repoB, e1A.Id())
+ require.NoError(t, err)
+
+ e2B, err := Read(def, repoB, e2A.Id())
+ require.NoError(t, err)
+
+ e1B.Append(newOp1(id1, "barbarfoofoo"))
+ err = e1B.Commit(repoB)
+ require.NoError(t, err)
+
+ e2B.Append(newOp2(id2, "barbarbarfoofoo"))
+ err = e2B.Commit(repoB)
+ require.NoError(t, err)
+
+ _, err = Push(def, repoA, "remote")
+ require.NoError(t, err)
+
+ _, err = Fetch(def, repoB, "remote")
+ require.NoError(t, err)
+
+ results = MergeAll(def, repoB, "remote", id1)
+
+ assertMergeResults(t, []entity.MergeResult{
+ {
+ Id: e1A.Id(),
+ Status: entity.MergeStatusUpdated,
+ },
+ {
+ Id: e2A.Id(),
+ Status: entity.MergeStatusUpdated,
+ },
+ }, results)
+
+ assertNotEqualRefs(t, repoA, repoB, "refs/"+def.namespace)
+
+ _, err = Push(def, repoB, "remote")
+ require.NoError(t, err)
+
+ _, err = Fetch(def, repoA, "remote")
+ require.NoError(t, err)
+
+ results = MergeAll(def, repoA, "remote", id1)
+
+ assertMergeResults(t, []entity.MergeResult{
+ {
+ Id: e1A.Id(),
+ Status: entity.MergeStatusUpdated,
+ },
+ {
+ Id: e2A.Id(),
+ Status: entity.MergeStatusUpdated,
+ },
+ }, results)
+
+ // make sure that the graphs become stable over multiple repo, due to the
+ // fast-forward
+ assertEqualRefs(t, repoA, repoB, "refs/"+def.namespace)
}