merge package operations into bug, they are tightly coupled anyway

Michael MurΓ© created

Change summary

Gopkg.lock                                 |   9 
bug/bug.go                                 |   4 
bug/bug_actions_test.go                    | 143 ++++----
bug/bug_test.go                            |  57 +-
bug/op_add_comment.go                      |  27 +
bug/op_create.go                           |  27 +
bug/op_create_test.go                      |  11 
bug/op_label_change.go                     |  35 +-
bug/op_set_status.go                       |  63 ++++
bug/op_set_title.go                        |  27 +
bug/operation.go                           |  28 -
bug/operation_iterator_test.go             |  20 
bug/operation_pack.go                      |  53 ++-
bug/operation_pack_test.go                 |  19 
bug/operation_test.go                      |  32 +-
cache/bug_cache.go                         |  15 
cache/repo_cache.go                        |   3 
graphql/gqlgen.yml                         |  10 
graphql/graph/gen_graph.go                 | 103 +++---
graphql/resolvers/operations.go            |  23 
misc/random_bugs/create_random_bugs.go     |  17 
operations/operations.go                   |  17 -
operations/set_status.go                   |  60 ----
termui/show_bug.go                         |  22 
tests/read_bugs_test.go                    |  26 +
vendor/github.com/go-test/deep/.gitignore  |   2 
vendor/github.com/go-test/deep/.travis.yml |  13 
vendor/github.com/go-test/deep/CHANGES.md  |   9 
vendor/github.com/go-test/deep/LICENSE     |  21 +
vendor/github.com/go-test/deep/README.md   |  51 +++
vendor/github.com/go-test/deep/deep.go     | 352 ++++++++++++++++++++++++
31 files changed, 881 insertions(+), 418 deletions(-)

Detailed changes

Gopkg.lock πŸ”—

@@ -65,6 +65,14 @@
   revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
   version = "v1.7.0"
 
+[[projects]]
+  digest = "1:7f89e0c888fb99c61055c646f5678aae645b0b0a1443d9b2dcd9964d850827ce"
+  name = "github.com/go-test/deep"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "6592d9cc0a499ad2d5f574fde80a2b5c5cc3b4f5"
+  version = "v1.0.1"
+
 [[projects]]
   digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d"
   name = "github.com/golang/protobuf"
@@ -385,6 +393,7 @@
     "github.com/cheekybits/genny/generic",
     "github.com/dustin/go-humanize",
     "github.com/fatih/color",
+    "github.com/go-test/deep",
     "github.com/gorilla/mux",
     "github.com/icrowley/fake",
     "github.com/jroimartin/gocui",

bug/bug.go πŸ”—

