bug: add the ability to store arbitrary metadata on an operation

Michael MurΓ© created

Change summary

bug/operation.go                 | 40 +++++++++++++++++++++++++--------
operations/add_comment.go        |  2 
operations/create.go             |  2 
operations/label_change.go       |  2 
operations/operations_test.go    |  2 
operations/set_status.go         |  2 
operations/set_title.go          |  2 
tests/operation_iterator_test.go |  3 ++
tests/operation_pack_test.go     | 20 +++++++++++++++++
9 files changed, 59 insertions(+), 16 deletions(-)

Detailed changes

bug/operation.go πŸ”—

@@ -36,18 +36,23 @@ type Operation interface {
 	Apply(snapshot Snapshot) Snapshot
 	// Validate check if the operation is valid (ex: a title is a single line)
 	Validate() error
+	// SetMetadata store arbitrary metadata about the operation
+	SetMetadata(key string, value string)
+	// GetMetadata retrieve arbitrary metadata about the operation
+	GetMetadata(key string) (string, bool)
 }
 
 // OpBase implement the common code for all operations
 type OpBase struct {
-	OperationType OperationType `json:"type"`
-	Author        Person        `json:"author"`
-	UnixTime      int64         `json:"timestamp"`
+	OperationType OperationType     `json:"type"`
+	Author        Person            `json:"author"`
+	UnixTime      int64             `json:"timestamp"`
+	Metadata      map[string]string `json:"metadata,omitempty"`
 }
 
 // NewOpBase is the constructor for an OpBase
