From 214abe4dea1984086e45d1399538fb12aa010642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Sat, 20 Feb 2021 15:48:44 +0100 Subject: [PATCH] WIP operation with files --- bug/op_add_comment.go | 2 ++ bug/op_create.go | 2 ++ bug/op_edit_comment.go | 2 ++ bug/operation.go | 17 +++++------- entity/dag/common_test.go | 15 +++++++---- entity/dag/operation.go | 9 +++++++ entity/dag/operation_pack.go | 45 ++++++++++++++++++++++++++++--- entity/dag/operation_pack_test.go | 20 ++++++++++++-- 8 files changed, 91 insertions(+), 21 deletions(-) diff --git a/bug/op_add_comment.go b/bug/op_add_comment.go index 4cba200f..f835866b 100644 --- a/bug/op_add_comment.go +++ b/bug/op_add_comment.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/entity/dag" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/text" @@ -12,6 +13,7 @@ import ( ) var _ Operation = &AddCommentOperation{} +var _ dag.OperationWithFiles = &AddCommentOperation{} // AddCommentOperation will add a new comment in the bug type AddCommentOperation struct { diff --git a/bug/op_create.go b/bug/op_create.go index 37e1ddc5..75b60bd8 100644 --- a/bug/op_create.go +++ b/bug/op_create.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/entity/dag" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/text" @@ -13,6 +14,7 @@ import ( ) var _ Operation = &CreateOperation{} +var _ dag.OperationWithFiles = &CreateOperation{} // CreateOperation define the initial creation of a bug type CreateOperation struct { diff --git a/bug/op_edit_comment.go b/bug/op_edit_comment.go index 653ab71e..3e6634e4 100644 --- a/bug/op_edit_comment.go +++ b/bug/op_edit_comment.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/entity/dag" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/timestamp" @@ -15,6 +16,7 @@ import ( ) var _ Operation = &EditCommentOperation{} +var _ dag.OperationWithFiles = &EditCommentOperation{} // EditCommentOperation will change a comment in the bug type EditCommentOperation struct { diff --git a/bug/operation.go b/bug/operation.go index d01f1cc9..8daa2cde 100644 --- a/bug/operation.go +++ b/bug/operation.go @@ -11,7 +11,6 @@ import ( "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/entity/dag" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/repository" ) // OperationType is an operation type identifier @@ -38,10 +37,9 @@ type Operation interface { // Time return the time when the operation was added Time() time.Time - // GetFiles return the files needed by this operation - GetFiles() []repository.Hash // Apply the operation to a Snapshot to create the final state Apply(snapshot *Snapshot) + // SetMetadata store arbitrary metadata about the operation SetMetadata(key string, value string) // GetMetadata retrieve arbitrary metadata about the operation @@ -212,11 +210,6 @@ func (base *OpBase) Time() time.Time { return time.Unix(base.UnixTime, 0) } -// GetFiles return the files needed by this operation -func (base *OpBase) GetFiles() []repository.Hash { - return nil -} - // Validate check the OpBase for errors func (base *OpBase) Validate(op Operation, opType OperationType) error { if base.OperationType != opType { @@ -235,9 +228,11 @@ func (base *OpBase) Validate(op Operation, opType OperationType) error { return errors.Wrap(err, "author") } - for _, hash := range op.GetFiles() { - if !hash.IsValid() { - return fmt.Errorf("file with invalid hash %v", hash) + if op, ok := op.(dag.OperationWithFiles); ok { + for _, hash := range op.GetFiles() { + if !hash.IsValid() { + return fmt.Errorf("file with invalid hash %v", hash) + } } } diff --git a/entity/dag/common_test.go b/entity/dag/common_test.go index fa15cd1f..1898451d 100644 --- a/entity/dag/common_test.go +++ b/entity/dag/common_test.go @@ -23,10 +23,11 @@ type op1 struct { OperationType int `json:"type"` Field1 string `json:"field_1"` + Files []repository.Hash } -func newOp1(author identity.Interface, field1 string) *op1 { - return &op1{author: author, OperationType: 1, Field1: field1} +func newOp1(author identity.Interface, field1 string, files ...repository.Hash) *op1 { + return &op1{author: author, OperationType: 1, Field1: field1, Files: files} } func (o *op1) Id() entity.Id { @@ -34,11 +35,15 @@ func (o *op1) Id() entity.Id { return entity.DeriveId(data) } +func (o *op1) Validate() error { return nil } + func (o *op1) Author() identity.Interface { return o.author } -func (o *op1) Validate() error { return nil } +func (o *op1) GetFiles() []repository.Hash { + return o.Files +} type op2 struct { author identity.Interface @@ -56,12 +61,12 @@ func (o *op2) Id() entity.Id { return entity.DeriveId(data) } +func (o *op2) Validate() error { return nil } + func (o *op2) Author() identity.Interface { return o.author } -func (o *op2) Validate() error { return nil } - func unmarshaler(author identity.Interface, raw json.RawMessage) (Operation, error) { var t struct { OperationType int `json:"type"` diff --git a/entity/dag/operation.go b/entity/dag/operation.go index 94974a82..1bfb3d3d 100644 --- a/entity/dag/operation.go +++ b/entity/dag/operation.go @@ -3,6 +3,7 @@ package dag import ( "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" + "github.com/MichaelMure/git-bug/repository" ) // Operation is a piece of data defining a change to reflect on the state of an Entity. @@ -33,3 +34,11 @@ type Operation interface { // Author returns the author of this operation Author() identity.Interface } + +// OperationWithFiles is an extended Operation that has files dependency, stored in git. +type OperationWithFiles interface { + Operation + + // GetFiles return the files needed by this operation + GetFiles() []repository.Hash +} diff --git a/entity/dag/operation_pack.go b/entity/dag/operation_pack.go index a436fd33..72063c60 100644 --- a/entity/dag/operation_pack.go +++ b/entity/dag/operation_pack.go @@ -15,10 +15,8 @@ import ( "github.com/MichaelMure/git-bug/util/lamport" ) -// TODO: extra data tree -const extraEntryName = "extra" - const opsEntryName = "ops" +const extraEntryName = "extra" const versionEntryPrefix = "version-" const createClockEntryPrefix = "create-clock-" const editClockEntryPrefix = "edit-clock-" @@ -118,6 +116,7 @@ func (opp *operationPack) Write(def Definition, repo repository.Repo, parentComm // Make a Git tree referencing this blob and encoding the other values: // - format version // - clocks + // - extra data tree := []repository.TreeEntry{ {ObjectType: repository.Blob, Hash: emptyBlobHash, Name: fmt.Sprintf(versionEntryPrefix+"%d", def.FormatVersion)}, @@ -133,6 +132,17 @@ func (opp *operationPack) Write(def Definition, repo repository.Repo, parentComm Name: fmt.Sprintf(createClockEntryPrefix+"%d", opp.CreateTime), }) } + if extraTree := opp.makeExtraTree(); len(extraTree) > 0 { + extraTreeHash, err := repo.StoreTree(extraTree) + if err != nil { + return "", err + } + tree = append(tree, repository.TreeEntry{ + ObjectType: repository.Tree, + Hash: extraTreeHash, + Name: extraEntryName, + }) + } // Store the tree treeHash, err := repo.StoreTree(tree) @@ -163,6 +173,35 @@ func (opp *operationPack) Write(def Definition, repo repository.Repo, parentComm return commitHash, nil } +func (opp *operationPack) makeExtraTree() []repository.TreeEntry { + var tree []repository.TreeEntry + counter := 0 + added := make(map[repository.Hash]interface{}) + + for _, ops := range opp.Operations { + ops, ok := ops.(OperationWithFiles) + if !ok { + continue + } + + for _, file := range ops.GetFiles() { + if _, has := added[file]; !has { + tree = append(tree, repository.TreeEntry{ + ObjectType: repository.Blob, + Hash: file, + // The name is not important here, we only need to + // reference the blob. + Name: fmt.Sprintf("file%d", counter), + }) + counter++ + added[file] = struct{}{} + } + } + } + + return tree +} + // readOperationPack read the operationPack encoded in git at the given Tree hash. // // Validity of the Lamport clocks is left for the caller to decide. diff --git a/entity/dag/operation_pack_test.go b/entity/dag/operation_pack_test.go index a12382af..0fe98dc7 100644 --- a/entity/dag/operation_pack_test.go +++ b/entity/dag/operation_pack_test.go @@ -1,6 +1,7 @@ package dag import ( + "math/rand" "testing" "github.com/stretchr/testify/require" @@ -11,10 +12,16 @@ import ( func TestOperationPackReadWrite(t *testing.T) { repo, id1, _, resolver, def := makeTestContext() + blobHash1, err := repo.StoreData(randomData()) + require.NoError(t, err) + + blobHash2, err := repo.StoreData(randomData()) + require.NoError(t, err) + opp := &operationPack{ Author: id1, Operations: []Operation{ - newOp1(id1, "foo"), + newOp1(id1, "foo", blobHash1, blobHash2), newOp2(id1, "bar"), }, CreateTime: 123, @@ -36,7 +43,7 @@ func TestOperationPackReadWrite(t *testing.T) { opp3 := &operationPack{ Author: id1, Operations: []Operation{ - newOp1(id1, "foo"), + newOp1(id1, "foo", blobHash1, blobHash2), newOp2(id1, "bar"), }, CreateTime: 123, @@ -86,3 +93,12 @@ func TestOperationPackSignedReadWrite(t *testing.T) { } require.Equal(t, opp.Id(), opp3.Id()) } + +func randomData() []byte { + var letterRunes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + b := make([]byte, 32) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return b +}