Add logic update and create logic

This commit is contained in:
mcrakhman 2022-07-10 15:53:50 +02:00 committed by Mikhail Iudin
parent e6534e134b
commit 43797b1b84
No known key found for this signature in database
GPG Key ID: FAAAA8BAABDFF1C0
9 changed files with 164 additions and 94 deletions

View File

@ -10,7 +10,6 @@ import (
type aclStateBuilder struct { type aclStateBuilder struct {
tree *Tree tree *Tree
aclState *ACLState
identity string identity string
key keys.EncryptionPrivKey key keys.EncryptionPrivKey
decoder keys.SigningPubKeyDecoder decoder keys.SigningPubKeyDecoder
@ -29,15 +28,28 @@ func newACLStateBuilder(decoder keys.SigningPubKeyDecoder, accountData *account.
} }
} }
func (sb *aclStateBuilder) build() (*ACLState, error) { func (sb *aclStateBuilder) Init(tree *Tree) error {
state, _, err := sb.buildBefore("") sb.tree = tree
return nil
}
func (sb *aclStateBuilder) Build() (*ACLState, error) {
state, _, err := sb.BuildBefore("")
return state, err return state, err
} }
func (sb *aclStateBuilder) init(tree *Tree) error { // TODO: we can probably have only one state builder, because we can Build both at the same time
root := tree.Root() func (sb *aclStateBuilder) BuildBefore(beforeId string) (*ACLState, bool, error) {
var (
err error
startChange = sb.tree.root
foundId bool
idSeenMap = make(map[string][]*Change)
decreasedPermissions *decreasedPermissionsParameters
)
root := sb.tree.Root()
if !root.IsSnapshot { if !root.IsSnapshot {
return fmt.Errorf("root should always be a snapshot") return nil, false, fmt.Errorf("root should always be a snapshot")
} }
snapshot := root.Content.GetAclData().GetAclSnapshot() snapshot := root.Content.GetAclData().GetAclSnapshot()
@ -47,27 +59,13 @@ func (sb *aclStateBuilder) init(tree *Tree) error {
sb.key, sb.key,
sb.decoder) sb.decoder)
if err != nil { if err != nil {
return fmt.Errorf("could not build ACLState from snapshot: %w", err) return nil, false, fmt.Errorf("could not build ACLState from snapshot: %w", err)
} }
sb.tree = tree
sb.aclState = state
return nil
}
// TODO: we can probably have only one state builder, because we can build both at the same time
func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error) {
var (
err error
startChange = sb.tree.root
foundId bool
idSeenMap = make(map[string][]*Change)
decreasedPermissions *decreasedPermissionsParameters
)
idSeenMap[startChange.Content.Identity] = append(idSeenMap[startChange.Content.Identity], startChange) idSeenMap[startChange.Content.Identity] = append(idSeenMap[startChange.Content.Identity], startChange)
if startChange.Content.GetChangesData() != nil { if startChange.Content.GetChangesData() != nil {
key, exists := sb.aclState.userReadKeys[startChange.Content.CurrentReadKeyHash] key, exists := state.userReadKeys[startChange.Content.CurrentReadKeyHash]
if !exists { if !exists {
return nil, false, fmt.Errorf("no first snapshot") return nil, false, fmt.Errorf("no first snapshot")
} }
@ -79,7 +77,7 @@ func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error)
} }
if beforeId == startChange.Id { if beforeId == startChange.Id {
return sb.aclState, true, nil return state, true, nil
} }
for { for {
@ -101,13 +99,13 @@ func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error)
idSeenMap[c.Content.Identity] = append(idSeenMap[c.Content.Identity], c) idSeenMap[c.Content.Identity] = append(idSeenMap[c.Content.Identity], c)
if c.Content.GetAclData() != nil { if c.Content.GetAclData() != nil {
err = sb.aclState.applyChange(c.Id, c.Content) err = state.applyChange(c.Id, c.Content)
if err != nil { if err != nil {
return false return false
} }
// if we have some users who have less permissions now // if we have some users who have less permissions now
users := sb.aclState.getPermissionDecreasedUsers(c.Content) users := state.getPermissionDecreasedUsers(c.Content)
if len(users) > 0 { if len(users) > 0 {
decreasedPermissions = &decreasedPermissionsParameters{ decreasedPermissions = &decreasedPermissionsParameters{
users: users, users: users,
@ -118,14 +116,14 @@ func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error)
} }
// the user can't make changes // the user can't make changes
if !sb.aclState.hasPermission(c.Content.Identity, pb.ACLChange_Writer) && !sb.aclState.hasPermission(c.Content.Identity, pb.ACLChange_Admin) { if !state.hasPermission(c.Content.Identity, pb.ACLChange_Writer) && !state.hasPermission(c.Content.Identity, pb.ACLChange_Admin) {
err = fmt.Errorf("user %s cannot make changes", c.Content.Identity) err = fmt.Errorf("user %s cannot make changes", c.Content.Identity)
return false return false
} }
// decrypting contents on the fly // decrypting contents on the fly
if c.Content.GetChangesData() != nil { if c.Content.GetChangesData() != nil {
key, exists := sb.aclState.userReadKeys[c.Content.CurrentReadKeyHash] key, exists := state.userReadKeys[c.Content.CurrentReadKeyHash]
if !exists { if !exists {
err = fmt.Errorf("failed to find key with hash: %d", c.Content.CurrentReadKeyHash) err = fmt.Errorf("failed to find key with hash: %d", c.Content.CurrentReadKeyHash)
return false return false
@ -169,7 +167,7 @@ func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error)
decreasedPermissions = nil decreasedPermissions = nil
if removed { if removed {
// starting from the beginning but with updated Tree // starting from the beginning but with updated Tree
return sb.buildBefore(beforeId) return sb.BuildBefore(beforeId)
} }
} else if err == nil { } else if err == nil {
// we can finish the acl state building process // we can finish the acl state building process
@ -185,5 +183,5 @@ func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error)
err = nil err = nil
} }
return sb.aclState, foundId, err return state, foundId, err
} }

View File

@ -19,8 +19,8 @@ type ACLContext struct {
func createTreeFromThread(t thread.Thread, fromStart bool) (*Tree, error) { func createTreeFromThread(t thread.Thread, fromStart bool) (*Tree, error) {
treeBuilder := newTreeBuilder(t, keys.NewEd25519Decoder()) treeBuilder := newTreeBuilder(t, keys.NewEd25519Decoder())
treeBuilder.init() treeBuilder.Init()
return treeBuilder.build(fromStart) return treeBuilder.Build(fromStart)
} }
func createACLStateFromThread( func createACLStateFromThread(
@ -40,16 +40,16 @@ func createACLStateFromThread(
} }
aclTreeBuilder := newACLTreeBuilder(t, decoder) aclTreeBuilder := newACLTreeBuilder(t, decoder)
aclTreeBuilder.init() aclTreeBuilder.Init()
aclTree, err := aclTreeBuilder.build() aclTree, err := aclTreeBuilder.Build()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !fromStart { if !fromStart {
snapshotValidator := newSnapshotValidator(decoder, accountData) snapshotValidator := newSnapshotValidator(decoder, accountData)
snapshotValidator.init(aclTree) snapshotValidator.Init(aclTree)
valid, err := snapshotValidator.validateSnapshot(tree.root) valid, err := snapshotValidator.ValidateSnapshot(tree.root)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -60,12 +60,12 @@ func createACLStateFromThread(
} }
aclBuilder := newACLStateBuilder(decoder, accountData) aclBuilder := newACLStateBuilder(decoder, accountData)
err = aclBuilder.init(tree) err = aclBuilder.Init(tree)
if err != nil { if err != nil {
return nil, err return nil, err
} }
aclState, err := aclBuilder.build() aclState, err := aclBuilder.Build()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -88,7 +88,7 @@ func TestACLStateBuilder_UserJoinBuild(t *testing.T) {
keys.NewEd25519Decoder(), keys.NewEd25519Decoder(),
false) false)
if err != nil { if err != nil {
t.Fatalf("should build acl ACLState without err: %v", err) t.Fatalf("should Build acl ACLState without err: %v", err)
} }
aclState := ctx.ACLState aclState := ctx.ACLState
//fmt.Println(ctx.Tree.Graph()) //fmt.Println(ctx.Tree.Graph())
@ -122,7 +122,7 @@ func TestACLStateBuilder_UserRemoveBuild(t *testing.T) {
keys.NewEd25519Decoder(), keys.NewEd25519Decoder(),
false) false)
if err != nil { if err != nil {
t.Fatalf("should build acl ACLState without err: %v", err) t.Fatalf("should Build acl ACLState without err: %v", err)
} }
aclState := ctx.ACLState aclState := ctx.ACLState
//fmt.Println(ctx.Tree.Graph()) //fmt.Println(ctx.Tree.Graph())
@ -152,7 +152,7 @@ func TestACLStateBuilder_UserRemoveBeforeBuild(t *testing.T) {
keys.NewEd25519Decoder(), keys.NewEd25519Decoder(),
false) false)
if err != nil { if err != nil {
t.Fatalf("should build acl ACLState without err: %v", err) t.Fatalf("should Build acl ACLState without err: %v", err)
} }
aclState := ctx.ACLState aclState := ctx.ACLState
//fmt.Println(ctx.Tree.Graph()) //fmt.Println(ctx.Tree.Graph())
@ -183,7 +183,7 @@ func TestACLStateBuilder_InvalidSnapshotBuild(t *testing.T) {
keys.NewEd25519Decoder(), keys.NewEd25519Decoder(),
false) false)
if err != nil { if err != nil {
t.Fatalf("should build acl ACLState without err: %v", err) t.Fatalf("should Build acl ACLState without err: %v", err)
} }
aclState := ctx.ACLState aclState := ctx.ACLState
//fmt.Println(ctx.Tree.Graph()) //fmt.Println(ctx.Tree.Graph())
@ -213,7 +213,7 @@ func TestACLStateBuilder_ValidSnapshotBuild(t *testing.T) {
keys.NewEd25519Decoder(), keys.NewEd25519Decoder(),
false) false)
if err != nil { if err != nil {
t.Fatalf("should build acl ACLState without err: %v", err) t.Fatalf("should Build acl ACLState without err: %v", err)
} }
aclState := ctx.ACLState aclState := ctx.ACLState
//fmt.Println(ctx.Tree.Graph()) //fmt.Println(ctx.Tree.Graph())

