Add Set support to merge.ThreeWay

merge.ThreeWay(SetA, SetB, Parent) essentially returns the union of
the three Sets.

Toward #148
This commit is contained in:
Chris Masone
2016-08-25 13:41:47 -07:00
parent 2f66e67763
commit c53d5c1189
4 changed files with 313 additions and 168 deletions
+41 -4
View File
@@ -36,10 +36,10 @@ func newMergeConflict(format string, args ...interface{}) *ErrMergeConflict {
// - If the same index is both removed and inserted wrt parent: conflict
// - If the same index is inserted wrt parent, but with different values: conflict
// - If we are dealing with a set:
// - If the same object is both removed and inserted wrt parent: conflict
// - `merged` is essentially union(a, b, parent)
//
// All other modifications are allowed.
// Currently, ThreeWay() only works on types.Map.
// ThreeWay() works on types.Map, types.Set, and types.Struct.
func ThreeWay(a, b, parent types.Value, vwr types.ValueReadWriter) (merged types.Value, err error) {
if a == nil && b == nil {
return parent, nil
@@ -93,8 +93,9 @@ func threeWayMerge(a, b, parent types.Value, vwr types.ValueReadWriter) (merged
}
case types.SetKind:
// TODO: Implement plan from BUG148
return parent, newMergeConflict("Cannot merge %s.", a.Type().Describe())
if aSet, bSet, pSet, ok := setAssert(a, b, parent); ok {
return threeWaySetMerge(aSet, bSet, pSet, vwr)
}
case types.StructKind:
if aStruct, bStruct, pStruct, ok := structAssert(a, b, parent); ok {
@@ -128,6 +129,29 @@ func threeWayMapMerge(a, b, parent types.Map, vwr types.ValueReadWriter) (merged
return threeWayOrderedSequenceMerge(parent, aDiff, bDiff, a.Get, b.Get, parent.Get, apply, vwr)
}
func threeWaySetMerge(a, b, parent types.Set, vwr types.ValueReadWriter) (merged types.Value, err error) {
aDiff := func(change chan<- types.ValueChanged, stop <-chan struct{}) {
a.DiffLeftRight(parent, change, stop)
}
bDiff := func(change chan<- types.ValueChanged, stop <-chan struct{}) {
b.DiffLeftRight(parent, change, stop)
}
getSelf := func(v types.Value) types.Value {
return v
}
apply := func(target types.Value, change types.ValueChanged, ignored types.Value) types.Value {
switch change.ChangeType {
case types.DiffChangeAdded, types.DiffChangeModified:
return target.(types.Set).Insert(change.V)
case types.DiffChangeRemoved:
return target.(types.Set).Remove(change.V)
default:
panic("Not Reached")
}
}
return threeWayOrderedSequenceMerge(parent, aDiff, bDiff, getSelf, getSelf, getSelf, apply, vwr)
}
func threeWayStructMerge(a, b, parent types.Struct, vwr types.ValueReadWriter) (merged types.Value, err error) {
aDiff := func(change chan<- types.ValueChanged, stop <-chan struct{}) {
a.Diff(parent, change, stop)
@@ -201,6 +225,19 @@ func refAssert(a, b, parent types.Value, vwr types.ValueReadWriter) (aValue, bVa
return aValue, bValue, pValue, aOk && bOk && pOk
}
func setAssert(a, b, parent types.Value) (aSet, bSet, pSet types.Set, ok bool) {
var aOk, bOk, pOk bool
aSet, aOk = a.(types.Set)
bSet, bOk = b.(types.Set)
if parent != nil {
pSet, pOk = parent.(types.Set)
} else {
pSet, pOk = types.NewSet(), true
}
ok = aOk && bOk && pOk
return
}
func structAssert(a, b, parent types.Value) (aStruct, bStruct, pStruct types.Struct, ok bool) {
var aOk, bOk, pOk bool
aStruct, aOk = a.(types.Struct)
+176
View File
@@ -0,0 +1,176 @@
// Copyright 2016 Attic Labs, Inc. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0
package merge
import (
"testing"
"github.com/attic-labs/noms/go/types"
"github.com/attic-labs/testify/suite"
)
func TestThreeWayMapMerge(t *testing.T) {
suite.Run(t, &ThreeWayMapMergeSuite{})
}
func TestThreeWayStructMerge(t *testing.T) {
suite.Run(t, &ThreeWayStructMergeSuite{})
}
type kvs []interface{}
func (kv kvs) items() []interface{} {
return kv
}
func (kv kvs) remove(k interface{}) kvs {
out := make(kvs, 0, len(kv))
for i := 0; i < len(kv); i += 2 {
if kv[i] != k {
out = append(out, kv[i], kv[i+1])
}
}
return out
}
func (kv kvs) set(k, v interface{}) kvs {
out := make(kvs, len(kv))
for i := 0; i < len(kv); i += 2 {
out[i], out[i+1] = kv[i], kv[i+1]
if kv[i] == k {
out[i+1] = v
}
}
return out
}
var (
aa1 = kvs{"a1", "a-one", "a2", "a-two", "a3", "a-three", "a4", "a-four"}
aa1a = kvs{"a1", "a-one", "a2", "a-two", "a3", "a-three-diff", "a4", "a-four", "a6", "a-six"}
aa1b = kvs{"a1", "a-one", "a3", "a-three-diff", "a4", "a-four", "a5", "a-five"}
aaMerged = kvs{"a1", "a-one", "a3", "a-three-diff", "a4", "a-four", "a5", "a-five", "a6", "a-six"}
mm1 = kvs{}
mm1a = kvs{"k1", kvs{"a", 0}}
mm1b = kvs{"k1", kvs{"b", 1}}
mm1Merged = kvs{"k1", kvs{"a", 0, "b", 1}}
mm2 = kvs{"k2", aa1, "k3", "k-three"}
mm2a = kvs{"k1", kvs{"a", 0}, "k2", aa1a, "k3", "k-three", "k4", "k-four"}
mm2b = kvs{"k1", kvs{"b", 1}, "k2", aa1b}
mm2Merged = kvs{"k1", kvs{"a", 0, "b", 1}, "k2", aaMerged, "k4", "k-four"}
)
type ThreeWayKeyValMergeSuite struct {
ThreeWayMergeSuite
}
type ThreeWayMapMergeSuite struct {
ThreeWayKeyValMergeSuite
}
func (s *ThreeWayMapMergeSuite) SetupSuite() {
s.create = func(seq seq) (val types.Value) {
if seq != nil {
keyValues := valsToTypesValues(s.create, seq.items()...)
val = types.NewMap(keyValues...)
}
return
}
s.typeStr = "Map"
}
type ThreeWayStructMergeSuite struct {
ThreeWayKeyValMergeSuite
}
func (s *ThreeWayStructMergeSuite) SetupSuite() {
s.create = func(seq seq) (val types.Value) {
if seq != nil {
kv := seq.items()
fields := types.StructData{}
for i := 0; i < len(kv); i += 2 {
fields[kv[i].(string)] = valToTypesValue(s.create, kv[i+1])
}
val = types.NewStruct("TestStruct", fields)
}
return
}
s.typeStr = "struct"
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_DoNothing() {
s.tryThreeWayMerge(nil, nil, aa1, aa1, nil)
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_NoRecursion() {
s.tryThreeWayMerge(aa1a, aa1b, aa1, aaMerged, nil)
s.tryThreeWayMerge(aa1b, aa1a, aa1, aaMerged, nil)
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_RecursiveCreate() {
s.tryThreeWayMerge(mm1a, mm1b, mm1, mm1Merged, nil)
s.tryThreeWayMerge(mm1b, mm1a, mm1, mm1Merged, nil)
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_RecursiveCreateNil() {
s.tryThreeWayMerge(mm1a, mm1b, nil, mm1Merged, nil)
s.tryThreeWayMerge(mm1b, mm1a, nil, mm1Merged, nil)
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_RecursiveMerge() {
s.tryThreeWayMerge(mm2a, mm2b, mm2, mm2Merged, nil)
s.tryThreeWayMerge(mm2b, mm2a, mm2, mm2Merged, nil)
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_RefMerge() {
vs := types.NewTestValueStore()
strRef := vs.WriteValue(types.NewStruct("Foo", types.StructData{"life": types.Number(42)}))
m := kvs{"r2", vs.WriteValue(s.create(aa1))}
ma := kvs{"r1", strRef, "r2", vs.WriteValue(s.create(aa1a))}
mb := kvs{"r1", strRef, "r2", vs.WriteValue(s.create(aa1b))}
mMerged := kvs{"r1", strRef, "r2", vs.WriteValue(s.create(aaMerged))}
vs.Flush()
s.tryThreeWayMerge(ma, mb, m, mMerged, vs)
s.tryThreeWayMerge(mb, ma, m, mMerged, vs)
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_RecursiveMultiLevelMerge() {
vs := types.NewTestValueStore()
m := kvs{"mm1", mm1, "mm2", vs.WriteValue(s.create(mm2))}
ma := kvs{"mm1", mm1a, "mm2", vs.WriteValue(s.create(mm2a))}
mb := kvs{"mm1", mm1b, "mm2", vs.WriteValue(s.create(mm2b))}
mMerged := kvs{"mm1", mm1Merged, "mm2", vs.WriteValue(s.create(mm2Merged))}
vs.Flush()
s.tryThreeWayMerge(ma, mb, m, mMerged, vs)
s.tryThreeWayMerge(mb, ma, m, mMerged, vs)
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_NilConflict() {
s.tryThreeWayConflict(nil, s.create(mm2b), s.create(mm2), "Cannot merge nil Value with")
s.tryThreeWayConflict(s.create(mm2a), nil, s.create(mm2), "with nil value.")
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_ImmediateConflict() {
s.tryThreeWayConflict(types.NewSet(), s.create(mm2b), s.create(mm2), "Cannot merge Set<> with "+s.typeStr)
s.tryThreeWayConflict(s.create(mm2b), types.NewSet(), s.create(mm2), "Cannot merge "+s.typeStr)
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_NestedConflict() {
a := mm2a.set("k2", types.NewSet())
s.tryThreeWayConflict(s.create(a), s.create(mm2b), s.create(mm2), types.EncodedValue(types.NewSet()))
s.tryThreeWayConflict(s.create(a), s.create(mm2b), s.create(mm2), types.EncodedValue(s.create(aa1b)))
}
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_NestedConflictingOperation() {
a := mm2a.remove("k2")
s.tryThreeWayConflict(s.create(a), s.create(mm2b), s.create(mm2), `removed "k2"`)
s.tryThreeWayConflict(s.create(a), s.create(mm2b), s.create(mm2), `modded "k2"`)
}
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2016 Attic Labs, Inc. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0
package merge
import (
"testing"
"github.com/attic-labs/noms/go/types"
"github.com/attic-labs/testify/suite"
)
func TestThreeWaySetMerge(t *testing.T) {
suite.Run(t, &ThreeWaySetMergeSuite{})
}
type items []interface{}
func (kv items) items() []interface{} {
return kv
}
type ThreeWaySetMergeSuite struct {
ThreeWayMergeSuite
}
func (s *ThreeWaySetMergeSuite) SetupSuite() {
s.create = func(i seq) (val types.Value) {
if i != nil {
keyValues := valsToTypesValues(s.create, i.items()...)
val = types.NewSet(keyValues...)
}
return
}
s.typeStr = "Set"
}
var (
flat = items{"a1", "a2", "a3", "a4"}
flatA = items{"a1", "a2", "a5", "a6"}
flatB = items{"a1", "a4", "a7", "a5"}
flatM = items{"a1", "a5", "a6", "a7"}
ss1 = items{}
ss1a = items{"k1", flatA, items{"a", 0}}
ss1b = items{"k1", items{"a", 0}, flatB}
ss1Merged = items{"k1", items{"a", 0}, flatA, flatB}
)
func (s *ThreeWaySetMergeSuite) TestThreeWayMerge_DoNothing() {
s.tryThreeWayMerge(nil, nil, flat, flat, nil)
}
func (s *ThreeWaySetMergeSuite) TestThreeWayMerge_Primitives() {
s.tryThreeWayMerge(flatA, flatB, flat, flatM, nil)
s.tryThreeWayMerge(flatB, flatA, flat, flatM, nil)
}
func (s *ThreeWaySetMergeSuite) TestThreeWayMerge_HandleEmpty() {
s.tryThreeWayMerge(ss1a, ss1b, ss1, ss1Merged, nil)
s.tryThreeWayMerge(ss1b, ss1a, ss1, ss1Merged, nil)
}
func (s *ThreeWaySetMergeSuite) TestThreeWayMerge_HandleNil() {
s.tryThreeWayMerge(ss1a, ss1b, nil, ss1Merged, nil)
s.tryThreeWayMerge(ss1b, ss1a, nil, ss1Merged, nil)
}
func (s *ThreeWaySetMergeSuite) TestThreeWayMerge_Refs() {
vs := types.NewTestValueStore()
strRef := vs.WriteValue(types.NewStruct("Foo", types.StructData{"life": types.Number(42)}))
m := items{vs.WriteValue(s.create(flatA)), vs.WriteValue(s.create(flatB))}
ma := items{"r1", vs.WriteValue(s.create(flatA))}
mb := items{"r1", strRef, vs.WriteValue(s.create(flatA))}
mMerged := items{"r1", strRef, vs.WriteValue(s.create(flatA))}
vs.Flush()
s.tryThreeWayMerge(ma, mb, m, mMerged, vs)
s.tryThreeWayMerge(mb, ma, m, mMerged, vs)
}
func (s *ThreeWaySetMergeSuite) TestThreeWayMerge_ImmediateConflict() {
s.tryThreeWayConflict(types.NewMap(), s.create(ss1b), s.create(ss1), "Cannot merge Map<, > with "+s.typeStr)
s.tryThreeWayConflict(s.create(ss1b), types.NewMap(), s.create(ss1), "Cannot merge "+s.typeStr)
}
+8 -164
View File
@@ -5,103 +5,21 @@
package merge
import (
"testing"
"github.com/attic-labs/noms/go/types"
"github.com/attic-labs/testify/suite"
)
func TestThreeWayMapMerge(t *testing.T) {
suite.Run(t, &ThreeWayMapMergeSuite{})
type seq interface {
items() []interface{}
}
func TestThreeWayStructMerge(t *testing.T) {
suite.Run(t, &ThreeWayStructMergeSuite{})
}
type kvs []interface{}
func (kv kvs) remove(k interface{}) kvs {
out := make(kvs, 0, len(kv))
for i := 0; i < len(kv); i++ {
if kv[i] == k {
i++ // skip kv[i] and kv[i+1]
continue
}
out = append(out, kv[i])
}
return out
}
func (kv kvs) set(k, v interface{}) kvs {
out := make(kvs, len(kv))
for i := 0; i < len(kv); i++ {
out[i] = kv[i]
if kv[i] == k {
i++
out[i] = v
}
}
return out
}
var (
aa1 = kvs{"a1", "a-one", "a2", "a-two", "a3", "a-three", "a4", "a-four"}
aa1a = kvs{"a1", "a-one", "a2", "a-two", "a3", "a-three-diff", "a4", "a-four", "a6", "a-six"}
aa1b = kvs{"a1", "a-one", "a3", "a-three-diff", "a4", "a-four", "a5", "a-five"}
aaMerged = kvs{"a1", "a-one", "a3", "a-three-diff", "a4", "a-four", "a5", "a-five", "a6", "a-six"}
mm1 = kvs{}
mm1a = kvs{"k1", kvs{"a", 0}}
mm1b = kvs{"k1", kvs{"b", 1}}
mm1Merged = kvs{"k1", kvs{"a", 0, "b", 1}}
mm2 = kvs{"k2", aa1, "k3", "k-three"}
mm2a = kvs{"k1", kvs{"a", 0}, "k2", aa1a, "k3", "k-three", "k4", "k-four"}
mm2b = kvs{"k1", kvs{"b", 1}, "k2", aa1b}
mm2Merged = kvs{"k1", kvs{"a", 0, "b", 1}, "k2", aaMerged, "k4", "k-four"}
)
type ThreeWayMergeSuite struct {
suite.Suite
create func(kvs) types.Value
create func(seq) types.Value
typeStr string
}
type ThreeWayMapMergeSuite struct {
ThreeWayMergeSuite
}
func (s *ThreeWayMapMergeSuite) SetupSuite() {
s.create = func(kv kvs) (val types.Value) {
if kv != nil {
keyValues := valsToTypesValues(s.create, kv...)
val = types.NewMap(keyValues...)
}
return
}
s.typeStr = "Map"
}
type ThreeWayStructMergeSuite struct {
ThreeWayMergeSuite
}
func (s *ThreeWayStructMergeSuite) SetupSuite() {
s.create = func(kv kvs) (val types.Value) {
if kv != nil {
fields := types.StructData{}
for i := 0; i < len(kv); i += 2 {
fields[kv[i].(string)] = valToTypesValue(s.create, kv[i+1])
}
val = types.NewStruct("TestStruct", fields)
}
return
}
s.typeStr = "struct"
}
func (s *ThreeWayMergeSuite) tryThreeWayMerge(a, b, p, exp kvs, vs types.ValueReadWriter) {
func (s *ThreeWayMergeSuite) tryThreeWayMerge(a, b, p, exp seq, vs types.ValueReadWriter) {
merged, err := ThreeWay(s.create(a), s.create(b), s.create(p), vs)
if s.NoError(err) {
expected := s.create(exp)
@@ -116,97 +34,23 @@ func (s *ThreeWayMergeSuite) tryThreeWayConflict(a, b, p types.Value, contained
}
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_DoNothing() {
s.tryThreeWayMerge(nil, nil, aa1, aa1, nil)
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_NoRecursion() {
s.tryThreeWayMerge(aa1a, aa1b, aa1, aaMerged, nil)
s.tryThreeWayMerge(aa1b, aa1a, aa1, aaMerged, nil)
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RecursiveCreate() {
s.tryThreeWayMerge(mm1a, mm1b, mm1, mm1Merged, nil)
s.tryThreeWayMerge(mm1b, mm1a, mm1, mm1Merged, nil)
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RecursiveCreateNil() {
s.tryThreeWayMerge(mm1a, mm1b, nil, mm1Merged, nil)
s.tryThreeWayMerge(mm1b, mm1a, nil, mm1Merged, nil)
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RecursiveMerge() {
s.tryThreeWayMerge(mm2a, mm2b, mm2, mm2Merged, nil)
s.tryThreeWayMerge(mm2b, mm2a, mm2, mm2Merged, nil)
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RefMerge() {
vs := types.NewTestValueStore()
strRef := vs.WriteValue(types.NewStruct("Foo", types.StructData{"life": types.Number(42)}))
m := kvs{"r2", vs.WriteValue(s.create(aa1))}
ma := kvs{"r1", strRef, "r2", vs.WriteValue(s.create(aa1a))}
mb := kvs{"r1", strRef, "r2", vs.WriteValue(s.create(aa1b))}
mMerged := kvs{"r1", strRef, "r2", vs.WriteValue(s.create(aaMerged))}
vs.Flush()
s.tryThreeWayMerge(ma, mb, m, mMerged, vs)
s.tryThreeWayMerge(mb, ma, m, mMerged, vs)
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RecursiveMultiLevelMerge() {
vs := types.NewTestValueStore()
m := kvs{"mm1", mm1, "mm2", vs.WriteValue(s.create(mm2))}
ma := kvs{"mm1", mm1a, "mm2", vs.WriteValue(s.create(mm2a))}
mb := kvs{"mm1", mm1b, "mm2", vs.WriteValue(s.create(mm2b))}
mMerged := kvs{"mm1", mm1Merged, "mm2", vs.WriteValue(s.create(mm2Merged))}
vs.Flush()
s.tryThreeWayMerge(ma, mb, m, mMerged, vs)
s.tryThreeWayMerge(mb, ma, m, mMerged, vs)
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_NilConflict() {
s.tryThreeWayConflict(nil, s.create(mm2b), s.create(mm2), "Cannot merge nil Value with")
s.tryThreeWayConflict(s.create(mm2a), nil, s.create(mm2), "with nil value.")
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_ImmediateConflict() {
s.tryThreeWayConflict(types.NewSet(), s.create(mm2b), s.create(mm2), "Cannot merge Set<> with "+s.typeStr)
s.tryThreeWayConflict(s.create(mm2b), types.NewSet(), s.create(mm2), "Cannot merge "+s.typeStr)
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_NestedConflict() {
a := mm2a.set("k2", types.NewSet())
s.tryThreeWayConflict(s.create(a), s.create(mm2b), s.create(mm2), types.EncodedValue(types.NewSet()))
s.tryThreeWayConflict(s.create(a), s.create(mm2b), s.create(mm2), types.EncodedValue(s.create(aa1b)))
}
func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_NestedConflictingOperation() {
a := mm2a.remove("k2")
s.tryThreeWayConflict(s.create(a), s.create(mm2b), s.create(mm2), `removed "k2"`)
s.tryThreeWayConflict(s.create(a), s.create(mm2b), s.create(mm2), `modded "k2"`)
}
func valsToTypesValues(f func(kvs) types.Value, kv ...interface{}) []types.Value {
func valsToTypesValues(f func(seq) types.Value, items ...interface{}) []types.Value {
keyValues := []types.Value{}
for _, e := range kv {
for _, e := range items {
v := valToTypesValue(f, e)
keyValues = append(keyValues, v)
}
return keyValues
}
func valToTypesValue(f func(kvs) types.Value, v interface{}) types.Value {
func valToTypesValue(f func(seq) types.Value, v interface{}) types.Value {
var v1 types.Value
switch t := v.(type) {
case string:
v1 = types.String(t)
case int:
v1 = types.Number(t)
case kvs:
case seq:
v1 = f(t)
case types.Value:
v1 = t