diff --git a/bug/op_set_metadata.go b/bug/op_set_metadata.go
new file mode 100644
index 0000000000000000000000000000000000000000..68cf1d51e2c9ce32f1c1674aac1b2a5dbf8b13ac
--- /dev/null
+++ b/bug/op_set_metadata.go
@@ -0,0 +1,72 @@
+package bug
+
+import "github.com/MichaelMure/git-bug/util/git"
+
+var _ Operation = &SetMetadataOperation{}
+
+type SetMetadataOperation struct {
+	OpBase
+	Target      git.Hash          `json:"target"`
+	NewMetadata map[string]string `json:"new_metadata"`
+}
+
+func (op *SetMetadataOperation) base() *OpBase {
+	return &op.OpBase
+}
+
+func (op *SetMetadataOperation) Hash() (git.Hash, error) {
+	return hashOperation(op)
+}
+
+func (op *SetMetadataOperation) Apply(snapshot *Snapshot) {
+	for _, target := range snapshot.Operations {
+		hash, err := target.Hash()
+		if err != nil {
+			// Should never error unless a programming error happened
+			// (covered in OpBase.Validate())
+			panic(err)
+		}
+
+		if hash == op.Target {
+			base := target.base()
+
+			if base.extraMetadata == nil {
+				base.extraMetadata = make(map[string]string)
+			}
+
+			for key, val := range op.NewMetadata {
+				if _, exist := base.extraMetadata[key]; !exist {
+					base.extraMetadata[key] = val
+				}
+			}
+
+			return
+		}
+	}
+}
+
+func (op *SetMetadataOperation) Validate() error {
+	if err := opBaseValidate(op, SetMetadataOp); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func NewSetMetadataOp(author Person, unixTime int64, target git.Hash, newMetadata map[string]string) *SetMetadataOperation {
+	return &SetMetadataOperation{
+		OpBase:      newOpBase(SetMetadataOp, author, unixTime),
+		Target:      target,
+		NewMetadata: newMetadata,
+	}
+}
+
+// Convenience function to apply the operation
+func SetMetadata(b Interface, author Person, unixTime int64, target git.Hash, newMetadata map[string]string) (*SetMetadataOperation, error) {
+	SetMetadataOp := NewSetMetadataOp(author, unixTime, target, newMetadata)
+	if err := SetMetadataOp.Validate(); err != nil {
+		return nil, err
+	}
+	b.Append(SetMetadataOp)
+	return SetMetadataOp, nil
+}
diff --git a/bug/op_set_metadata_test.go b/bug/op_set_metadata_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..068e2bb0bf63364dd41de53b839279c3a50eb6be
--- /dev/null
+++ b/bug/op_set_metadata_test.go
@@ -0,0 +1,98 @@
+package bug
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSetMetadata(t *testing.T) {
+	snapshot := Snapshot{}
+
+	var rene = Person{
+		Name:  "René Descartes",
+		Email: "rene@descartes.fr",
+	}
+
+	unix := time.Now().Unix()
+
+	create := NewCreateOp(rene, unix, "title", "create", nil)
+	create.SetMetadata("key", "value")
+	create.Apply(&snapshot)
+	snapshot.Operations = append(snapshot.Operations, create)
+
+	hash1, err := create.Hash()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	comment := NewAddCommentOp(rene, unix, "comment", nil)
+	comment.SetMetadata("key2", "value2")
+	comment.Apply(&snapshot)
+	snapshot.Operations = append(snapshot.Operations, comment)
+
+	hash2, err := comment.Hash()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	op1 := NewSetMetadataOp(rene, unix, hash1, map[string]string{
+		"key":  "override",
+		"key2": "value",
+	})
+
+	op1.Apply(&snapshot)
+	snapshot.Operations = append(snapshot.Operations, op1)
+
+	createMetadata := snapshot.Operations[0].AllMetadata()
+	assert.Equal(t, len(createMetadata), 2)
+	// original key is not overrided
+	assert.Equal(t, createMetadata["key"], "value")
+	// new key is set
+	assert.Equal(t, createMetadata["key2"], "value")
+
+	commentMetadata := snapshot.Operations[1].AllMetadata()
+	assert.Equal(t, len(commentMetadata), 1)
+	assert.Equal(t, commentMetadata["key2"], "value2")
+
+	op2 := NewSetMetadataOp(rene, unix, hash2, map[string]string{
+		"key2": "value",
+		"key3": "value3",
+	})
+
+	op2.Apply(&snapshot)
+	snapshot.Operations = append(snapshot.Operations, op2)
+
+	createMetadata = snapshot.Operations[0].AllMetadata()
+	assert.Equal(t, len(createMetadata), 2)
+	assert.Equal(t, createMetadata["key"], "value")
+	assert.Equal(t, createMetadata["key2"], "value")
+
+	commentMetadata = snapshot.Operations[1].AllMetadata()
+	assert.Equal(t, len(commentMetadata), 2)
+	// original key is not overrided
+	assert.Equal(t, commentMetadata["key2"], "value2")
+	// new key is set
+	assert.Equal(t, commentMetadata["key3"], "value3")
+
+	op3 := NewSetMetadataOp(rene, unix, hash1, map[string]string{
+		"key":  "override",
+		"key2": "override",
+	})
+
+	op3.Apply(&snapshot)
+	snapshot.Operations = append(snapshot.Operations, op3)
+
+	createMetadata = snapshot.Operations[0].AllMetadata()
+	assert.Equal(t, len(createMetadata), 2)
+	// original key is not overrided
+	assert.Equal(t, createMetadata["key"], "value")
+	// previously set key is not overrided
+	assert.Equal(t, createMetadata["key2"], "value")
+
+	commentMetadata = snapshot.Operations[1].AllMetadata()
+	assert.Equal(t, len(commentMetadata), 2)
+	assert.Equal(t, commentMetadata["key2"], "value2")
+	assert.Equal(t, commentMetadata["key3"], "value3")
+}
diff --git a/bug/operation.go b/bug/operation.go
index bb88af1f23859bf81dc342e7105829958c7a4d73..592b5616ea427bc9f79cd820427c6a5a3169b347 100644
--- a/bug/operation.go
+++ b/bug/operation.go
@@ -22,6 +22,7 @@ const (
 	LabelChangeOp
 	EditCommentOp
 	NoOpOp
+	SetMetadataOp
 )
 
 // Operation define the interface to fulfill for an edit operation of a Bug
@@ -75,11 +76,15 @@ func hashOperation(op Operation) (git.Hash, error) {
 
 // OpBase implement the common code for all operations
 type OpBase struct {
-	OperationType OperationType `json:"type"`
-	Author        Person        `json:"author"`
-	UnixTime      int64         `json:"timestamp"`
-	hash          git.Hash
+	OperationType OperationType     `json:"type"`
+	Author        Person            `json:"author"`
+	UnixTime      int64             `json:"timestamp"`
 	Metadata      map[string]string `json:"metadata,omitempty"`
+	// Not serialized. Store the op's hash in memory.
+	hash git.Hash
+	// Not serialized. Store the extra metadata compiled from SetMetadataOperation
+	// in memory.
+	extraMetadata map[string]string
 }
 
 // newOpBase is the constructor for an OpBase
@@ -146,10 +151,29 @@ func (op *OpBase) SetMetadata(key string, value string) {
 // GetMetadata retrieve arbitrary metadata about the operation
 func (op *OpBase) GetMetadata(key string) (string, bool) {
 	val, ok := op.Metadata[key]
+
+	if ok {
+		return val, true
+	}
+
+	// extraMetadata can't replace the original operations value if any
+	val, ok = op.extraMetadata[key]
+
 	return val, ok
 }
 
 // AllMetadata return all metadata for this operation
 func (op *OpBase) AllMetadata() map[string]string {
-	return op.Metadata
+	result := make(map[string]string)
+
+	for key, val := range op.extraMetadata {
+		result[key] = val
+	}
+
+	// Original metadata take precedence
+	for key, val := range op.Metadata {
+		result[key] = val
+	}
+
+	return result
 }