@@ -300,7 +300,7 @@ func (bug *Bug) Validate() error {
 
 	// The very first Op should be a CreateOp
 	firstOp := bug.FirstOp()
-	if firstOp == nil || firstOp.OpType() != CreateOp {
+	if firstOp == nil || firstOp.base().OperationType != CreateOp {
 		return fmt.Errorf("first operation should be a Create op")
 	}
 
@@ -308,7 +308,7 @@ func (bug *Bug) Validate() error {
 	it := NewOperationIterator(bug)
 	createCount := 0
 	for it.Next() {
-		if it.Value().OpType() == CreateOp {
+		if it.Value().base().OperationType == CreateOp {
 			createCount++
 		}
 	}

tests/bug_actions_test.go β†’ bug/bug_actions_test.go πŸ”—

@@ -1,14 +1,11 @@
-package tests
+package bug
 
 import (
+	"github.com/MichaelMure/git-bug/repository"
 	"io/ioutil"
 	"log"
 	"os"
 	"testing"
-
-	"github.com/MichaelMure/git-bug/bug"
-	"github.com/MichaelMure/git-bug/operations"
-	"github.com/MichaelMure/git-bug/repository"
 )
 
 func createRepo(bare bool) *repository.GitRepo {
@@ -71,37 +68,37 @@ func TestPushPull(t *testing.T) {
 	repoA, repoB, remote := setupRepos(t)
 	defer cleanupRepos(repoA, repoB, remote)
 
-	bug1, err := operations.Create(rene, unix, "bug1", "message")
+	bug1, err := Create(rene, unix, "bug1", "message")
 	checkErr(t, err)
 	err = bug1.Commit(repoA)
 	checkErr(t, err)
 
 	// A --> remote --> B
-	_, err = bug.Push(repoA, "origin")
+	_, err = Push(repoA, "origin")
 	checkErr(t, err)
 
-	err = bug.Pull(repoB, "origin")
+	err = Pull(repoB, "origin")
 	checkErr(t, err)
 
-	bugs := allBugs(t, bug.ReadAllLocalBugs(repoB))
+	bugs := allBugs(t, ReadAllLocalBugs(repoB))
 
 	if len(bugs) != 1 {
 		t.Fatal("Unexpected number of bugs")
 	}
 
 	// B --> remote --> A
-	bug2, err := operations.Create(rene, unix, "bug2", "message")
+	bug2, err := Create(rene, unix, "bug2", "message")
 	checkErr(t, err)
 	err = bug2.Commit(repoB)
 	checkErr(t, err)
 
-	_, err = bug.Push(repoB, "origin")
+	_, err = Push(repoB, "origin")
 	checkErr(t, err)
 
-	err = bug.Pull(repoA, "origin")
+	err = Pull(repoA, "origin")
 	checkErr(t, err)
 
-	bugs = allBugs(t, bug.ReadAllLocalBugs(repoA))
+	bugs = allBugs(t, ReadAllLocalBugs(repoA))
 
 	if len(bugs) != 2 {
 		t.Fatal("Unexpected number of bugs")
@@ -114,8 +111,8 @@ func checkErr(t testing.TB, err error) {
 	}
 }
 
-func allBugs(t testing.TB, bugs <-chan bug.StreamedBug) []*bug.Bug {
-	var result []*bug.Bug
+func allBugs(t testing.TB, bugs <-chan StreamedBug) []*Bug {
+	var result []*Bug
 	for streamed := range bugs {
 		if streamed.Err != nil {
 			t.Fatal(streamed.Err)
@@ -139,43 +136,43 @@ func _RebaseTheirs(t testing.TB) {
 	repoA, repoB, remote := setupRepos(t)
 	defer cleanupRepos(repoA, repoB, remote)
 
-	bug1, err := operations.Create(rene, unix, "bug1", "message")
+	bug1, err := Create(rene, unix, "bug1", "message")
 	checkErr(t, err)
 	err = bug1.Commit(repoA)
 	checkErr(t, err)
 
 	// A --> remote
-	_, err = bug.Push(repoA, "origin")
+	_, err = Push(repoA, "origin")
 	checkErr(t, err)
 
 	// remote --> B
-	err = bug.Pull(repoB, "origin")
+	err = Pull(repoB, "origin")
 	checkErr(t, err)
 
-	bug2, err := bug.ReadLocalBug(repoB, bug1.Id())
+	bug2, err := ReadLocalBug(repoB, bug1.Id())
 	checkErr(t, err)
 
-	operations.Comment(bug2, rene, unix, "message2")
-	operations.Comment(bug2, rene, unix, "message3")
-	operations.Comment(bug2, rene, unix, "message4")
+	AddComment(bug2, rene, unix, "message2")
+	AddComment(bug2, rene, unix, "message3")
+	AddComment(bug2, rene, unix, "message4")
 	err = bug2.Commit(repoB)
 	checkErr(t, err)
 
 	// B --> remote
-	_, err = bug.Push(repoB, "origin")
+	_, err = Push(repoB, "origin")
 	checkErr(t, err)
 
 	// remote --> A
-	err = bug.Pull(repoA, "origin")
+	err = Pull(repoA, "origin")
 	checkErr(t, err)
 
-	bugs := allBugs(t, bug.ReadAllLocalBugs(repoB))
+	bugs := allBugs(t, ReadAllLocalBugs(repoB))
 
 	if len(bugs) != 1 {
 		t.Fatal("Unexpected number of bugs")
 	}
 
-	bug3, err := bug.ReadLocalBug(repoA, bug1.Id())
+	bug3, err := ReadLocalBug(repoA, bug1.Id())
 	checkErr(t, err)
 
 	if nbOps(bug3) != 4 {
@@ -197,48 +194,48 @@ func _RebaseOurs(t testing.TB) {
 	repoA, repoB, remote := setupRepos(t)
 	defer cleanupRepos(repoA, repoB, remote)
 
-	bug1, err := operations.Create(rene, unix, "bug1", "message")
+	bug1, err := Create(rene, unix, "bug1", "message")
 	checkErr(t, err)
 	err = bug1.Commit(repoA)
 	checkErr(t, err)
 
 	// A --> remote
-	_, err = bug.Push(repoA, "origin")
+	_, err = Push(repoA, "origin")
 	checkErr(t, err)
 
 	// remote --> B
-	err = bug.Pull(repoB, "origin")
+	err = Pull(repoB, "origin")
 	checkErr(t, err)
 
-	operations.Comment(bug1, rene, unix, "message2")
-	operations.Comment(bug1, rene, unix, "message3")
-	operations.Comment(bug1, rene, unix, "message4")
+	AddComment(bug1, rene, unix, "message2")
+	AddComment(bug1, rene, unix, "message3")
+	AddComment(bug1, rene, unix, "message4")
 	err = bug1.Commit(repoA)
 	checkErr(t, err)
 
-	operations.Comment(bug1, rene, unix, "message5")
-	operations.Comment(bug1, rene, unix, "message6")
-	operations.Comment(bug1, rene, unix, "message7")
+	AddComment(bug1, rene, unix, "message5")
+	AddComment(bug1, rene, unix, "message6")
+	AddComment(bug1, rene, unix, "message7")
 	err = bug1.Commit(repoA)
 	checkErr(t, err)
 
-	operations.Comment(bug1, rene, unix, "message8")
-	operations.Comment(bug1, rene, unix, "message9")
-	operations.Comment(bug1, rene, unix, "message10")
+	AddComment(bug1, rene, unix, "message8")
+	AddComment(bug1, rene, unix, "message9")
+	AddComment(bug1, rene, unix, "message10")
 	err = bug1.Commit(repoA)
 	checkErr(t, err)
 
 	// remote --> A
-	err = bug.Pull(repoA, "origin")
+	err = Pull(repoA, "origin")
 	checkErr(t, err)
 
-	bugs := allBugs(t, bug.ReadAllLocalBugs(repoA))
+	bugs := allBugs(t, ReadAllLocalBugs(repoA))
 
 	if len(bugs) != 1 {
 		t.Fatal("Unexpected number of bugs")
 	}
 
-	bug2, err := bug.ReadLocalBug(repoA, bug1.Id())
+	bug2, err := ReadLocalBug(repoA, bug1.Id())
 	checkErr(t, err)
 
 	if nbOps(bug2) != 10 {
@@ -246,8 +243,8 @@ func _RebaseOurs(t testing.TB) {
 	}
 }
 
-func nbOps(b *bug.Bug) int {
-	it := bug.NewOperationIterator(b)
+func nbOps(b *Bug) int {
+	it := NewOperationIterator(b)
 	counter := 0
 	for it.Next() {
 		counter++
@@ -269,73 +266,73 @@ func _RebaseConflict(t testing.TB) {
 	repoA, repoB, remote := setupRepos(t)
 	defer cleanupRepos(repoA, repoB, remote)
 
-	bug1, err := operations.Create(rene, unix, "bug1", "message")
+	bug1, err := Create(rene, unix, "bug1", "message")
 	checkErr(t, err)
 	err = bug1.Commit(repoA)
 	checkErr(t, err)
 
 	// A --> remote
-	_, err = bug.Push(repoA, "origin")
+	_, err = Push(repoA, "origin")
 	checkErr(t, err)
 
 	// remote --> B
-	err = bug.Pull(repoB, "origin")
+	err = Pull(repoB, "origin")
 	checkErr(t, err)
 
-	operations.Comment(bug1, rene, unix, "message2")
-	operations.Comment(bug1, rene, unix, "message3")
-	operations.Comment(bug1, rene, unix, "message4")
+	AddComment(bug1, rene, unix, "message2")
+	AddComment(bug1, rene, unix, "message3")
+	AddComment(bug1, rene, unix, "message4")
 	err = bug1.Commit(repoA)
 	checkErr(t, err)
 
-	operations.Comment(bug1, rene, unix, "message5")
-	operations.Comment(bug1, rene, unix, "message6")
-	operations.Comment(bug1, rene, unix, "message7")
+	AddComment(bug1, rene, unix, "message5")
+	AddComment(bug1, rene, unix, "message6")
+	AddComment(bug1, rene, unix, "message7")
 	err = bug1.Commit(repoA)
 	checkErr(t, err)
 
-	operations.Comment(bug1, rene, unix, "message8")
-	operations.Comment(bug1, rene, unix, "message9")
-	operations.Comment(bug1, rene, unix, "message10")
+	AddComment(bug1, rene, unix, "message8")
+	AddComment(bug1, rene, unix, "message9")
+	AddComment(bug1, rene, unix, "message10")
 	err = bug1.Commit(repoA)
 	checkErr(t, err)
 
-	bug2, err := bug.ReadLocalBug(repoB, bug1.Id())
+	bug2, err := ReadLocalBug(repoB, bug1.Id())
 	checkErr(t, err)
 
-	operations.Comment(bug2, rene, unix, "message11")
-	operations.Comment(bug2, rene, unix, "message12")
-	operations.Comment(bug2, rene, unix, "message13")
+	AddComment(bug2, rene, unix, "message11")
+	AddComment(bug2, rene, unix, "message12")
+	AddComment(bug2, rene, unix, "message13")
 	err = bug2.Commit(repoB)
 	checkErr(t, err)
 
-	operations.Comment(bug2, rene, unix, "message14")
-	operations.Comment(bug2, rene, unix, "message15")
-	operations.Comment(bug2, rene, unix, "message16")
+	AddComment(bug2, rene, unix, "message14")
+	AddComment(bug2, rene, unix, "message15")
+	AddComment(bug2, rene, unix, "message16")
 	err = bug2.Commit(repoB)
 	checkErr(t, err)
 
-	operations.Comment(bug2, rene, unix, "message17")
-	operations.Comment(bug2, rene, unix, "message18")
-	operations.Comment(bug2, rene, unix, "message19")
+	AddComment(bug2, rene, unix, "message17")
+	AddComment(bug2, rene, unix, "message18")
+	AddComment(bug2, rene, unix, "message19")
 	err = bug2.Commit(repoB)
 	checkErr(t, err)
 
 	// A --> remote
-	_, err = bug.Push(repoA, "origin")
+	_, err = Push(repoA, "origin")
 	checkErr(t, err)
 
 	// remote --> B
-	err = bug.Pull(repoB, "origin")
+	err = Pull(repoB, "origin")
 	checkErr(t, err)
 
-	bugs := allBugs(t, bug.ReadAllLocalBugs(repoB))
+	bugs := allBugs(t, ReadAllLocalBugs(repoB))
 
 	if len(bugs) != 1 {
 		t.Fatal("Unexpected number of bugs")
 	}
 
-	bug3, err := bug.ReadLocalBug(repoB, bug1.Id())
+	bug3, err := ReadLocalBug(repoB, bug1.Id())
 	checkErr(t, err)
 
 	if nbOps(bug3) != 19 {
@@ -343,20 +340,20 @@ func _RebaseConflict(t testing.TB) {
 	}
 
 	// B --> remote
-	_, err = bug.Push(repoB, "origin")
+	_, err = Push(repoB, "origin")
 	checkErr(t, err)
 
 	// remote --> A
-	err = bug.Pull(repoA, "origin")
+	err = Pull(repoA, "origin")
 	checkErr(t, err)
 
-	bugs = allBugs(t, bug.ReadAllLocalBugs(repoA))
+	bugs = allBugs(t, ReadAllLocalBugs(repoA))
 
 	if len(bugs) != 1 {
 		t.Fatal("Unexpected number of bugs")
 	}
 
-	bug4, err := bug.ReadLocalBug(repoA, bug1.Id())
+	bug4, err := ReadLocalBug(repoA, bug1.Id())
 	checkErr(t, err)
 
 	if nbOps(bug4) != 19 {

tests/bug_test.go β†’ bug/bug_test.go πŸ”—

@@ -1,8 +1,8 @@
-package tests
+package bug
 
 import (
-	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/repository"
+	"github.com/go-test/deep"
 
 	"testing"
 )
@@ -10,7 +10,7 @@ import (
 func TestBugId(t *testing.T) {
 	mockRepo := repository.NewMockRepoForTest()
 
-	bug1 := bug.NewBug()
+	bug1 := NewBug()
 
 	bug1.Append(createOp)
 
@@ -26,7 +26,7 @@ func TestBugId(t *testing.T) {
 func TestBugValidity(t *testing.T) {
 	mockRepo := repository.NewMockRepoForTest()
 
-	bug1 := bug.NewBug()
+	bug1 := NewBug()
 
 	if bug1.Validate() == nil {
 		t.Fatal("Empty bug should be invalid")
@@ -55,27 +55,28 @@ func TestBugValidity(t *testing.T) {
 	}
 }
 
-//func TestBugSerialisation(t *testing.T) {
-//	bug1, err := bug.NewBug()
-//	if err != nil {
-//		t.Error(err)
-//	}
-//
-//	bug1.Append(createOp)
-//	bug1.Append(setTitleOp)
-//	bug1.Append(setTitleOp)
-//	bug1.Append(addCommentOp)
-//
-//	repo := repository.NewMockRepoForTest()
-//
-//	bug1.Commit(repo)
-//
-//	bug2, err := bug.ReadBug(repo, bug.BugsRefPattern+bug1.Id())
-//	if err != nil {
-//		t.Error(err)
-//	}
-//
-//	if !reflect.DeepEqual(bug1, bug2) {
-//		t.Fatalf("%v different than %v", bug1, bug2)
-//	}
-//}
+func TestBugSerialisation(t *testing.T) {
+	bug1 := NewBug()
+
+	bug1.Append(createOp)
+	bug1.Append(setTitleOp)
+	bug1.Append(setTitleOp)
+	bug1.Append(addCommentOp)
+
+	repo := repository.NewMockRepoForTest()
+
+	bug1.Commit(repo)
+
+	bug2, err := ReadLocalBug(repo, bug1.Id())
+	if err != nil {
+		t.Error(err)
+	}
+
+	// ignore some fields
+	bug2.packs[0].commitHash = bug1.packs[0].commitHash
+
+	deep.CompareUnexportedFields = true
+	if diff := deep.Equal(bug1, bug2); diff != nil {
+		t.Fatal(diff)
+	}
+}

operations/add_comment.go β†’ bug/op_add_comment.go πŸ”—

@@ -1,26 +1,29 @@
-package operations
+package bug
 
 import (
 	"fmt"
 
-	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/util/git"
 	"github.com/MichaelMure/git-bug/util/text"
 )
 
 // AddCommentOperation will add a new comment in the bug
 
-var _ bug.Operation = AddCommentOperation{}
+var _ Operation = AddCommentOperation{}
 
 type AddCommentOperation struct {
-	*bug.OpBase
+	*OpBase
 	Message string `json:"message"`
 	// TODO: change for a map[string]util.hash to store the filename ?
 	Files []git.Hash `json:"files"`
 }
 
-func (op AddCommentOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
-	comment := bug.Comment{
+func (op AddCommentOperation) base() *OpBase {
+	return op.OpBase
+}
+
+func (op AddCommentOperation) Apply(snapshot Snapshot) Snapshot {
+	comment := Comment{
 		Message:  op.Message,
 		Author:   op.Author,
 		Files:    op.Files,
@@ -37,7 +40,7 @@ func (op AddCommentOperation) GetFiles() []git.Hash {
 }
 
 func (op AddCommentOperation) Validate() error {
-	if err := bug.OpBaseValidate(op, bug.AddCommentOp); err != nil {
+	if err := opBaseValidate(op, AddCommentOp); err != nil {
 		return err
 	}
 
@@ -52,20 +55,20 @@ func (op AddCommentOperation) Validate() error {
 	return nil
 }
 
-func NewAddCommentOp(author bug.Person, unixTime int64, message string, files []git.Hash) AddCommentOperation {
+func NewAddCommentOp(author Person, unixTime int64, message string, files []git.Hash) AddCommentOperation {
 	return AddCommentOperation{
-		OpBase:  bug.NewOpBase(bug.AddCommentOp, author, unixTime),
+		OpBase:  newOpBase(AddCommentOp, author, unixTime),
 		Message: message,
 		Files:   files,
 	}
 }
 
 // Convenience function to apply the operation
-func Comment(b bug.Interface, author bug.Person, unixTime int64, message string) error {
-	return CommentWithFiles(b, author, unixTime, message, nil)
+func AddComment(b Interface, author Person, unixTime int64, message string) error {
+	return AddCommentWithFiles(b, author, unixTime, message, nil)
 }
 
-func CommentWithFiles(b bug.Interface, author bug.Person, unixTime int64, message string, files []git.Hash) error {
+func AddCommentWithFiles(b Interface, author Person, unixTime int64, message string, files []git.Hash) error {
 	addCommentOp := NewAddCommentOp(author, unixTime, message, files)
 	if err := addCommentOp.Validate(); err != nil {
 		return err

operations/create.go β†’ bug/op_create.go πŸ”—

@@ -1,28 +1,31 @@
-package operations
+package bug
 
 import (
 	"fmt"
 	"strings"
 
-	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/util/git"
 	"github.com/MichaelMure/git-bug/util/text"
 )
 
 // CreateOperation define the initial creation of a bug
 
-var _ bug.Operation = CreateOperation{}
+var _ Operation = CreateOperation{}
 
 type CreateOperation struct {
-	*bug.OpBase
+	*OpBase
 	Title   string     `json:"title"`
 	Message string     `json:"message"`
 	Files   []git.Hash `json:"files"`
 }
 
-func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
+func (op CreateOperation) base() *OpBase {
+	return op.OpBase
+}
+
+func (op CreateOperation) Apply(snapshot Snapshot) Snapshot {
 	snapshot.Title = op.Title
-	snapshot.Comments = []bug.Comment{
+	snapshot.Comments = []Comment{
 		{
 			Message:  op.Message,
 			Author:   op.Author,
@@ -39,7 +42,7 @@ func (op CreateOperation) GetFiles() []git.Hash {
 }
 
 func (op CreateOperation) Validate() error {
-	if err := bug.OpBaseValidate(op, bug.CreateOp); err != nil {
+	if err := opBaseValidate(op, CreateOp); err != nil {
 		return err
 	}
 
@@ -62,9 +65,9 @@ func (op CreateOperation) Validate() error {
 	return nil
 }
 
-func NewCreateOp(author bug.Person, unixTime int64, title, message string, files []git.Hash) CreateOperation {
+func NewCreateOp(author Person, unixTime int64, title, message string, files []git.Hash) CreateOperation {
 	return CreateOperation{
-		OpBase:  bug.NewOpBase(bug.CreateOp, author, unixTime),
+		OpBase:  newOpBase(CreateOp, author, unixTime),
 		Title:   title,
 		Message: message,
 		Files:   files,
@@ -72,12 +75,12 @@ func NewCreateOp(author bug.Person, unixTime int64, title, message string, files
 }
 
 // Convenience function to apply the operation
-func Create(author bug.Person, unixTime int64, title, message string) (*bug.Bug, error) {
+func Create(author Person, unixTime int64, title, message string) (*Bug, error) {
 	return CreateWithFiles(author, unixTime, title, message, nil)
 }
 
-func CreateWithFiles(author bug.Person, unixTime int64, title, message string, files []git.Hash) (*bug.Bug, error) {
-	newBug := bug.NewBug()
+func CreateWithFiles(author Person, unixTime int64, title, message string, files []git.Hash) (*Bug, error) {
+	newBug := NewBug()
 	createOp := NewCreateOp(author, unixTime, title, message, files)
 
 	if err := createOp.Validate(); err != nil {

operations/create_test.go β†’ bug/op_create_test.go πŸ”—

@@ -1,16 +1,15 @@
-package operations
+package bug
 
 import (
-	"github.com/MichaelMure/git-bug/bug"
 	"reflect"
 	"testing"
 	"time"
 )
 
 func TestCreate(t *testing.T) {
-	snapshot := bug.Snapshot{}
+	snapshot := Snapshot{}
 
-	var rene = bug.Person{
+	var rene = Person{
 		Name:  "RenΓ© Descartes",
 		Email: "rene@descartes.fr",
 	}
@@ -21,9 +20,9 @@ func TestCreate(t *testing.T) {
 
 	snapshot = create.Apply(snapshot)
 
-	expected := bug.Snapshot{
+	expected := Snapshot{
 		Title: "title",
-		Comments: []bug.Comment{
+		Comments: []Comment{
 			{Author: rene, Message: "message", UnixTime: create.UnixTime},
 		},
 		Author:    rene,

operations/label_change.go β†’ bug/op_label_change.go πŸ”—

@@ -1,24 +1,27 @@
-package operations
+package bug
 
 import (
 	"fmt"
 	"sort"
 
-	"github.com/MichaelMure/git-bug/bug"
 	"github.com/pkg/errors"
 )
 
-var _ bug.Operation = LabelChangeOperation{}
+var _ Operation = LabelChangeOperation{}
 
 // LabelChangeOperation define a Bug operation to add or remove labels
 type LabelChangeOperation struct {
-	*bug.OpBase
-	Added   []bug.Label `json:"added"`
-	Removed []bug.Label `json:"removed"`
+	*OpBase
+	Added   []Label `json:"added"`
+	Removed []Label `json:"removed"`
+}
+
+func (op LabelChangeOperation) base() *OpBase {
+	return op.OpBase
 }
 
 // Apply apply the operation
-func (op LabelChangeOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
+func (op LabelChangeOperation) Apply(snapshot Snapshot) Snapshot {
 	// Add in the set
 AddLoop:
 	for _, added := range op.Added {
@@ -51,7 +54,7 @@ AddLoop:
 }
 
 func (op LabelChangeOperation) Validate() error {
-	if err := bug.OpBaseValidate(op, bug.LabelChangeOp); err != nil {
+	if err := opBaseValidate(op, LabelChangeOp); err != nil {
 		return err
 	}
 
@@ -74,23 +77,23 @@ func (op LabelChangeOperation) Validate() error {
 	return nil
 }
 
-func NewLabelChangeOperation(author bug.Person, unixTime int64, added, removed []bug.Label) LabelChangeOperation {
+func NewLabelChangeOperation(author Person, unixTime int64, added, removed []Label) LabelChangeOperation {
 	return LabelChangeOperation{
-		OpBase:  bug.NewOpBase(bug.LabelChangeOp, author, unixTime),
+		OpBase:  newOpBase(LabelChangeOp, author, unixTime),
 		Added:   added,
 		Removed: removed,
 	}
 }
 
 // ChangeLabels is a convenience function to apply the operation
-func ChangeLabels(b bug.Interface, author bug.Person, unixTime int64, add, remove []string) ([]LabelChangeResult, error) {
-	var added, removed []bug.Label
+func ChangeLabels(b Interface, author Person, unixTime int64, add, remove []string) ([]LabelChangeResult, error) {
+	var added, removed []Label
 	var results []LabelChangeResult
 
 	snap := b.Compile()
 
 	for _, str := range add {
-		label := bug.Label(str)
+		label := Label(str)
 
 		// check for duplicate
 		if labelExist(added, label) {
@@ -109,7 +112,7 @@ func ChangeLabels(b bug.Interface, author bug.Person, unixTime int64, add, remov
 	}
 
 	for _, str := range remove {
-		label := bug.Label(str)
+		label := Label(str)
 
 		// check for duplicate
 		if labelExist(removed, label) {
@@ -142,7 +145,7 @@ func ChangeLabels(b bug.Interface, author bug.Person, unixTime int64, add, remov
 	return results, nil
 }
 
-func labelExist(labels []bug.Label, label bug.Label) bool {
+func labelExist(labels []Label, label Label) bool {
 	for _, l := range labels {
 		if l == label {
 			return true
@@ -164,7 +167,7 @@ const (
 )
 
 type LabelChangeResult struct {
-	Label  bug.Label
+	Label  Label
 	Status LabelChangeStatus
 }
 

bug/op_set_status.go πŸ”—

@@ -0,0 +1,63 @@
+package bug
+
+import (
+	"github.com/pkg/errors"
+)
+
+// SetStatusOperation will change the status of a bug
+
+var _ Operation = SetStatusOperation{}
+
+type SetStatusOperation struct {
+	*OpBase
+	Status Status `json:"status"`
+}
+
+func (op SetStatusOperation) base() *OpBase {
+	return op.OpBase
+}
+
+func (op SetStatusOperation) Apply(snapshot Snapshot) Snapshot {
+	snapshot.Status = op.Status
+
+	return snapshot
+}
+
+func (op SetStatusOperation) Validate() error {
+	if err := opBaseValidate(op, SetStatusOp); err != nil {
+		return err
+	}
+
+	if err := op.Status.Validate(); err != nil {
+		return errors.Wrap(err, "status")
+	}
+
+	return nil
+}
+
+func NewSetStatusOp(author Person, unixTime int64, status Status) SetStatusOperation {
+	return SetStatusOperation{
+		OpBase: newOpBase(SetStatusOp, author, unixTime),
+		Status: status,
+	}
+}
+
+// Convenience function to apply the operation
+func Open(b Interface, author Person, unixTime int64) error {
+	op := NewSetStatusOp(author, unixTime, OpenStatus)
+	if err := op.Validate(); err != nil {
+		return err
+	}
+	b.Append(op)
+	return nil
+}
+
+// Convenience function to apply the operation
+func Close(b Interface, author Person, unixTime int64) error {
+	op := NewSetStatusOp(author, unixTime, ClosedStatus)
+	if err := op.Validate(); err != nil {
+		return err
+	}
+	b.Append(op)
+	return nil
+}

operations/set_title.go β†’ bug/op_set_title.go πŸ”—

@@ -1,31 +1,34 @@
-package operations
+package bug
 
 import (
 	"fmt"
 	"strings"
 
-	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/util/text"
 )
 
 // SetTitleOperation will change the title of a bug
 
-var _ bug.Operation = SetTitleOperation{}
+var _ Operation = SetTitleOperation{}
 
 type SetTitleOperation struct {
-	*bug.OpBase
+	*OpBase
 	Title string `json:"title"`
 	Was   string `json:"was"`
 }
 
-func (op SetTitleOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
+func (op SetTitleOperation) base() *OpBase {
+	return op.OpBase
+}
+
+func (op SetTitleOperation) Apply(snapshot Snapshot) Snapshot {
 	snapshot.Title = op.Title
 
 	return snapshot
 }
 
 func (op SetTitleOperation) Validate() error {
-	if err := bug.OpBaseValidate(op, bug.SetTitleOp); err != nil {
+	if err := opBaseValidate(op, SetTitleOp); err != nil {
 		return err
 	}
 
@@ -52,22 +55,22 @@ func (op SetTitleOperation) Validate() error {
 	return nil
 }
 
-func NewSetTitleOp(author bug.Person, unixTime int64, title string, was string) SetTitleOperation {
+func NewSetTitleOp(author Person, unixTime int64, title string, was string) SetTitleOperation {
 	return SetTitleOperation{
-		OpBase: bug.NewOpBase(bug.SetTitleOp, author, unixTime),
+		OpBase: newOpBase(SetTitleOp, author, unixTime),
 		Title:  title,
 		Was:    was,
 	}
 }
 
 // Convenience function to apply the operation
-func SetTitle(b bug.Interface, author bug.Person, unixTime int64, title string) error {
-	it := bug.NewOperationIterator(b)
+func SetTitle(b Interface, author Person, unixTime int64, title string) error {
+	it := NewOperationIterator(b)
 
-	var lastTitleOp bug.Operation
+	var lastTitleOp Operation
 	for it.Next() {
 		op := it.Value()
-		if op.OpType() == bug.SetTitleOp {
+		if op.base().OperationType == SetTitleOp {
 			lastTitleOp = op
 		}
 	}

bug/operation.go πŸ”—

@@ -22,14 +22,12 @@ const (
 
 // Operation define the interface to fulfill for an edit operation of a Bug
 type Operation interface {
-	// OpType return the type of operation
-	OpType() OperationType
+	// base return the OpBase of the Operation, for package internal use
+	base() *OpBase
 	// Time return the time when the operation was added
 	Time() time.Time
 	// GetUnixTime return the unix timestamp when the operation was added
 	GetUnixTime() int64
-	// GetAuthor return the author of the operation
-	GetAuthor() Person
 	// GetFiles return the files needed by this operation
 	GetFiles() []git.Hash
 	// Apply the operation to a Snapshot to create the final state
@@ -50,8 +48,8 @@ type OpBase struct {
 	Metadata      map[string]string `json:"metadata,omitempty"`
 }
 
-// NewOpBase is the constructor for an OpBase
-func NewOpBase(opType OperationType, author Person, unixTime int64) *OpBase {
+// newOpBase is the constructor for an OpBase
+func newOpBase(opType OperationType, author Person, unixTime int64) *OpBase {
 	return &OpBase{
 		OperationType: opType,
 		Author:        author,
@@ -59,11 +57,6 @@ func NewOpBase(opType OperationType, author Person, unixTime int64) *OpBase {
 	}
 }
 
-// OpType return the type of operation
-func (op *OpBase) OpType() OperationType {
-	return op.OperationType
-}
-
 // Time return the time when the operation was added
 func (op *OpBase) Time() time.Time {
 	return time.Unix(op.UnixTime, 0)
@@ -74,27 +67,22 @@ func (op *OpBase) GetUnixTime() int64 {
 	return op.UnixTime
 }
 
-// GetAuthor return the author of the operation
-func (op *OpBase) GetAuthor() Person {
-	return op.Author
-}
-
 // GetFiles return the files needed by this operation
 func (op *OpBase) GetFiles() []git.Hash {
 	return nil
 }
 
 // Validate check the OpBase for errors
-func OpBaseValidate(op Operation, opType OperationType) error {
-	if op.OpType() != opType {
-		return fmt.Errorf("incorrect operation type (expected: %v, actual: %v)", opType, op.OpType())
+func opBaseValidate(op Operation, opType OperationType) error {
+	if op.base().OperationType != opType {
+		return fmt.Errorf("incorrect operation type (expected: %v, actual: %v)", opType, op.base().OperationType)
 	}
 
 	if op.GetUnixTime() == 0 {
 		return fmt.Errorf("time not set")
 	}
 
-	if err := op.GetAuthor().Validate(); err != nil {
+	if err := op.base().Author.Validate(); err != nil {
 		return errors.Wrap(err, "author")
 	}
 

tests/operation_iterator_test.go β†’ bug/operation_iterator_test.go πŸ”—

@@ -1,32 +1,30 @@
-package tests
+package bug
 
 import (
-	"github.com/MichaelMure/git-bug/bug"
-	"github.com/MichaelMure/git-bug/operations"
 	"github.com/MichaelMure/git-bug/repository"
 	"testing"
 	"time"
 )
 
 var (
-	rene = bug.Person{
+	rene = Person{
 		Name:  "RenΓ© Descartes",
 		Email: "rene@descartes.fr",
 	}
 
 	unix = time.Now().Unix()
 
-	createOp      = operations.NewCreateOp(rene, unix, "title", "message", nil)
-	setTitleOp    = operations.NewSetTitleOp(rene, unix, "title2", "title1")
-	addCommentOp  = operations.NewAddCommentOp(rene, unix, "message2", nil)
-	setStatusOp   = operations.NewSetStatusOp(rene, unix, bug.ClosedStatus)
-	labelChangeOp = operations.NewLabelChangeOperation(rene, unix, []bug.Label{"added"}, []bug.Label{"removed"})
+	createOp      = NewCreateOp(rene, unix, "title", "message", nil)
+	setTitleOp    = NewSetTitleOp(rene, unix, "title2", "title1")
+	addCommentOp  = NewAddCommentOp(rene, unix, "message2", nil)
+	setStatusOp   = NewSetStatusOp(rene, unix, ClosedStatus)
+	labelChangeOp = NewLabelChangeOperation(rene, unix, []Label{"added"}, []Label{"removed"})
 )
 
 func TestOpIterator(t *testing.T) {
 	mockRepo := repository.NewMockRepoForTest()
 
-	bug1 := bug.NewBug()
+	bug1 := NewBug()
 
 	// first pack
 	bug1.Append(createOp)
@@ -47,7 +45,7 @@ func TestOpIterator(t *testing.T) {
 	bug1.Append(setTitleOp)
 	bug1.Append(setTitleOp)
 
-	it := bug.NewOperationIterator(bug1)
+	it := NewOperationIterator(bug1)
 
 	counter := 0
 	for it.Next() {

bug/operation_pack.go πŸ”—

@@ -3,7 +3,6 @@ package bug
 import (
 	"encoding/json"
 	"fmt"
-	"reflect"
 
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/git"
@@ -25,17 +24,6 @@ type OperationPack struct {
 	commitHash git.Hash
 }
 
-// hold the different operation type to instantiate to parse JSON
-var operations map[OperationType]reflect.Type
-
-// Register will register a new type of Operation to be able to parse the corresponding JSON
-func Register(t OperationType, op interface{}) {
-	if operations == nil {
-		operations = make(map[OperationType]reflect.Type)
-	}
-	operations[t] = reflect.TypeOf(op)
-}
-
 func (opp *OperationPack) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
 		Version    uint        `json:"version"`
@@ -69,25 +57,44 @@ func (opp *OperationPack) UnmarshalJSON(data []byte) error {
 			return err
 		}
 
-		opType, ok := operations[t.OperationType]
-		if !ok {
-			return fmt.Errorf("unknown operation type %v", t.OperationType)
-		}
-
-		op := reflect.New(opType).Interface()
-
-		if err := json.Unmarshal(raw, op); err != nil {
+		op, err := opp.unmarshalOp(raw, t.OperationType)
+		if err != nil {
 			return err
 		}
 
-		deref := reflect.ValueOf(op).Elem().Interface()
-
-		opp.Operations = append(opp.Operations, deref.(Operation))
+		opp.Operations = append(opp.Operations, op)
 	}
 
 	return nil
 }
 
+func (opp *OperationPack) unmarshalOp(raw []byte, _type OperationType) (Operation, error) {
+	switch _type {
+	case CreateOp:
+		op := CreateOperation{}
+		err := json.Unmarshal(raw, &op)
+		return op, err
+	case SetTitleOp:
+		op := SetTitleOperation{}
+		err := json.Unmarshal(raw, &op)
+		return op, err
+	case AddCommentOp:
+		op := AddCommentOperation{}
+		err := json.Unmarshal(raw, &op)
+		return op, err
+	case SetStatusOp:
+		op := SetStatusOperation{}
+		err := json.Unmarshal(raw, &op)
+		return op, err
+	case LabelChangeOp:
+		op := LabelChangeOperation{}
+		err := json.Unmarshal(raw, &op)
+		return op, err
+	default:
+		return nil, fmt.Errorf("unknown operation type %v", _type)
+	}
+}
+
 // Append a new operation to the pack
 func (opp *OperationPack) Append(op Operation) {
 	opp.Operations = append(opp.Operations, op)

tests/operation_pack_test.go β†’ bug/operation_pack_test.go πŸ”—

@@ -1,17 +1,15 @@
-package tests
+package bug
 
 import (
 	"encoding/json"
-	"reflect"
 	"testing"
 
-	"github.com/MichaelMure/git-bug/bug"
-	"github.com/MichaelMure/git-bug/operations"
 	"github.com/MichaelMure/git-bug/util/git"
+	"github.com/go-test/deep"
 )
 
 func TestOperationPackSerialize(t *testing.T) {
-	opp := &bug.OperationPack{}
+	opp := &OperationPack{}
 
 	opp.Append(createOp)
 	opp.Append(setTitleOp)
@@ -19,7 +17,7 @@ func TestOperationPackSerialize(t *testing.T) {
 	opp.Append(setStatusOp)
 	opp.Append(labelChangeOp)
 
-	opMeta := operations.NewCreateOp(rene, unix, "title", "message", nil)
+	opMeta := NewCreateOp(rene, unix, "title", "message", nil)
 	opMeta.SetMetadata("key", "value")
 	opp.Append(opMeta)
 
@@ -27,7 +25,7 @@ func TestOperationPackSerialize(t *testing.T) {
 		t.Fatal()
 	}
 
-	opFile := operations.NewCreateOp(rene, unix, "title", "message", []git.Hash{
+	opFile := NewCreateOp(rene, unix, "title", "message", []git.Hash{
 		"abcdef",
 		"ghijkl",
 	})
@@ -42,13 +40,14 @@ func TestOperationPackSerialize(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	var opp2 *bug.OperationPack
+	var opp2 *OperationPack
 	err = json.Unmarshal(data, &opp2)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	if !reflect.DeepEqual(opp, opp2) {
-		t.Fatalf("%v and %v are different", opp, opp2)
+	deep.CompareUnexportedFields = false
+	if diff := deep.Equal(opp, opp2); diff != nil {
+		t.Fatal(diff)
 	}
 }

operations/operations_test.go β†’ bug/operation_test.go πŸ”—

@@ -1,27 +1,26 @@
-package operations
+package bug
 
 import (
 	"testing"
 	"time"
 
-	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/util/git"
 )
 
 func TestValidate(t *testing.T) {
-	rene := bug.Person{
+	rene := Person{
 		Name:  "RenΓ© Descartes",
 		Email: "rene@descartes.fr",
 	}
 
 	unix := time.Now().Unix()
 
-	good := []bug.Operation{
+	good := []Operation{
 		NewCreateOp(rene, unix, "title", "message", nil),
 		NewSetTitleOp(rene, unix, "title2", "title1"),
 		NewAddCommentOp(rene, unix, "message2", nil),
-		NewSetStatusOp(rene, unix, bug.ClosedStatus),
-		NewLabelChangeOperation(rene, unix, []bug.Label{"added"}, []bug.Label{"removed"}),
+		NewSetStatusOp(rene, unix, ClosedStatus),
+		NewLabelChangeOperation(rene, unix, []Label{"added"}, []Label{"removed"}),
 	}
 
 	for _, op := range good {
@@ -30,17 +29,17 @@ func TestValidate(t *testing.T) {
 		}
 	}
 
-	bad := []bug.Operation{
+	bad := []Operation{
 		// opbase
-		NewSetStatusOp(bug.Person{Name: "", Email: "rene@descartes.fr"}, unix, bug.ClosedStatus),
-		NewSetStatusOp(bug.Person{Name: "RenΓ© Descartes\u001b", Email: "rene@descartes.fr"}, unix, bug.ClosedStatus),
-		NewSetStatusOp(bug.Person{Name: "RenΓ© Descartes", Email: "rene@descartes.fr\u001b"}, unix, bug.ClosedStatus),
-		NewSetStatusOp(bug.Person{Name: "RenΓ© \nDescartes", Email: "rene@descartes.fr"}, unix, bug.ClosedStatus),
-		NewSetStatusOp(bug.Person{Name: "RenΓ© Descartes", Email: "rene@\ndescartes.fr"}, unix, bug.ClosedStatus),
-		CreateOperation{OpBase: &bug.OpBase{
+		NewSetStatusOp(Person{Name: "", Email: "rene@descartes.fr"}, unix, ClosedStatus),
+		NewSetStatusOp(Person{Name: "RenΓ© Descartes\u001b", Email: "rene@descartes.fr"}, unix, ClosedStatus),
+		NewSetStatusOp(Person{Name: "RenΓ© Descartes", Email: "rene@descartes.fr\u001b"}, unix, ClosedStatus),
+		NewSetStatusOp(Person{Name: "RenΓ© \nDescartes", Email: "rene@descartes.fr"}, unix, ClosedStatus),
+		NewSetStatusOp(Person{Name: "RenΓ© Descartes", Email: "rene@\ndescartes.fr"}, unix, ClosedStatus),
+		CreateOperation{OpBase: &OpBase{
 			Author:        rene,
 			UnixTime:      0,
-			OperationType: bug.CreateOp,
+			OperationType: CreateOp,
 		},
 			Title:   "title",
 			Message: "message",
@@ -59,8 +58,8 @@ func TestValidate(t *testing.T) {
 		NewAddCommentOp(rene, unix, "message", []git.Hash{git.Hash("invalid")}),
 		NewSetStatusOp(rene, unix, 1000),
 		NewSetStatusOp(rene, unix, 0),
-		NewLabelChangeOperation(rene, unix, []bug.Label{}, []bug.Label{}),
-		NewLabelChangeOperation(rene, unix, []bug.Label{"multi\nline"}, []bug.Label{}),
+		NewLabelChangeOperation(rene, unix, []Label{}, []Label{}),
+		NewLabelChangeOperation(rene, unix, []Label{"multi\nline"}, []Label{}),
 	}
 
 	for i, op := range bad {
@@ -68,5 +67,4 @@ func TestValidate(t *testing.T) {
 			t.Fatal("validation should have failed", i, op)
 		}
 	}
-
 }

cache/bug_cache.go πŸ”—

@@ -4,7 +4,6 @@ import (
 	"time"
 
 	"github.com/MichaelMure/git-bug/bug"
-	"github.com/MichaelMure/git-bug/operations"
 	"github.com/MichaelMure/git-bug/util/git"
 )
 
@@ -50,7 +49,7 @@ func (c *BugCache) AddCommentWithFiles(message string, files []git.Hash) error {
 }
 
 func (c *BugCache) AddCommentRaw(author bug.Person, unixTime int64, message string, files []git.Hash, metadata map[string]string) error {
-	err := operations.CommentWithFiles(c.bug, author, unixTime, message, files)
+	err := bug.AddCommentWithFiles(c.bug, author, unixTime, message, files)
 	if err != nil {
 		return err
 	}
@@ -58,7 +57,7 @@ func (c *BugCache) AddCommentRaw(author bug.Person, unixTime int64, message stri
 	return c.notifyUpdated()
 }
 
-func (c *BugCache) ChangeLabels(added []string, removed []string) ([]operations.LabelChangeResult, error) {
+func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelChangeResult, error) {
 	author, err := bug.GetUser(c.repoCache.repo)
 	if err != nil {
 		return nil, err
@@ -67,8 +66,8 @@ func (c *BugCache) ChangeLabels(added []string, removed []string) ([]operations.
 	return c.ChangeLabelsRaw(author, time.Now().Unix(), added, removed)
 }
 
-func (c *BugCache) ChangeLabelsRaw(author bug.Person, unixTime int64, added []string, removed []string) ([]operations.LabelChangeResult, error) {
-	changes, err := operations.ChangeLabels(c.bug, author, unixTime, added, removed)
+func (c *BugCache) ChangeLabelsRaw(author bug.Person, unixTime int64, added []string, removed []string) ([]bug.LabelChangeResult, error) {
+	changes, err := bug.ChangeLabels(c.bug, author, unixTime, added, removed)
 	if err != nil {
 		return changes, err
 	}
@@ -91,7 +90,7 @@ func (c *BugCache) Open() error {
 }
 
 func (c *BugCache) OpenRaw(author bug.Person, unixTime int64) error {
-	err := operations.Open(c.bug, author, unixTime)
+	err := bug.Open(c.bug, author, unixTime)
 	if err != nil {
 		return err
 	}
@@ -109,7 +108,7 @@ func (c *BugCache) Close() error {
 }
 
 func (c *BugCache) CloseRaw(author bug.Person, unixTime int64) error {
-	err := operations.Close(c.bug, author, unixTime)
+	err := bug.Close(c.bug, author, unixTime)
 	if err != nil {
 		return err
 	}
@@ -127,7 +126,7 @@ func (c *BugCache) SetTitle(title string) error {
 }
 
 func (c *BugCache) SetTitleRaw(author bug.Person, unixTime int64, title string) error {
-	err := operations.SetTitle(c.bug, author, unixTime, title)
+	err := bug.SetTitle(c.bug, author, unixTime, title)
 	if err != nil {
 		return err
 	}

cache/repo_cache.go πŸ”—

@@ -14,7 +14,6 @@ import (
 	"time"
 
 	"github.com/MichaelMure/git-bug/bug"
-	"github.com/MichaelMure/git-bug/operations"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/git"
 	"github.com/MichaelMure/git-bug/util/process"
@@ -362,7 +361,7 @@ func (c *RepoCache) NewBugWithFiles(title string, message string, files []git.Ha
 // well as metadata for the Create operation.
 // The new bug is written in the repository (commit)
 func (c *RepoCache) NewBugRaw(author bug.Person, unixTime int64, title string, message string, files []git.Hash, metadata map[string]string) (*BugCache, error) {
-	b, err := operations.CreateWithFiles(author, unixTime, title, message, files)
+	b, err := bug.CreateWithFiles(author, unixTime, title, message, files)
 	if err != nil {
 		return nil, err
 	}

graphql/gqlgen.yml πŸ”—

@@ -22,12 +22,12 @@ models:
   Operation:
     model: github.com/MichaelMure/git-bug/bug.Operation
   CreateOperation:
-    model: github.com/MichaelMure/git-bug/operations.CreateOperation
+    model: github.com/MichaelMure/git-bug/bug.CreateOperation
   SetTitleOperation:
-    model: github.com/MichaelMure/git-bug/operations.SetTitleOperation
+    model: github.com/MichaelMure/git-bug/bug.SetTitleOperation
   AddCommentOperation:
-    model: github.com/MichaelMure/git-bug/operations.AddCommentOperation
+    model: github.com/MichaelMure/git-bug/bug.AddCommentOperation
   SetStatusOperation:
-    model: github.com/MichaelMure/git-bug/operations.SetStatusOperation
+    model: github.com/MichaelMure/git-bug/bug.SetStatusOperation
   LabelChangeOperation:
-    model: github.com/MichaelMure/git-bug/operations.LabelChangeOperation
+    model: github.com/MichaelMure/git-bug/bug.LabelChangeOperation

graphql/graph/gen_graph.go πŸ”—

@@ -14,7 +14,6 @@ import (
 	introspection "github.com/99designs/gqlgen/graphql/introspection"
 	bug "github.com/MichaelMure/git-bug/bug"
 	models "github.com/MichaelMure/git-bug/graphql/models"
-	operations "github.com/MichaelMure/git-bug/operations"
 	git "github.com/MichaelMure/git-bug/util/git"
 	gqlparser "github.com/vektah/gqlparser"
 	ast "github.com/vektah/gqlparser/ast"
@@ -176,8 +175,8 @@ type ComplexityRoot struct {
 }
 
 type AddCommentOperationResolver interface {
-	Author(ctx context.Context, obj *operations.AddCommentOperation) (bug.Person, error)
-	Date(ctx context.Context, obj *operations.AddCommentOperation) (time.Time, error)
+	Author(ctx context.Context, obj *bug.AddCommentOperation) (bug.Person, error)
+	Date(ctx context.Context, obj *bug.AddCommentOperation) (time.Time, error)
 }
 type BugResolver interface {
 	Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error)
@@ -187,12 +186,12 @@ type BugResolver interface {
 	Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.OperationConnection, error)
 }
 type CreateOperationResolver interface {
-	Author(ctx context.Context, obj *operations.CreateOperation) (bug.Person, error)
-	Date(ctx context.Context, obj *operations.CreateOperation) (time.Time, error)
+	Author(ctx context.Context, obj *bug.CreateOperation) (bug.Person, error)
+	Date(ctx context.Context, obj *bug.CreateOperation) (time.Time, error)
 }
 type LabelChangeOperationResolver interface {
-	Author(ctx context.Context, obj *operations.LabelChangeOperation) (bug.Person, error)
-	Date(ctx context.Context, obj *operations.LabelChangeOperation) (time.Time, error)
+	Author(ctx context.Context, obj *bug.LabelChangeOperation) (bug.Person, error)
+	Date(ctx context.Context, obj *bug.LabelChangeOperation) (time.Time, error)
 }
 type MutationResolver interface {
 	NewBug(ctx context.Context, repoRef *string, title string, message string, files []git.Hash) (bug.Snapshot, error)
@@ -212,13 +211,13 @@ type RepositoryResolver interface {
 	Bug(ctx context.Context, obj *models.Repository, prefix string) (*bug.Snapshot, error)
 }
 type SetStatusOperationResolver interface {
-	Author(ctx context.Context, obj *operations.SetStatusOperation) (bug.Person, error)
-	Date(ctx context.Context, obj *operations.SetStatusOperation) (time.Time, error)
-	Status(ctx context.Context, obj *operations.SetStatusOperation) (models.Status, error)
+	Author(ctx context.Context, obj *bug.SetStatusOperation) (bug.Person, error)
+	Date(ctx context.Context, obj *bug.SetStatusOperation) (time.Time, error)
+	Status(ctx context.Context, obj *bug.SetStatusOperation) (models.Status, error)
 }
 type SetTitleOperationResolver interface {
-	Author(ctx context.Context, obj *operations.SetTitleOperation) (bug.Person, error)
-	Date(ctx context.Context, obj *operations.SetTitleOperation) (time.Time, error)
+	Author(ctx context.Context, obj *bug.SetTitleOperation) (bug.Person, error)
+	Date(ctx context.Context, obj *bug.SetTitleOperation) (time.Time, error)
 }
 
 func field_Bug_comments_args(rawArgs map[string]interface{}) (map[string]interface{}, error) {
@@ -1410,7 +1409,7 @@ type executionContext struct {
 var addCommentOperationImplementors = []string{"AddCommentOperation", "Operation", "Authored"}
 
 // nolint: gocyclo, errcheck, gas, goconst
-func (ec *executionContext) _AddCommentOperation(ctx context.Context, sel ast.SelectionSet, obj *operations.AddCommentOperation) graphql.Marshaler {
+func (ec *executionContext) _AddCommentOperation(ctx context.Context, sel ast.SelectionSet, obj *bug.AddCommentOperation) graphql.Marshaler {
 	fields := graphql.CollectFields(ctx, sel, addCommentOperationImplementors)
 
 	var wg sync.WaitGroup
@@ -1462,7 +1461,7 @@ func (ec *executionContext) _AddCommentOperation(ctx context.Context, sel ast.Se
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _AddCommentOperation_author(ctx context.Context, field graphql.CollectedField, obj *operations.AddCommentOperation) graphql.Marshaler {
+func (ec *executionContext) _AddCommentOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "AddCommentOperation",
 		Args:   nil,
@@ -1485,7 +1484,7 @@ func (ec *executionContext) _AddCommentOperation_author(ctx context.Context, fie
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _AddCommentOperation_date(ctx context.Context, field graphql.CollectedField, obj *operations.AddCommentOperation) graphql.Marshaler {
+func (ec *executionContext) _AddCommentOperation_date(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "AddCommentOperation",
 		Args:   nil,
@@ -1507,7 +1506,7 @@ func (ec *executionContext) _AddCommentOperation_date(ctx context.Context, field
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _AddCommentOperation_message(ctx context.Context, field graphql.CollectedField, obj *operations.AddCommentOperation) graphql.Marshaler {
+func (ec *executionContext) _AddCommentOperation_message(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "AddCommentOperation",
 		Args:   nil,
@@ -1529,7 +1528,7 @@ func (ec *executionContext) _AddCommentOperation_message(ctx context.Context, fi
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _AddCommentOperation_files(ctx context.Context, field graphql.CollectedField, obj *operations.AddCommentOperation) graphql.Marshaler {
+func (ec *executionContext) _AddCommentOperation_files(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "AddCommentOperation",
 		Args:   nil,
@@ -2574,7 +2573,7 @@ func (ec *executionContext) _CommentEdge_node(ctx context.Context, field graphql
 var createOperationImplementors = []string{"CreateOperation", "Operation", "Authored"}
 
 // nolint: gocyclo, errcheck, gas, goconst
-func (ec *executionContext) _CreateOperation(ctx context.Context, sel ast.SelectionSet, obj *operations.CreateOperation) graphql.Marshaler {
+func (ec *executionContext) _CreateOperation(ctx context.Context, sel ast.SelectionSet, obj *bug.CreateOperation) graphql.Marshaler {
 	fields := graphql.CollectFields(ctx, sel, createOperationImplementors)
 
 	var wg sync.WaitGroup
@@ -2631,7 +2630,7 @@ func (ec *executionContext) _CreateOperation(ctx context.Context, sel ast.Select
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _CreateOperation_author(ctx context.Context, field graphql.CollectedField, obj *operations.CreateOperation) graphql.Marshaler {
+func (ec *executionContext) _CreateOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.CreateOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "CreateOperation",
 		Args:   nil,
@@ -2654,7 +2653,7 @@ func (ec *executionContext) _CreateOperation_author(ctx context.Context, field g
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _CreateOperation_date(ctx context.Context, field graphql.CollectedField, obj *operations.CreateOperation) graphql.Marshaler {
+func (ec *executionContext) _CreateOperation_date(ctx context.Context, field graphql.CollectedField, obj *bug.CreateOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "CreateOperation",
 		Args:   nil,
@@ -2676,7 +2675,7 @@ func (ec *executionContext) _CreateOperation_date(ctx context.Context, field gra
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _CreateOperation_title(ctx context.Context, field graphql.CollectedField, obj *operations.CreateOperation) graphql.Marshaler {
+func (ec *executionContext) _CreateOperation_title(ctx context.Context, field graphql.CollectedField, obj *bug.CreateOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "CreateOperation",
 		Args:   nil,
@@ -2698,7 +2697,7 @@ func (ec *executionContext) _CreateOperation_title(ctx context.Context, field gr
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _CreateOperation_message(ctx context.Context, field graphql.CollectedField, obj *operations.CreateOperation) graphql.Marshaler {
+func (ec *executionContext) _CreateOperation_message(ctx context.Context, field graphql.CollectedField, obj *bug.CreateOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "CreateOperation",
 		Args:   nil,
@@ -2720,7 +2719,7 @@ func (ec *executionContext) _CreateOperation_message(ctx context.Context, field
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _CreateOperation_files(ctx context.Context, field graphql.CollectedField, obj *operations.CreateOperation) graphql.Marshaler {
+func (ec *executionContext) _CreateOperation_files(ctx context.Context, field graphql.CollectedField, obj *bug.CreateOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "CreateOperation",
 		Args:   nil,
@@ -2753,7 +2752,7 @@ func (ec *executionContext) _CreateOperation_files(ctx context.Context, field gr
 var labelChangeOperationImplementors = []string{"LabelChangeOperation", "Operation", "Authored"}
 
 // nolint: gocyclo, errcheck, gas, goconst
-func (ec *executionContext) _LabelChangeOperation(ctx context.Context, sel ast.SelectionSet, obj *operations.LabelChangeOperation) graphql.Marshaler {
+func (ec *executionContext) _LabelChangeOperation(ctx context.Context, sel ast.SelectionSet, obj *bug.LabelChangeOperation) graphql.Marshaler {
 	fields := graphql.CollectFields(ctx, sel, labelChangeOperationImplementors)
 
 	var wg sync.WaitGroup
@@ -2805,7 +2804,7 @@ func (ec *executionContext) _LabelChangeOperation(ctx context.Context, sel ast.S
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _LabelChangeOperation_author(ctx context.Context, field graphql.CollectedField, obj *operations.LabelChangeOperation) graphql.Marshaler {
+func (ec *executionContext) _LabelChangeOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "LabelChangeOperation",
 		Args:   nil,
@@ -2828,7 +2827,7 @@ func (ec *executionContext) _LabelChangeOperation_author(ctx context.Context, fi
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _LabelChangeOperation_date(ctx context.Context, field graphql.CollectedField, obj *operations.LabelChangeOperation) graphql.Marshaler {
+func (ec *executionContext) _LabelChangeOperation_date(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "LabelChangeOperation",
 		Args:   nil,
@@ -2850,7 +2849,7 @@ func (ec *executionContext) _LabelChangeOperation_date(ctx context.Context, fiel
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _LabelChangeOperation_added(ctx context.Context, field graphql.CollectedField, obj *operations.LabelChangeOperation) graphql.Marshaler {
+func (ec *executionContext) _LabelChangeOperation_added(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "LabelChangeOperation",
 		Args:   nil,
@@ -2881,7 +2880,7 @@ func (ec *executionContext) _LabelChangeOperation_added(ctx context.Context, fie
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _LabelChangeOperation_removed(ctx context.Context, field graphql.CollectedField, obj *operations.LabelChangeOperation) graphql.Marshaler {
+func (ec *executionContext) _LabelChangeOperation_removed(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "LabelChangeOperation",
 		Args:   nil,
@@ -3942,7 +3941,7 @@ func (ec *executionContext) _Repository_bug(ctx context.Context, field graphql.C
 var setStatusOperationImplementors = []string{"SetStatusOperation", "Operation", "Authored"}
 
 // nolint: gocyclo, errcheck, gas, goconst
-func (ec *executionContext) _SetStatusOperation(ctx context.Context, sel ast.SelectionSet, obj *operations.SetStatusOperation) graphql.Marshaler {
+func (ec *executionContext) _SetStatusOperation(ctx context.Context, sel ast.SelectionSet, obj *bug.SetStatusOperation) graphql.Marshaler {
 	fields := graphql.CollectFields(ctx, sel, setStatusOperationImplementors)
 
 	var wg sync.WaitGroup
@@ -3993,7 +3992,7 @@ func (ec *executionContext) _SetStatusOperation(ctx context.Context, sel ast.Sel
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _SetStatusOperation_author(ctx context.Context, field graphql.CollectedField, obj *operations.SetStatusOperation) graphql.Marshaler {
+func (ec *executionContext) _SetStatusOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.SetStatusOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "SetStatusOperation",
 		Args:   nil,
@@ -4016,7 +4015,7 @@ func (ec *executionContext) _SetStatusOperation_author(ctx context.Context, fiel
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _SetStatusOperation_date(ctx context.Context, field graphql.CollectedField, obj *operations.SetStatusOperation) graphql.Marshaler {
+func (ec *executionContext) _SetStatusOperation_date(ctx context.Context, field graphql.CollectedField, obj *bug.SetStatusOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "SetStatusOperation",
 		Args:   nil,
@@ -4038,7 +4037,7 @@ func (ec *executionContext) _SetStatusOperation_date(ctx context.Context, field
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _SetStatusOperation_status(ctx context.Context, field graphql.CollectedField, obj *operations.SetStatusOperation) graphql.Marshaler {
+func (ec *executionContext) _SetStatusOperation_status(ctx context.Context, field graphql.CollectedField, obj *bug.SetStatusOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "SetStatusOperation",
 		Args:   nil,
@@ -4062,7 +4061,7 @@ func (ec *executionContext) _SetStatusOperation_status(ctx context.Context, fiel
 var setTitleOperationImplementors = []string{"SetTitleOperation", "Operation", "Authored"}
 
 // nolint: gocyclo, errcheck, gas, goconst
-func (ec *executionContext) _SetTitleOperation(ctx context.Context, sel ast.SelectionSet, obj *operations.SetTitleOperation) graphql.Marshaler {
+func (ec *executionContext) _SetTitleOperation(ctx context.Context, sel ast.SelectionSet, obj *bug.SetTitleOperation) graphql.Marshaler {
 	fields := graphql.CollectFields(ctx, sel, setTitleOperationImplementors)
 
 	var wg sync.WaitGroup
@@ -4114,7 +4113,7 @@ func (ec *executionContext) _SetTitleOperation(ctx context.Context, sel ast.Sele
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _SetTitleOperation_author(ctx context.Context, field graphql.CollectedField, obj *operations.SetTitleOperation) graphql.Marshaler {
+func (ec *executionContext) _SetTitleOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.SetTitleOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "SetTitleOperation",
 		Args:   nil,
@@ -4137,7 +4136,7 @@ func (ec *executionContext) _SetTitleOperation_author(ctx context.Context, field
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _SetTitleOperation_date(ctx context.Context, field graphql.CollectedField, obj *operations.SetTitleOperation) graphql.Marshaler {
+func (ec *executionContext) _SetTitleOperation_date(ctx context.Context, field graphql.CollectedField, obj *bug.SetTitleOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "SetTitleOperation",
 		Args:   nil,
@@ -4159,7 +4158,7 @@ func (ec *executionContext) _SetTitleOperation_date(ctx context.Context, field g
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _SetTitleOperation_title(ctx context.Context, field graphql.CollectedField, obj *operations.SetTitleOperation) graphql.Marshaler {
+func (ec *executionContext) _SetTitleOperation_title(ctx context.Context, field graphql.CollectedField, obj *bug.SetTitleOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "SetTitleOperation",
 		Args:   nil,
@@ -4181,7 +4180,7 @@ func (ec *executionContext) _SetTitleOperation_title(ctx context.Context, field
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _SetTitleOperation_was(ctx context.Context, field graphql.CollectedField, obj *operations.SetTitleOperation) graphql.Marshaler {
+func (ec *executionContext) _SetTitleOperation_was(ctx context.Context, field graphql.CollectedField, obj *bug.SetTitleOperation) graphql.Marshaler {
 	rctx := &graphql.ResolverContext{
 		Object: "SetTitleOperation",
 		Args:   nil,
@@ -5487,25 +5486,25 @@ func (ec *executionContext) _Authored(ctx context.Context, sel ast.SelectionSet,
 		return ec._Comment(ctx, sel, &obj)
 	case *bug.Comment:
 		return ec._Comment(ctx, sel, obj)
-	case operations.CreateOperation:
+	case bug.CreateOperation:
 		return ec._CreateOperation(ctx, sel, &obj)
-	case *operations.CreateOperation:
+	case *bug.CreateOperation:
 		return ec._CreateOperation(ctx, sel, obj)
-	case operations.SetTitleOperation:
+	case bug.SetTitleOperation:
 		return ec._SetTitleOperation(ctx, sel, &obj)
-	case *operations.SetTitleOperation:
+	case *bug.SetTitleOperation:
 		return ec._SetTitleOperation(ctx, sel, obj)
-	case operations.AddCommentOperation:
+	case bug.AddCommentOperation:
 		return ec._AddCommentOperation(ctx, sel, &obj)
-	case *operations.AddCommentOperation:
+	case *bug.AddCommentOperation:
 		return ec._AddCommentOperation(ctx, sel, obj)
-	case operations.SetStatusOperation:
+	case bug.SetStatusOperation:
 		return ec._SetStatusOperation(ctx, sel, &obj)
-	case *operations.SetStatusOperation:
+	case *bug.SetStatusOperation:
 		return ec._SetStatusOperation(ctx, sel, obj)
-	case operations.LabelChangeOperation:
+	case bug.LabelChangeOperation:
 		return ec._LabelChangeOperation(ctx, sel, &obj)
-	case *operations.LabelChangeOperation:
+	case *bug.LabelChangeOperation:
 		return ec._LabelChangeOperation(ctx, sel, obj)
 	default:
 		panic(fmt.Errorf("unexpected type %T", obj))
@@ -5516,15 +5515,15 @@ func (ec *executionContext) _Operation(ctx context.Context, sel ast.SelectionSet
 	switch obj := (*obj).(type) {
 	case nil:
 		return graphql.Null
-	case operations.CreateOperation:
+	case bug.CreateOperation:
 		return ec._CreateOperation(ctx, sel, &obj)
-	case operations.SetTitleOperation:
+	case bug.SetTitleOperation:
 		return ec._SetTitleOperation(ctx, sel, &obj)
-	case operations.AddCommentOperation:
+	case bug.AddCommentOperation:
 		return ec._AddCommentOperation(ctx, sel, &obj)
-	case operations.SetStatusOperation:
+	case bug.SetStatusOperation:
 		return ec._SetStatusOperation(ctx, sel, &obj)
-	case operations.LabelChangeOperation:
+	case bug.LabelChangeOperation:
 		return ec._LabelChangeOperation(ctx, sel, &obj)
 	default:
 		panic(fmt.Errorf("unexpected type %T", obj))

graphql/resolvers/operations.go πŸ”—

@@ -7,60 +7,59 @@ import (
 
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/graphql/models"
-	"github.com/MichaelMure/git-bug/operations"
 )
 
 type addCommentOperationResolver struct{}
 
-func (addCommentOperationResolver) Author(ctx context.Context, obj *operations.AddCommentOperation) (bug.Person, error) {
+func (addCommentOperationResolver) Author(ctx context.Context, obj *bug.AddCommentOperation) (bug.Person, error) {
 	return obj.Author, nil
 }
 
-func (addCommentOperationResolver) Date(ctx context.Context, obj *operations.AddCommentOperation) (time.Time, error) {
+func (addCommentOperationResolver) Date(ctx context.Context, obj *bug.AddCommentOperation) (time.Time, error) {
 	return obj.Time(), nil
 }
 
 type createOperationResolver struct{}
 
-func (createOperationResolver) Author(ctx context.Context, obj *operations.CreateOperation) (bug.Person, error) {
+func (createOperationResolver) Author(ctx context.Context, obj *bug.CreateOperation) (bug.Person, error) {
 	return obj.Author, nil
 }
 
-func (createOperationResolver) Date(ctx context.Context, obj *operations.CreateOperation) (time.Time, error) {
+func (createOperationResolver) Date(ctx context.Context, obj *bug.CreateOperation) (time.Time, error) {
 	return obj.Time(), nil
 }
 
 type labelChangeOperation struct{}
 
-func (labelChangeOperation) Author(ctx context.Context, obj *operations.LabelChangeOperation) (bug.Person, error) {
+func (labelChangeOperation) Author(ctx context.Context, obj *bug.LabelChangeOperation) (bug.Person, error) {
 	return obj.Author, nil
 }
 
-func (labelChangeOperation) Date(ctx context.Context, obj *operations.LabelChangeOperation) (time.Time, error) {
+func (labelChangeOperation) Date(ctx context.Context, obj *bug.LabelChangeOperation) (time.Time, error) {
 	return obj.Time(), nil
 }
 
 type setStatusOperationResolver struct{}
 
-func (setStatusOperationResolver) Author(ctx context.Context, obj *operations.SetStatusOperation) (bug.Person, error) {
+func (setStatusOperationResolver) Author(ctx context.Context, obj *bug.SetStatusOperation) (bug.Person, error) {
 	return obj.Author, nil
 }
 
-func (setStatusOperationResolver) Date(ctx context.Context, obj *operations.SetStatusOperation) (time.Time, error) {
+func (setStatusOperationResolver) Date(ctx context.Context, obj *bug.SetStatusOperation) (time.Time, error) {
 	return obj.Time(), nil
 }
 
-func (setStatusOperationResolver) Status(ctx context.Context, obj *operations.SetStatusOperation) (models.Status, error) {
+func (setStatusOperationResolver) Status(ctx context.Context, obj *bug.SetStatusOperation) (models.Status, error) {
 	return convertStatus(obj.Status)
 }
 
 type setTitleOperationResolver struct{}
 
-func (setTitleOperationResolver) Author(ctx context.Context, obj *operations.SetTitleOperation) (bug.Person, error) {
+func (setTitleOperationResolver) Author(ctx context.Context, obj *bug.SetTitleOperation) (bug.Person, error) {
 	return obj.Author, nil
 }
 
-func (setTitleOperationResolver) Date(ctx context.Context, obj *operations.SetTitleOperation) (time.Time, error) {
+func (setTitleOperationResolver) Date(ctx context.Context, obj *bug.SetTitleOperation) (time.Time, error) {
 	return obj.Time(), nil
 }
 

misc/random_bugs/create_random_bugs.go πŸ”—

@@ -6,7 +6,6 @@ import (
 	"time"
 
 	"github.com/MichaelMure/git-bug/bug"
-	"github.com/MichaelMure/git-bug/operations"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/icrowley/fake"
 )
@@ -66,7 +65,7 @@ func GenerateRandomBugsWithSeed(opts Options, seed int64) []*bug.Bug {
 	for i := 0; i < opts.BugNumber; i++ {
 		addedLabels = []string{}
 
-		b, err := operations.Create(
+		b, err := bug.Create(
 			randomPerson(opts.PersonNumber),
 			time.Now().Unix(),
 			fake.Sentence(),
@@ -111,7 +110,7 @@ func GenerateRandomOperationPacksWithSeed(packNumber int, opNumber int, seed int
 
 		var op bug.Operation
 
-		op = operations.NewCreateOp(
+		op = bug.NewCreateOp(
 			randomPerson(5),
 			time.Now().Unix(),
 			fake.Sentence(),
@@ -122,7 +121,7 @@ func GenerateRandomOperationPacksWithSeed(packNumber int, opNumber int, seed int
 		opp.Append(op)
 
 		for j := 0; j < opNumber-1; j++ {
-			op = operations.NewAddCommentOp(
+			op = bug.NewAddCommentOp(
 				randomPerson(5),
 				time.Now().Unix(),
 				paragraphs(),
@@ -164,19 +163,19 @@ func paragraphs() string {
 }
 
 func comment(b bug.Interface, p bug.Person) {
-	_ = operations.Comment(b, p, time.Now().Unix(), paragraphs())
+	_ = bug.AddComment(b, p, time.Now().Unix(), paragraphs())
 }
 
 func title(b bug.Interface, p bug.Person) {
-	_ = operations.SetTitle(b, p, time.Now().Unix(), fake.Sentence())
+	_ = bug.SetTitle(b, p, time.Now().Unix(), fake.Sentence())
 }
 
 func open(b bug.Interface, p bug.Person) {
-	_ = operations.Open(b, p, time.Now().Unix())
+	_ = bug.Open(b, p, time.Now().Unix())
 }
 
 func close(b bug.Interface, p bug.Person) {
-	_ = operations.Close(b, p, time.Now().Unix())
+	_ = bug.Close(b, p, time.Now().Unix())
 }
 
 var addedLabels []string
@@ -203,5 +202,5 @@ func labels(b bug.Interface, p bug.Person) {
 	// ignore error
 	// if the randomisation produce no changes, no op
 	// is added to the bug
-	_, _ = operations.ChangeLabels(b, p, time.Now().Unix(), added, removed)
+	_, _ = bug.ChangeLabels(b, p, time.Now().Unix(), added, removed)
 }

operations/operations.go πŸ”—

@@ -1,17 +0,0 @@
-// Package operations contains the various bug operations. A bug operation is
-// an atomic edit operation of a bug state. These operations are applied
-// sequentially to compile the current state of the bug.
-package operations
-
-import (
-	"github.com/MichaelMure/git-bug/bug"
-)
-
-// Package initialisation used to register operation's type for (de)serialization
-func init() {
-	bug.Register(bug.CreateOp, CreateOperation{})
-	bug.Register(bug.SetTitleOp, SetTitleOperation{})
-	bug.Register(bug.AddCommentOp, AddCommentOperation{})
-	bug.Register(bug.SetStatusOp, SetStatusOperation{})
-	bug.Register(bug.LabelChangeOp, LabelChangeOperation{})
-}

operations/set_status.go πŸ”—

@@ -1,60 +0,0 @@
-package operations
-
-import (
-	"github.com/MichaelMure/git-bug/bug"
-	"github.com/pkg/errors"
-)
-
-// SetStatusOperation will change the status of a bug
-
-var _ bug.Operation = SetStatusOperation{}
-
-type SetStatusOperation struct {
-	*bug.OpBase
-	Status bug.Status `json:"status"`
-}
-
-func (op SetStatusOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
-	snapshot.Status = op.Status
-
-	return snapshot
-}
-
-func (op SetStatusOperation) Validate() error {
-	if err := bug.OpBaseValidate(op, bug.SetStatusOp); err != nil {
-		return err
-	}
-
-	if err := op.Status.Validate(); err != nil {
-		return errors.Wrap(err, "status")
-	}
-
-	return nil
-}
-
-func NewSetStatusOp(author bug.Person, unixTime int64, status bug.Status) SetStatusOperation {
-	return SetStatusOperation{
-		OpBase: bug.NewOpBase(bug.SetStatusOp, author, unixTime),
-		Status: status,
-	}
-}
-
-// Convenience function to apply the operation
-func Open(b bug.Interface, author bug.Person, unixTime int64) error {
-	op := NewSetStatusOp(author, unixTime, bug.OpenStatus)
-	if err := op.Validate(); err != nil {
-		return err
-	}
-	b.Append(op)
-	return nil
-}
-
-// Convenience function to apply the operation
-func Close(b bug.Interface, author bug.Person, unixTime int64) error {
-	op := NewSetStatusOp(author, unixTime, bug.ClosedStatus)
-	if err := op.Validate(); err != nil {
-		return err
-	}
-	b.Append(op)
-	return nil
-}

termui/show_bug.go πŸ”—

@@ -5,8 +5,8 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/operations"
 	"github.com/MichaelMure/git-bug/util/colors"
 	"github.com/MichaelMure/git-bug/util/text"
 	"github.com/jroimartin/gocui"
@@ -234,8 +234,8 @@ func (sb *showBug) renderMain(g *gocui.Gui, mainView *gocui.View) error {
 
 		switch op.(type) {
 
-		case operations.CreateOperation:
-			create := op.(operations.CreateOperation)
+		case bug.CreateOperation:
+			create := op.(bug.CreateOperation)
 			content, lines := text.WrapLeftPadded(create.Message, maxX, 4)
 
 			v, err := sb.createOpView(g, viewName, x0, y0, maxX+1, lines, true)
@@ -245,8 +245,8 @@ func (sb *showBug) renderMain(g *gocui.Gui, mainView *gocui.View) error {
 			fmt.Fprint(v, content)
 			y0 += lines + 2
 
-		case operations.AddCommentOperation:
-			comment := op.(operations.AddCommentOperation)
+		case bug.AddCommentOperation:
+			comment := op.(bug.AddCommentOperation)
 
 			message, _ := text.WrapLeftPadded(comment.Message, maxX, 4)
 			content := fmt.Sprintf("%s commented on %s\n\n%s",
@@ -263,8 +263,8 @@ func (sb *showBug) renderMain(g *gocui.Gui, mainView *gocui.View) error {
 			fmt.Fprint(v, content)
 			y0 += lines + 2
 
-		case operations.SetTitleOperation:
-			setTitle := op.(operations.SetTitleOperation)
+		case bug.SetTitleOperation:
+			setTitle := op.(bug.SetTitleOperation)
 
 			content := fmt.Sprintf("%s changed the title to %s on %s",
 				colors.Magenta(setTitle.Author.Name),
@@ -280,8 +280,8 @@ func (sb *showBug) renderMain(g *gocui.Gui, mainView *gocui.View) error {
 			fmt.Fprint(v, content)
 			y0 += lines + 2
 
-		case operations.SetStatusOperation:
-			setStatus := op.(operations.SetStatusOperation)
+		case bug.SetStatusOperation:
+			setStatus := op.(bug.SetStatusOperation)
 
 			content := fmt.Sprintf("%s %s the bug on %s",
 				colors.Magenta(setStatus.Author.Name),
@@ -297,8 +297,8 @@ func (sb *showBug) renderMain(g *gocui.Gui, mainView *gocui.View) error {
 			fmt.Fprint(v, content)
 			y0 += lines + 2
 
-		case operations.LabelChangeOperation:
-			labelChange := op.(operations.LabelChangeOperation)
+		case bug.LabelChangeOperation:
+			labelChange := op.(bug.LabelChangeOperation)
 
 			var added []string
 			for _, label := range labelChange.Added {

tests/read_bugs_test.go πŸ”—

@@ -1,6 +1,8 @@
 package tests
 
 import (
+	"io/ioutil"
+	"log"
 	"testing"
 
 	"github.com/MichaelMure/git-bug/bug"
@@ -8,6 +10,30 @@ import (
 	"github.com/MichaelMure/git-bug/repository"
 )
 
+func createRepo(bare bool) *repository.GitRepo {
+	dir, err := ioutil.TempDir("", "")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// fmt.Println("Creating repo:", dir)
+
+	var creator func(string) (*repository.GitRepo, error)
+
+	if bare {
+		creator = repository.InitBareGitRepo
+	} else {
+		creator = repository.InitGitRepo
+	}
+
+	repo, err := creator(dir)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	return repo
+}
+
 func createFilledRepo(bugNumber int) repository.ClockedRepo {
 	repo := createRepo(false)
 

vendor/github.com/go-test/deep/.travis.yml πŸ”—

@@ -0,0 +1,13 @@
+language: go
+
+go:
+  - 1.7
+  - 1.8
+  - 1.9
+
+before_install:
+  - go get github.com/mattn/goveralls
+  - go get golang.org/x/tools/cover
+
+script:
+  - $HOME/gopath/bin/goveralls -service=travis-ci

vendor/github.com/go-test/deep/LICENSE πŸ”—

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright 2015-2017 Daniel Nichter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

vendor/github.com/go-test/deep/README.md πŸ”—

@@ -0,0 +1,51 @@
+# Deep Variable Equality for Humans
+
+[![Go Report Card](https://goreportcard.com/badge/github.com/go-test/deep)](https://goreportcard.com/report/github.com/go-test/deep) [![Build Status](https://travis-ci.org/go-test/deep.svg?branch=master)](https://travis-ci.org/go-test/deep) [![Coverage Status](https://coveralls.io/repos/github/go-test/deep/badge.svg?branch=master)](https://coveralls.io/github/go-test/deep?branch=master) [![GoDoc](https://godoc.org/github.com/go-test/deep?status.svg)](https://godoc.org/github.com/go-test/deep)
+
+This package provides a single function: `deep.Equal`. It's like [reflect.DeepEqual](http://golang.org/pkg/reflect/#DeepEqual) but much friendlier to humans (or any sentient being) for two reason:
+
+* `deep.Equal` returns a list of differences
+* `deep.Equal` does not compare unexported fields (by default)
+
+`reflect.DeepEqual` is good (like all things Golang!), but it's a game of [Hunt the Wumpus](https://en.wikipedia.org/wiki/Hunt_the_Wumpus). For large maps, slices, and structs, finding the difference is difficult.
+
+`deep.Equal` doesn't play games with you, it lists the differences:
+
+```go
+package main_test
+
+import (
+	"testing"
+	"github.com/go-test/deep"
+)
+
+type T struct {
+	Name    string
+	Numbers []float64
+}
+
+func TestDeepEqual(t *testing.T) {
+	// Can you spot the difference?
+	t1 := T{
+		Name:    "Isabella",
+		Numbers: []float64{1.13459, 2.29343, 3.010100010},
+	}
+	t2 := T{
+		Name:    "Isabella",
+		Numbers: []float64{1.13459, 2.29843, 3.010100010},
+	}
+
+	if diff := deep.Equal(t1, t2); diff != nil {
+		t.Error(diff)
+	}
+}
+```
+
+
+```
+$ go test
+--- FAIL: TestDeepEqual (0.00s)
+        main_test.go:25: [Numbers.slice[1]: 2.29343 != 2.29843]
+```
+
+The difference is in `Numbers.slice[1]`: the two values aren't equal using Go `==`.

vendor/github.com/go-test/deep/deep.go πŸ”—

@@ -0,0 +1,352 @@
+// Package deep provides function deep.Equal which is like reflect.DeepEqual but
+// returns a list of differences. This is helpful when comparing complex types
+// like structures and maps.
+package deep
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"reflect"
+	"strings"
+)
+
+var (
+	// FloatPrecision is the number of decimal places to round float values
+	// to when comparing.
+	FloatPrecision = 10
+
+	// MaxDiff specifies the maximum number of differences to return.
+	MaxDiff = 10
+
+	// MaxDepth specifies the maximum levels of a struct to recurse into.
+	MaxDepth = 10
+
+	// LogErrors causes errors to be logged to STDERR when true.
+	LogErrors = false
+
+	// CompareUnexportedFields causes unexported struct fields, like s in
+	// T{s int}, to be comparsed when true.
+	CompareUnexportedFields = false
+)
+
+var (
+	// ErrMaxRecursion is logged when MaxDepth is reached.
+	ErrMaxRecursion = errors.New("recursed to MaxDepth")
+
+	// ErrTypeMismatch is logged when Equal passed two different types of values.
+	ErrTypeMismatch = errors.New("variables are different reflect.Type")
+
+	// ErrNotHandled is logged when a primitive Go kind is not handled.
+	ErrNotHandled = errors.New("cannot compare the reflect.Kind")
+)
+
+type cmp struct {
+	diff        []string
+	buff        []string
+	floatFormat string
+}
+
+var errorType = reflect.TypeOf((*error)(nil)).Elem()
+
+// Equal compares variables a and b, recursing into their structure up to
+// MaxDepth levels deep, and returns a list of differences, or nil if there are
+// none. Some differences may not be found if an error is also returned.
+//
+// If a type has an Equal method, like time.Equal, it is called to check for
+// equality.
+func Equal(a, b interface{}) []string {
+	aVal := reflect.ValueOf(a)
+	bVal := reflect.ValueOf(b)
+	c := &cmp{
+		diff:        []string{},
+		buff:        []string{},
+		floatFormat: fmt.Sprintf("%%.%df", FloatPrecision),
+	}
+	if a == nil && b == nil {
+		return nil
+	} else if a == nil && b != nil {
+		c.saveDiff(b, "<nil pointer>")
+	} else if a != nil && b == nil {
+		c.saveDiff(a, "<nil pointer>")
+	}
+	if len(c.diff) > 0 {
+		return c.diff
+	}
+
+	c.equals(aVal, bVal, 0)
+	if len(c.diff) > 0 {
+		return c.diff // diffs
+	}
+	return nil // no diffs
+}
+
+func (c *cmp) equals(a, b reflect.Value, level int) {
+	if level > MaxDepth {
+		logError(ErrMaxRecursion)
+		return
+	}
+
+	// Check if one value is nil, e.g. T{x: *X} and T.x is nil
+	if !a.IsValid() || !b.IsValid() {
+		if a.IsValid() && !b.IsValid() {
+			c.saveDiff(a.Type(), "<nil pointer>")
+		} else if !a.IsValid() && b.IsValid() {
+			c.saveDiff("<nil pointer>", b.Type())
+		}
+		return
+	}
+
+	// If differenet types, they can't be equal
+	aType := a.Type()
+	bType := b.Type()
+	if aType != bType {
+		c.saveDiff(aType, bType)
+		logError(ErrTypeMismatch)
+		return
+	}
+
+	// Primitive https://golang.org/pkg/reflect/#Kind
+	aKind := a.Kind()
+	bKind := b.Kind()
+
+	// If both types implement the error interface, compare the error strings.
+	// This must be done before dereferencing because the interface is on a
+	// pointer receiver.
+	if aType.Implements(errorType) && bType.Implements(errorType) {
+		if a.Elem().IsValid() && b.Elem().IsValid() { // both err != nil
+			aString := a.MethodByName("Error").Call(nil)[0].String()
+			bString := b.MethodByName("Error").Call(nil)[0].String()
+			if aString != bString {
+				c.saveDiff(aString, bString)
+			}
+			return
+		}
+	}
+
+	// Dereference pointers and interface{}
+	if aElem, bElem := (aKind == reflect.Ptr || aKind == reflect.Interface),
+		(bKind == reflect.Ptr || bKind == reflect.Interface); aElem || bElem {
+
+		if aElem {
+			a = a.Elem()
+		}
+
+		if bElem {
+			b = b.Elem()
+		}
+
+		c.equals(a, b, level+1)
+		return
+	}
+
+	// Types with an Equal(), like time.Time.
+	eqFunc := a.MethodByName("Equal")
+	if eqFunc.IsValid() {
+		retVals := eqFunc.Call([]reflect.Value{b})
+		if !retVals[0].Bool() {
+			c.saveDiff(a, b)
+		}
+		return
+	}
+
+	switch aKind {
+
+	/////////////////////////////////////////////////////////////////////
+	// Iterable kinds
+	/////////////////////////////////////////////////////////////////////
+
+	case reflect.Struct:
+		/*
+			The variables are structs like:
+				type T struct {
+					FirstName string
+					LastName  string
+				}
+			Type = <pkg>.T, Kind = reflect.Struct
+
+			Iterate through the fields (FirstName, LastName), recurse into their values.
+		*/
+		for i := 0; i < a.NumField(); i++ {
+			if aType.Field(i).PkgPath != "" && !CompareUnexportedFields {
+				continue // skip unexported field, e.g. s in type T struct {s string}
+			}
+
+			c.push(aType.Field(i).Name) // push field name to buff
+
+			// Get the Value for each field, e.g. FirstName has Type = string,
+			// Kind = reflect.String.
+			af := a.Field(i)
+			bf := b.Field(i)
+
+			// Recurse to compare the field values
+			c.equals(af, bf, level+1)
+
+			c.pop() // pop field name from buff
+
+			if len(c.diff) >= MaxDiff {
+				break
+			}
+		}
+	case reflect.Map:
+		/*
+			The variables are maps like:
+				map[string]int{
+					"foo": 1,
+					"bar": 2,
+				}
+			Type = map[string]int, Kind = reflect.Map
+
+			Or:
+				type T map[string]int{}
+			Type = <pkg>.T, Kind = reflect.Map
+
+			Iterate through the map keys (foo, bar), recurse into their values.
+		*/
+
+		if a.IsNil() || b.IsNil() {
+			if a.IsNil() && !b.IsNil() {
+				c.saveDiff("<nil map>", b)
+			} else if !a.IsNil() && b.IsNil() {
+				c.saveDiff(a, "<nil map>")
+			}
+			return
+		}
+
+		if a.Pointer() == b.Pointer() {
+			return
+		}
+
+		for _, key := range a.MapKeys() {
+			c.push(fmt.Sprintf("map[%s]", key))
+
+			aVal := a.MapIndex(key)
+			bVal := b.MapIndex(key)
+			if bVal.IsValid() {
+				c.equals(aVal, bVal, level+1)
+			} else {
+				c.saveDiff(aVal, "<does not have key>")
+			}
+
+			c.pop()
+
+			if len(c.diff) >= MaxDiff {
+				return
+			}
+		}
+
+		for _, key := range b.MapKeys() {
+			if aVal := a.MapIndex(key); aVal.IsValid() {
+				continue
+			}
+
+			c.push(fmt.Sprintf("map[%s]", key))
+			c.saveDiff("<does not have key>", b.MapIndex(key))
+			c.pop()
+			if len(c.diff) >= MaxDiff {
+				return
+			}
+		}
+	case reflect.Array:
+		n := a.Len()
+		for i := 0; i < n; i++ {
+			c.push(fmt.Sprintf("array[%d]", i))
+			c.equals(a.Index(i), b.Index(i), level+1)
+			c.pop()
+			if len(c.diff) >= MaxDiff {
+				break
+			}
+		}
+	case reflect.Slice:
+		if a.IsNil() || b.IsNil() {
+			if a.IsNil() && !b.IsNil() {
+				c.saveDiff("<nil slice>", b)
+			} else if !a.IsNil() && b.IsNil() {
+				c.saveDiff(a, "<nil slice>")
+			}
+			return
+		}
+
+		if a.Pointer() == b.Pointer() {
+			return
+		}
+
+		aLen := a.Len()
+		bLen := b.Len()
+		n := aLen
+		if bLen > aLen {
+			n = bLen
+		}
+		for i := 0; i < n; i++ {
+			c.push(fmt.Sprintf("slice[%d]", i))
+			if i < aLen && i < bLen {
+				c.equals(a.Index(i), b.Index(i), level+1)
+			} else if i < aLen {
+				c.saveDiff(a.Index(i), "<no value>")
+			} else {
+				c.saveDiff("<no value>", b.Index(i))
+			}
+			c.pop()
+			if len(c.diff) >= MaxDiff {
+				break
+			}
+		}
+
+	/////////////////////////////////////////////////////////////////////
+	// Primitive kinds
+	/////////////////////////////////////////////////////////////////////
+
+	case reflect.Float32, reflect.Float64:
+		// Avoid 0.04147685731961082 != 0.041476857319611
+		// 6 decimal places is close enough
+		aval := fmt.Sprintf(c.floatFormat, a.Float())
+		bval := fmt.Sprintf(c.floatFormat, b.Float())
+		if aval != bval {
+			c.saveDiff(a.Float(), b.Float())
+		}
+	case reflect.Bool:
+		if a.Bool() != b.Bool() {
+			c.saveDiff(a.Bool(), b.Bool())
+		}
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		if a.Int() != b.Int() {
+			c.saveDiff(a.Int(), b.Int())
+		}
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		if a.Uint() != b.Uint() {
+			c.saveDiff(a.Uint(), b.Uint())
+		}
+	case reflect.String:
+		if a.String() != b.String() {
+			c.saveDiff(a.String(), b.String())
+		}
+
+	default:
+		logError(ErrNotHandled)
+	}
+}
+
+func (c *cmp) push(name string) {
+	c.buff = append(c.buff, name)
+}
+
+func (c *cmp) pop() {
+	if len(c.buff) > 0 {
+		c.buff = c.buff[0 : len(c.buff)-1]
+	}
+}
+
+func (c *cmp) saveDiff(aval, bval interface{}) {
+	if len(c.buff) > 0 {
+		varName := strings.Join(c.buff, ".")
+		c.diff = append(c.diff, fmt.Sprintf("%s: %v != %v", varName, aval, bval))
+	} else {
+		c.diff = append(c.diff, fmt.Sprintf("%v != %v", aval, bval))
+	}
+}
+
+func logError(err error) {
+	if LogErrors {
+		log.Println(err)
+	}
+}