View File

@ -7,6 +7,7 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
"sync"
) )
type AddResultSummary int type AddResultSummary int
@ -18,17 +19,17 @@ const (
) )
type AddResult struct { type AddResult struct {
AttachedChanges []*Change OldHeads []string
InvalidChanges []*Change Heads []string
UnattachedChanges []*Change // TODO: add summary for changes
Summary AddResultSummary Summary AddResultSummary
} }
// TODO: Change add change content to include ACLChangeBuilder
type ACLTree interface { type ACLTree interface {
ACLState() *ACLState ACLState() *ACLState
AddContent(changeContent *ChangeContent) (*Change, error) AddContent(changeContent *ChangeContent) (*Change, error)
AddChanges(changes ...*Change) (AddResult, error) // TODO: Make change as interface AddChanges(changes ...*Change) (AddResult, error)
Heads() []string Heads() []string
Iterate(func(change *Change) bool) Iterate(func(change *Change) bool)
IterateFrom(string, func(change *Change) bool) IterateFrom(string, func(change *Change) bool)
@ -40,13 +41,15 @@ type aclTree struct {
accountData *account.AccountData accountData *account.AccountData
fullTree *Tree fullTree *Tree
aclTree *Tree // this tree is built from start of the document aclTree *Tree // TODO: right now we don't use it, we can probably have only local var for now. This tree is built from start of the document
aclState *ACLState aclState *ACLState
treeBuilder *treeBuilder treeBuilder *treeBuilder
aclTreeBuilder *aclTreeBuilder aclTreeBuilder *aclTreeBuilder
aclStateBuilder *aclStateBuilder aclStateBuilder *aclStateBuilder
snapshotValidator *snapshotValidator snapshotValidator *snapshotValidator
sync.Mutex
} }
func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) { func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) {
@ -74,14 +77,15 @@ func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) {
return aclTree, nil return aclTree, nil
} }
// TODO: this is not used for now, in future we should think about not making full tree rebuild
func (a *aclTree) rebuildFromTree(validateSnapshot bool) (err error) { func (a *aclTree) rebuildFromTree(validateSnapshot bool) (err error) {
if validateSnapshot { if validateSnapshot {
err = a.snapshotValidator.init(a.aclTree) err = a.snapshotValidator.Init(a.aclTree)
if err != nil { if err != nil {
return err return err
} }
valid, err := a.snapshotValidator.validateSnapshot(a.fullTree.root) valid, err := a.snapshotValidator.ValidateSnapshot(a.fullTree.root)
if err != nil { if err != nil {
return err return err
} }
@ -90,12 +94,12 @@ func (a *aclTree) rebuildFromTree(validateSnapshot bool) (err error) {
} }
} }
err = a.aclStateBuilder.init(a.fullTree) err = a.aclStateBuilder.Init(a.fullTree)
if err != nil { if err != nil {
return err return err
} }
a.aclState, err = a.aclStateBuilder.build() a.aclState, err = a.aclStateBuilder.Build()
if err != nil { if err != nil {
return err return err
} }
@ -104,28 +108,28 @@ func (a *aclTree) rebuildFromTree(validateSnapshot bool) (err error) {
} }
func (a *aclTree) rebuildFromThread(fromStart bool) error { func (a *aclTree) rebuildFromThread(fromStart bool) error {
a.treeBuilder.init() a.treeBuilder.Init()
a.aclTreeBuilder.init() a.aclTreeBuilder.Init()
var err error var err error
a.fullTree, err = a.treeBuilder.build(fromStart) a.fullTree, err = a.treeBuilder.Build(fromStart)
if err != nil { if err != nil {
return err return err
} }
// TODO: remove this from context as this is used only to validate snapshot // TODO: remove this from context as this is used only to validate snapshot
a.aclTree, err = a.aclTreeBuilder.build() a.aclTree, err = a.aclTreeBuilder.Build()
if err != nil { if err != nil {
return err return err
} }
if !fromStart { if !fromStart {
err = a.snapshotValidator.init(a.aclTree) err = a.snapshotValidator.Init(a.aclTree)
if err != nil { if err != nil {
return err return err
} }
valid, err := a.snapshotValidator.validateSnapshot(a.fullTree.root) valid, err := a.snapshotValidator.ValidateSnapshot(a.fullTree.root)
if err != nil { if err != nil {
return err return err
} }
@ -133,12 +137,12 @@ func (a *aclTree) rebuildFromThread(fromStart bool) error {
return a.rebuildFromThread(true) return a.rebuildFromThread(true)
} }
} }
err = a.aclStateBuilder.init(a.fullTree) err = a.aclStateBuilder.Init(a.fullTree)
if err != nil { if err != nil {
return err return err
} }
a.aclState, err = a.aclStateBuilder.build() a.aclState, err = a.aclStateBuilder.Build()
if err != nil { if err != nil {
return err return err
} }
@ -181,6 +185,8 @@ func (a *aclTree) ACLState() *ACLState {
func (a *aclTree) AddContent(changeContent *ChangeContent) (*Change, error) { func (a *aclTree) AddContent(changeContent *ChangeContent) (*Change, error) {
// TODO: add snapshot creation logic // TODO: add snapshot creation logic
a.Lock()
defer a.Unlock()
marshalled, err := changeContent.ChangesData.Marshal() marshalled, err := changeContent.ChangesData.Marshal()
if err != nil { if err != nil {
return nil, err return nil, err
@ -239,40 +245,100 @@ func (a *aclTree) AddContent(changeContent *ChangeContent) (*Change, error) {
} }
func (a *aclTree) AddChanges(changes ...*Change) (AddResult, error) { func (a *aclTree) AddChanges(changes ...*Change) (AddResult, error) {
a.Lock()
defer a.Unlock()
// TODO: make proper error handling, because there are a lot of corner cases where this will break
var aclChanges []*Change var aclChanges []*Change
var err error
defer func() {
if err != nil {
return
}
// removing attached or invalid orphans
var toRemove []string
for _, orphan := range a.thread.Orphans() {
if _, exists := a.fullTree.attached[orphan]; exists {
toRemove = append(toRemove, orphan)
}
if _, exists := a.fullTree.invalidChanges[orphan]; exists {
toRemove = append(toRemove, orphan)
}
}
a.thread.RemoveOrphans(toRemove...)
}()
for _, ch := range changes { for _, ch := range changes {
if ch.IsACLChange() { if ch.IsACLChange() {
aclChanges = append(aclChanges, ch) aclChanges = append(aclChanges, ch)
break break
} }
a.thread.A err = a.thread.AddChange(ch)
if err != nil {
return AddResult{}, err
}
a.thread.AddOrphans(ch.Id)
} }
// TODO: understand the common snapshot problem
prevHeads := a.fullTree.Heads() prevHeads := a.fullTree.Heads()
mode := a.fullTree.Add(changes...) mode := a.fullTree.Add(changes...)
switch mode { switch mode {
case Nothing: case Nothing:
return AddResult{Summary: AddResultSummaryNothing}, nil return AddResult{
OldHeads: prevHeads,
Heads: prevHeads,
Summary: AddResultSummaryNothing,
}, nil
case Rebuild: case Rebuild:
res, err := d.Build() err = a.rebuildFromThread(false)
return AddResult{Summary: Rebuild}, err if err != nil {
return AddResult{}, err
}
return AddResult{
OldHeads: prevHeads,
Heads: a.fullTree.Heads(),
Summary: AddResultSummaryRebuild,
}, nil
default: default:
break a.aclState, err = a.aclStateBuilder.Build()
if err != nil {
return AddResult{}, err
}
return AddResult{
OldHeads: prevHeads,
Heads: a.fullTree.Heads(),
Summary: AddResultSummaryAppend,
}, nil
} }
} }
func (a *aclTree) Iterate(f func(change *Change) bool) { func (a *aclTree) Iterate(f func(change *Change) bool) {
//TODO implement me a.Lock()
panic("implement me") defer a.Unlock()
a.fullTree.Iterate(a.fullTree.RootId(), f)
} }
func (a *aclTree) IterateFrom(s string, f func(change *Change) bool) { func (a *aclTree) IterateFrom(s string, f func(change *Change) bool) {
//TODO implement me a.Lock()
panic("implement me") defer a.Unlock()
a.fullTree.Iterate(s, f)
} }
func (a *aclTree) HasChange(s string) bool { func (a *aclTree) HasChange(s string) bool {
//TODO implement me a.Lock()
panic("implement me") defer a.Unlock()
_, attachedExists := a.fullTree.attached[s]
_, unattachedExists := a.fullTree.unAttached[s]
_, invalidExists := a.fullTree.invalidChanges[s]
return attachedExists || unattachedExists || invalidExists
}
func (a *aclTree) Heads() []string {
a.Lock()
defer a.Unlock()
return a.fullTree.Heads()
} }

View File

@ -30,16 +30,19 @@ func newACLTreeBuilder(t thread.Thread, decoder keys.SigningPubKeyDecoder) *aclT
} }
} }
func (tb *aclTreeBuilder) init() { func (tb *aclTreeBuilder) Init() {
tb.cache = make(map[string]*Change) tb.cache = make(map[string]*Change)
tb.identityKeys = make(map[string]keys.SigningPubKey) tb.identityKeys = make(map[string]keys.SigningPubKey)
tb.tree = &Tree{} tb.tree = &Tree{}
tb.changeLoader.init(tb.cache, tb.identityKeys) tb.changeLoader.Init(tb.cache, tb.identityKeys)
} }
func (tb *aclTreeBuilder) build() (*Tree, error) { func (tb *aclTreeBuilder) Build() (*Tree, error) {
heads := tb.thread.PossibleHeads() var headsAndOrphans []string
aclHeads, err := tb.getACLHeads(heads) headsAndOrphans = append(headsAndOrphans, tb.thread.Orphans()...)
headsAndOrphans = append(headsAndOrphans, tb.thread.Heads()...)
aclHeads, err := tb.getACLHeads(headsAndOrphans)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -30,7 +30,7 @@ func newChangeLoader(
} }
} }
func (c *changeLoader) init(cache map[string]*Change, func (c *changeLoader) Init(cache map[string]*Change,
identityKeys map[string]keys.SigningPubKey) { identityKeys map[string]keys.SigningPubKey) {
c.cache = cache c.cache = cache
c.identityKeys = identityKeys c.identityKeys = identityKeys

View File

@ -26,13 +26,13 @@ func newSnapshotValidator(
} }
} }
func (s *snapshotValidator) init(aclTree *Tree) error { func (s *snapshotValidator) Init(aclTree *Tree) error {
s.aclTree = aclTree s.aclTree = aclTree
return s.stateBuilder.init(aclTree) return s.stateBuilder.Init(aclTree)
} }
func (s *snapshotValidator) validateSnapshot(ch *Change) (bool, error) { func (s *snapshotValidator) ValidateSnapshot(ch *Change) (bool, error) {
st, found, err := s.stateBuilder.buildBefore(ch.Id) st, found, err := s.stateBuilder.BuildBefore(ch.Id)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -38,27 +38,29 @@ func newTreeBuilder(t thread.Thread, decoder keys.SigningPubKeyDecoder) *treeBui
} }
} }
func (tb *treeBuilder) init() { func (tb *treeBuilder) Init() {
tb.cache = make(map[string]*Change) tb.cache = make(map[string]*Change)
tb.identityKeys = make(map[string]keys.SigningPubKey) tb.identityKeys = make(map[string]keys.SigningPubKey)
tb.tree = &Tree{} tb.tree = &Tree{}
tb.changeLoader.init(tb.cache, tb.identityKeys) tb.changeLoader.Init(tb.cache, tb.identityKeys)
} }
func (tb *treeBuilder) build(fromStart bool) (*Tree, error) { func (tb *treeBuilder) Build(fromStart bool) (*Tree, error) {
heads := tb.thread.PossibleHeads() var headsAndOrphans []string
headsAndOrphans = append(headsAndOrphans, tb.thread.Orphans()...)
headsAndOrphans = append(headsAndOrphans, tb.thread.Heads()...)
if fromStart { if fromStart {
if err := tb.buildTreeFromStart(heads); err != nil { if err := tb.buildTreeFromStart(headsAndOrphans); err != nil {
return nil, fmt.Errorf("buildTree error: %v", err) return nil, fmt.Errorf("buildTree error: %v", err)
} }
} else { } else {
breakpoint, err := tb.findBreakpoint(heads) breakpoint, err := tb.findBreakpoint(headsAndOrphans)
if err != nil { if err != nil {
return nil, fmt.Errorf("findBreakpoint error: %v", err) return nil, fmt.Errorf("findBreakpoint error: %v", err)
} }
if err = tb.buildTree(heads, breakpoint); err != nil { if err = tb.buildTree(headsAndOrphans, breakpoint); err != nil {
return nil, fmt.Errorf("buildTree error: %v", err) return nil, fmt.Errorf("buildTree error: %v", err)
} }
} }

View File

@ -109,7 +109,7 @@ func (t *ThreadBuilder) AddRawChange(change *thread.RawChange) error {
return nil return nil
} }
func (t *ThreadBuilder) AddPossibleHead(head string) { func (t *ThreadBuilder) AddOrphans(head string) {
t.maybeHeads = append(t.maybeHeads, head) t.maybeHeads = append(t.maybeHeads, head)
} }
@ -140,7 +140,7 @@ func (t *ThreadBuilder) AddChange(change aclchanges.Change) error {
return nil return nil
} }
func (t *ThreadBuilder) PossibleHeads() []string { func (t *ThreadBuilder) Orphans() []string {
return t.maybeHeads return t.maybeHeads
} }

View File

@ -11,13 +11,14 @@ type Thread interface {
ID() string ID() string
Heads() []string Heads() []string
PossibleHeads() []string Orphans() []string
SetHeads(heads []string) SetHeads(heads []string)
SetPossibleHeads(heads []string) RemoveOrphans(orphan ...string)
AddPossibleHead(head string) AddOrphans(orphan ...string)
AddRawChange(change *RawChange) error AddRawChange(change *RawChange) error
AddChange(change aclchanges.Change) error AddChange(change aclchanges.Change) error
// TODO: have methods with raw changes also
GetChange(ctx context.Context, recordID string) (*RawChange, error) GetChange(ctx context.Context, recordID string) (*RawChange, error)
} }