-func NewOpBase(opType OperationType, author Person) OpBase {
-	return OpBase{
+func NewOpBase(opType OperationType, author Person) *OpBase {
+	return &OpBase{
 		OperationType: opType,
 		Author:        author,
 		UnixTime:      time.Now().Unix(),
@@ -55,27 +60,27 @@ func NewOpBase(opType OperationType, author Person) OpBase {
 }
 
 // OpType return the type of operation
-func (op OpBase) OpType() OperationType {
+func (op *OpBase) OpType() OperationType {
 	return op.OperationType
 }
 
 // Time return the time when the operation was added
-func (op OpBase) Time() time.Time {
+func (op *OpBase) Time() time.Time {
 	return time.Unix(op.UnixTime, 0)
 }
 
 // GetUnixTime return the unix timestamp when the operation was added
-func (op OpBase) GetUnixTime() int64 {
+func (op *OpBase) GetUnixTime() int64 {
 	return op.UnixTime
 }
 
 // GetAuthor return the author of the operation
-func (op OpBase) GetAuthor() Person {
+func (op *OpBase) GetAuthor() Person {
 	return op.Author
 }
 
 // GetFiles return the files needed by this operation
-func (op OpBase) GetFiles() []git.Hash {
+func (op *OpBase) GetFiles() []git.Hash {
 	return nil
 }
 
@@ -101,3 +106,18 @@ func OpBaseValidate(op Operation, opType OperationType) error {
 
 	return nil
 }
+
+// SetMetadata store arbitrary metadata about the operation
+func (op *OpBase) SetMetadata(key string, value string) {
+	if op.Metadata == nil {
+		op.Metadata = make(map[string]string)
+	}
+
+	op.Metadata[key] = value
+}
+
+// GetMetadata retrieve arbitrary metadata about the operation
+func (op *OpBase) GetMetadata(key string) (string, bool) {
+	val, ok := op.Metadata[key]
+	return val, ok
+}

operations/add_comment.go πŸ”—

@@ -13,7 +13,7 @@ import (
 var _ bug.Operation = AddCommentOperation{}
 
 type AddCommentOperation struct {
-	bug.OpBase
+	*bug.OpBase
 	Message string `json:"message"`
 	// TODO: change for a map[string]util.hash to store the filename ?
 	Files []git.Hash `json:"files"`

operations/create.go πŸ”—

@@ -14,7 +14,7 @@ import (
 var _ bug.Operation = CreateOperation{}
 
 type CreateOperation struct {
-	bug.OpBase
+	*bug.OpBase
 	Title   string     `json:"title"`
 	Message string     `json:"message"`
 	Files   []git.Hash `json:"files"`

operations/label_change.go πŸ”—

@@ -12,7 +12,7 @@ var _ bug.Operation = LabelChangeOperation{}
 
 // LabelChangeOperation define a Bug operation to add or remove labels
 type LabelChangeOperation struct {
-	bug.OpBase
+	*bug.OpBase
 	Added   []bug.Label `json:"added"`
 	Removed []bug.Label `json:"removed"`
 }

operations/operations_test.go πŸ”—

@@ -34,7 +34,7 @@ func TestValidate(t *testing.T) {
 		NewSetStatusOp(bug.Person{Name: "RenΓ© Descartes", Email: "rene@descartes.fr\u001b"}, bug.ClosedStatus),
 		NewSetStatusOp(bug.Person{Name: "RenΓ© \nDescartes", Email: "rene@descartes.fr"}, bug.ClosedStatus),
 		NewSetStatusOp(bug.Person{Name: "RenΓ© Descartes", Email: "rene@\ndescartes.fr"}, bug.ClosedStatus),
-		CreateOperation{OpBase: bug.OpBase{
+		CreateOperation{OpBase: &bug.OpBase{
 			Author:        rene,
 			UnixTime:      0,
 			OperationType: bug.CreateOp,

operations/set_status.go πŸ”—

@@ -10,7 +10,7 @@ import (
 var _ bug.Operation = SetStatusOperation{}
 
 type SetStatusOperation struct {
-	bug.OpBase
+	*bug.OpBase
 	Status bug.Status `json:"status"`
 }
 

operations/set_title.go πŸ”—

@@ -13,7 +13,7 @@ import (
 var _ bug.Operation = SetTitleOperation{}
 
 type SetTitleOperation struct {
-	bug.OpBase
+	*bug.OpBase
 	Title string `json:"title"`
 	Was   string `json:"was"`
 }

tests/operation_iterator_test.go πŸ”—

@@ -25,6 +25,7 @@ func TestOpIterator(t *testing.T) {
 
 	bug1 := bug.NewBug()
 
+	// first pack
 	bug1.Append(createOp)
 	bug1.Append(setTitleOp)
 	bug1.Append(addCommentOp)
@@ -32,11 +33,13 @@ func TestOpIterator(t *testing.T) {
 	bug1.Append(labelChangeOp)
 	bug1.Commit(mockRepo)
 
+	// second pack
 	bug1.Append(setTitleOp)
 	bug1.Append(setTitleOp)
 	bug1.Append(setTitleOp)
 	bug1.Commit(mockRepo)
 
+	// staging
 	bug1.Append(setTitleOp)
 	bug1.Append(setTitleOp)
 	bug1.Append(setTitleOp)

tests/operation_pack_test.go πŸ”—

@@ -6,6 +6,8 @@ import (
 	"testing"
 
 	"github.com/MichaelMure/git-bug/bug"
+	"github.com/MichaelMure/git-bug/operations"
+	"github.com/MichaelMure/git-bug/util/git"
 )
 
 func TestOperationPackSerialize(t *testing.T) {
@@ -17,6 +19,24 @@ func TestOperationPackSerialize(t *testing.T) {
 	opp.Append(setStatusOp)
 	opp.Append(labelChangeOp)
 
+	opMeta := operations.NewCreateOp(rene, "title", "message", nil)
+	opMeta.SetMetadata("key", "value")
+	opp.Append(opMeta)
+
+	if len(opMeta.Metadata) != 1 {
+		t.Fatal()
+	}
+
+	opFile := operations.NewCreateOp(rene, "title", "message", []git.Hash{
+		"abcdef",
+		"ghijkl",
+	})
+	opp.Append(opFile)
+
+	if len(opFile.Files) != 2 {
+		t.Fatal()
+	}
+
 	data, err := json.Marshal(opp)
 	if err != nil {
 		t.Fatal(err)