mirror of
https://github.com/anyproto/any-sync.git
synced 2025-06-09 09:35:03 +09:00
Tree builder fixes
This commit is contained in:
parent
3c6284c750
commit
c8a5002d8c
3 changed files with 137 additions and 14 deletions
|
@ -1091,6 +1091,109 @@ func TestObjectTree(t *testing.T) {
|
|||
require.Equal(t, "0", objTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("find correct common snapshot", func(t *testing.T) {
|
||||
// checking that adding old changes did not affect the tree
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "1", true, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "2", true, "2"),
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "3", true, "3"),
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "4", true, "4"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "5", true, "5"),
|
||||
changeCreator.CreateRaw("6.a.1", aclList.Head().Id, "6", true, "6"),
|
||||
changeCreator.CreateRaw("6.a.2", aclList.Head().Id, "6.a.1", true, "6.a.1"),
|
||||
changeCreator.CreateRaw("6.a.3", aclList.Head().Id, "6.a.2", true, "6.a.2"),
|
||||
changeCreator.CreateRaw("6.b.1", aclList.Head().Id, "6", true, "6"),
|
||||
changeCreator.CreateRaw("6.b.2", aclList.Head().Id, "6.b.1", true, "6.b.1"),
|
||||
changeCreator.CreateRaw("6.b.3", aclList.Head().Id, "6.b.2", true, "6.b.2"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{"6.b.3", "6.a.3"},
|
||||
RawChanges: rawChanges,
|
||||
}
|
||||
_, err := objTree.AddRawChanges(context.Background(), payload)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "6", objTree.Root().Id)
|
||||
|
||||
rawChanges = []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("4.a.1", aclList.Head().Id, "4", true, "4"),
|
||||
changeCreator.CreateRaw("4.a.2", aclList.Head().Id, "4.a.1", true, "4.a.1"),
|
||||
changeCreator.CreateRaw("4.a.3", aclList.Head().Id, "4.a.2", true, "4.a.2"),
|
||||
changeCreator.CreateRaw("4.b.1", aclList.Head().Id, "4", true, "4"),
|
||||
changeCreator.CreateRaw("4.b.2", aclList.Head().Id, "4.b.1", true, "4.b.1"),
|
||||
changeCreator.CreateRaw("4.b.3", aclList.Head().Id, "4.b.2", true, "4.b.2"),
|
||||
changeCreator.CreateRaw("5.a.1", aclList.Head().Id, "5", true, "5"),
|
||||
changeCreator.CreateRaw("5.a.2", aclList.Head().Id, "5.a.1", true, "5.a.1"),
|
||||
changeCreator.CreateRaw("5.a.3", aclList.Head().Id, "5.a.2", true, "5.a.2"),
|
||||
changeCreator.CreateRaw("5.b.1", aclList.Head().Id, "5", true, "5"),
|
||||
changeCreator.CreateRaw("5.b.2", aclList.Head().Id, "5.b.1", true, "5.b.1"),
|
||||
changeCreator.CreateRaw("5.b.3", aclList.Head().Id, "5.b.2", true, "5.b.2"),
|
||||
}
|
||||
payload = RawChangesPayload{
|
||||
NewHeads: []string{"4.b.3", "4.a.3", "5.b.3", "5.a.3"},
|
||||
RawChanges: rawChanges,
|
||||
}
|
||||
_, err = objTree.AddRawChanges(context.Background(), payload)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "4", objTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("find correct common snapshot with existing path", func(t *testing.T) {
|
||||
// checking that adding old changes did not affect the tree
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "1", true, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "2", true, "2"),
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "3", true, "3"),
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "4", true, "4"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "5", true, "5"),
|
||||
changeCreator.CreateRaw("6.a.1", aclList.Head().Id, "6", true, "6"),
|
||||
changeCreator.CreateRaw("6.a.2", aclList.Head().Id, "6.a.1", true, "6.a.1"),
|
||||
changeCreator.CreateRaw("6.a.3", aclList.Head().Id, "6.a.2", true, "6.a.2"),
|
||||
changeCreator.CreateRaw("6.b.1", aclList.Head().Id, "6", true, "6"),
|
||||
changeCreator.CreateRaw("6.b.2", aclList.Head().Id, "6.b.1", true, "6.b.1"),
|
||||
changeCreator.CreateRaw("6.b.3", aclList.Head().Id, "6.b.2", true, "6.b.2"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{"6.b.3", "6.a.3"},
|
||||
RawChanges: rawChanges,
|
||||
}
|
||||
_, err := objTree.AddRawChanges(context.Background(), payload)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "6", objTree.Root().Id)
|
||||
|
||||
rawChanges = []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("4.a.1", aclList.Head().Id, "4", true, "4"),
|
||||
changeCreator.CreateRaw("4.a.2", aclList.Head().Id, "4.a.1", true, "4.a.1"),
|
||||
changeCreator.CreateRaw("4.a.3", aclList.Head().Id, "4.a.2", true, "4.a.2"),
|
||||
changeCreator.CreateRaw("4.b.1", aclList.Head().Id, "4", true, "4"),
|
||||
changeCreator.CreateRaw("4.b.2", aclList.Head().Id, "4.b.1", true, "4.b.1"),
|
||||
changeCreator.CreateRaw("4.b.3", aclList.Head().Id, "4.b.2", true, "4.b.2"),
|
||||
changeCreator.CreateRaw("5.a.1", aclList.Head().Id, "5", true, "5"),
|
||||
changeCreator.CreateRaw("5.a.2", aclList.Head().Id, "5.a.1", true, "5.a.1"),
|
||||
changeCreator.CreateRaw("5.a.3", aclList.Head().Id, "5.a.2", true, "5.a.2"),
|
||||
changeCreator.CreateRaw("5.b.1", aclList.Head().Id, "5", true, "5"),
|
||||
changeCreator.CreateRaw("5.b.2", aclList.Head().Id, "5.b.1", true, "5.b.1"),
|
||||
changeCreator.CreateRaw("5.b.3", aclList.Head().Id, "5.b.2", true, "5.b.2"),
|
||||
}
|
||||
payload = RawChangesPayload{
|
||||
NewHeads: []string{"4.b.3", "4.a.3", "5.b.3", "5.a.3"},
|
||||
RawChanges: rawChanges,
|
||||
SnapshotPath: []string{"4", "3", "2", "1", "0"},
|
||||
}
|
||||
_, err = objTree.AddRawChanges(context.Background(), payload)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "4", objTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("their heads before common snapshot", func(t *testing.T) {
|
||||
// checking that adding old changes did not affect the tree
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
|
|
|
@ -76,6 +76,10 @@ func (tb *treeBuilder) build(opts treeBuilderOpts) (tr *Tree, err error) {
|
|||
}
|
||||
} else if !opts.full {
|
||||
if len(opts.theirSnapshotPath) == 0 {
|
||||
// this is actually not obvious why we should call this here
|
||||
// but the idea is if we have no snapshot path, then this can be only in cases
|
||||
// when we want to use a common snapshot, otherwise we would provide this path
|
||||
// because we always have a snapshot path
|
||||
if len(opts.ourSnapshotPath) == 0 {
|
||||
common, err := tb.storage.CommonSnapshot(tb.ctx)
|
||||
if err != nil {
|
||||
|
@ -83,7 +87,7 @@ func (tb *treeBuilder) build(opts treeBuilderOpts) (tr *Tree, err error) {
|
|||
}
|
||||
snapshot = common
|
||||
} else {
|
||||
our := opts.ourSnapshotPath[len(opts.ourSnapshotPath)-1]
|
||||
our := opts.ourSnapshotPath[0]
|
||||
lowest, err := tb.lowestSnapshots(cache, opts.theirHeads, our)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -161,9 +165,9 @@ func (tb *treeBuilder) lowestSnapshots(cache map[string]*Change, heads []string,
|
|||
return nil, err
|
||||
}
|
||||
if ch.SnapshotId != "" {
|
||||
next = append(next, ch.SnapshotId)
|
||||
snapshots = append(snapshots, ch.SnapshotId)
|
||||
} else if len(ch.PrevIds) == 0 && ch.Id == tb.storage.Id() { // this is a root change
|
||||
next = append(next, ch.Id)
|
||||
snapshots = append(snapshots, ch.Id)
|
||||
} else {
|
||||
return nil, fmt.Errorf("head with empty snapshot id: %s", ch.Id)
|
||||
}
|
||||
|
@ -172,19 +176,29 @@ func (tb *treeBuilder) lowestSnapshots(cache map[string]*Change, heads []string,
|
|||
slices.Sort(next)
|
||||
next = slice.DiscardDuplicatesSorted(next)
|
||||
current = make([]string, 0, len(next))
|
||||
var visited []*Change
|
||||
for len(next) > 0 {
|
||||
current = current[:0]
|
||||
current = append(current, next...)
|
||||
next = next[:0]
|
||||
for _, id := range current {
|
||||
if ch, ok := cache[id]; ok {
|
||||
next = append(current, ch.SnapshotId)
|
||||
// prevent accidental cycles, which can happen if there is a malicious change
|
||||
if ch.visited {
|
||||
return nil, fmt.Errorf("cycle detected in snapshot path: %s", id)
|
||||
}
|
||||
ch.visited = true
|
||||
visited = append(visited, ch)
|
||||
next = append(next, ch.SnapshotId)
|
||||
} else {
|
||||
// this is the lowest snapshot from the ones provided
|
||||
snapshots = append(snapshots, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ch := range visited {
|
||||
ch.visited = false
|
||||
}
|
||||
if ourSnapshot != "" {
|
||||
snapshots = append(snapshots, ourSnapshot)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue