mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-15 10:12:06 +03:00
WIP operation with files
This commit is contained in:
parent
f1d4a19af8
commit
214abe4dea
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"`
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user