Skip to content

Commit

Permalink
Add Set support to merge.ThreeWay
Browse files Browse the repository at this point in the history
  • Loading branch information
cmasone-attic committed Aug 25, 2016
1 parent 2f66e67 commit 3f7c8f0
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 28 deletions.
45 changes: 41 additions & 4 deletions go/merge/three_way.go
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
61 changes: 37 additions & 24 deletions go/merge/three_way_test.go → go/merge/three_way_keyval_test.go
Expand Up @@ -19,8 +19,16 @@ func TestThreeWayStructMerge(t *testing.T) {
suite.Run(t, &ThreeWayStructMergeSuite{})
}

type seq interface {
items() []interface{}
}

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++ {
Expand Down Expand Up @@ -64,18 +72,22 @@ var (

type ThreeWayMergeSuite struct {
suite.Suite
create func(kvs) types.Value
create func(seq) types.Value
typeStr string
}

type ThreeWayMapMergeSuite struct {
type ThreeWayKeyValMergeSuite struct {
ThreeWayMergeSuite
}

type ThreeWayMapMergeSuite struct {
ThreeWayKeyValMergeSuite
}

func (s *ThreeWayMapMergeSuite) SetupSuite() {
s.create = func(kv kvs) (val types.Value) {
if kv != nil {
keyValues := valsToTypesValues(s.create, kv...)
s.create = func(seq seq) (val types.Value) {
if seq != nil {
keyValues := valsToTypesValues(s.create, seq.items()...)
val = types.NewMap(keyValues...)
}
return
Expand All @@ -84,12 +96,13 @@ func (s *ThreeWayMapMergeSuite) SetupSuite() {
}

type ThreeWayStructMergeSuite struct {
ThreeWayMergeSuite
ThreeWayKeyValMergeSuite
}

func (s *ThreeWayStructMergeSuite) SetupSuite() {
s.create = func(kv kvs) (val types.Value) {
if kv != nil {
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])
Expand All @@ -101,7 +114,7 @@ func (s *ThreeWayStructMergeSuite) SetupSuite() {
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)
Expand All @@ -116,31 +129,31 @@ func (s *ThreeWayMergeSuite) tryThreeWayConflict(a, b, p types.Value, contained
}
}

func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_DoNothing() {
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_DoNothing() {
s.tryThreeWayMerge(nil, nil, aa1, aa1, nil)
}

func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_NoRecursion() {
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_NoRecursion() {
s.tryThreeWayMerge(aa1a, aa1b, aa1, aaMerged, nil)
s.tryThreeWayMerge(aa1b, aa1a, aa1, aaMerged, nil)
}

func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RecursiveCreate() {
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_RecursiveCreate() {
s.tryThreeWayMerge(mm1a, mm1b, mm1, mm1Merged, nil)
s.tryThreeWayMerge(mm1b, mm1a, mm1, mm1Merged, nil)
}

func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RecursiveCreateNil() {
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_RecursiveCreateNil() {
s.tryThreeWayMerge(mm1a, mm1b, nil, mm1Merged, nil)
s.tryThreeWayMerge(mm1b, mm1a, nil, mm1Merged, nil)
}

func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RecursiveMerge() {
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_RecursiveMerge() {
s.tryThreeWayMerge(mm2a, mm2b, mm2, mm2Merged, nil)
s.tryThreeWayMerge(mm2b, mm2a, mm2, mm2Merged, nil)
}

func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RefMerge() {
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_RefMerge() {
vs := types.NewTestValueStore()

strRef := vs.WriteValue(types.NewStruct("Foo", types.StructData{"life": types.Number(42)}))
Expand All @@ -155,7 +168,7 @@ func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RefMerge() {
s.tryThreeWayMerge(mb, ma, m, mMerged, vs)
}

func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RecursiveMultiLevelMerge() {
func (s *ThreeWayKeyValMergeSuite) TestThreeWayMerge_RecursiveMultiLevelMerge() {
vs := types.NewTestValueStore()

m := kvs{"mm1", mm1, "mm2", vs.WriteValue(s.create(mm2))}
Expand All @@ -168,45 +181,45 @@ func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_RecursiveMultiLevelMerge() {
s.tryThreeWayMerge(mb, ma, m, mMerged, vs)
}

func (s *ThreeWayMergeSuite) TestThreeWayMergeMap_NilConflict() {
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 *ThreeWayMergeSuite) TestThreeWayMergeMap_ImmediateConflict() {
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 *ThreeWayMergeSuite) TestThreeWayMergeMap_NestedConflict() {
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 *ThreeWayMergeSuite) TestThreeWayMergeMap_NestedConflictingOperation() {
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"`)
}

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
Expand Down
112 changes: 112 additions & 0 deletions go/merge/three_way_set_test.go
@@ -0,0 +1,112 @@
// 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
}

func (kv items) remove(k interface{}) items {
out := make(items, 0, len(kv))
for i := 0; i < len(kv); i++ {
switch k := k.(type) {
default:
if kv[i] == k {
continue
}
case types.Value:
if item, ok := kv[i].(types.Value); ok && k.Equals(item) {
continue
}
}
out = append(out, kv[i])
}
return out
}

func (kv items) set(idx int, v interface{}) (out items) {
copy(out, kv)
out[idx] = v
return
}

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)
}

0 comments on commit 3f7c8f0

Please sign in to comment.