WIP operation with files

This commit is contained in:
Michael Muré 2021-02-20 15:48:44 +01:00
parent f1d4a19af8
commit 214abe4dea
No known key found for this signature in database
GPG Key ID: A4457C029293126F
8 changed files with 91 additions and 21 deletions

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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,11 +228,13 @@ func (base *OpBase) Validate(op Operation, opType OperationType) error {
return errors.Wrap(err, "author")
}
if op, ok := op.(dag.OperationWithFiles); ok {
for _, hash := range op.GetFiles() {
if !hash.IsValid() {
return fmt.Errorf("file with invalid hash %v", hash)
}
}
}
if len(base.Nonce) > 64 {
return fmt.Errorf("nonce is too big")

View File

@ -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"`

View File

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

View File

@ -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.

View File

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