add bug status + open/close commands

Michael Muré created

Change summary

bug/bug.go                       | 15 +++++++-----
bug/operation.go                 |  9 ++++---
bug/operations/add_comment.go    |  2 
bug/operations/create.go         |  9 ------
bug/operations/operations.go     |  1 
bug/operations/set_status.go     | 24 ++++++++++++++++++++
bug/operations/set_title.go      |  2 
bug/snapshot.go                  | 12 ++++++++++
bug/status.go                    | 20 +++++++++++++++++
commands/close.go                | 39 ++++++++++++++++++++++++++++++++++
commands/command.go              |  2 +
commands/comment.go              |  9 ++++---
commands/ls.go                   |  2 
commands/open.go                 | 39 ++++++++++++++++++++++++++++++++++
tests/bug_test.go                |  6 ++--
tests/operation_iterator_test.go |  2 +
16 files changed, 165 insertions(+), 28 deletions(-)

Detailed changes

bug/bug.go 🔗

@@ -182,17 +182,17 @@ func (bug *Bug) IsValid() bool {
 		}
 	}
 
-	// The very first Op should be a CREATE
+	// The very first Op should be a CreateOp
 	firstOp := bug.firstOp()
-	if firstOp == nil || firstOp.OpType() != CREATE {
+	if firstOp == nil || firstOp.OpType() != CreateOp {
 		return false
 	}
 
-	// Check that there is no more CREATE op
+	// Check that there is no more CreateOp op
 	it := NewOperationIterator(bug)
 	createCount := 0
 	for it.Next() {
-		if it.Value().OpType() == CREATE {
+		if it.Value().OpType() == CreateOp {
 			createCount++
 		}
 	}
@@ -351,7 +351,7 @@ func (bug *Bug) HumanId() string {
 }
 
 // Lookup for the very first operation of the bug.
-// For a valid Bug, this operation should be a CREATE
+// For a valid Bug, this operation should be a CreateOp
 func (bug *Bug) firstOp() Operation {
 	for _, pack := range bug.packs {
 		for _, op := range pack.Operations {
@@ -368,7 +368,10 @@ func (bug *Bug) firstOp() Operation {
 
 // Compile a bug in a easily usable snapshot
 func (bug *Bug) Compile() Snapshot {
-	snap := Snapshot{}
+	snap := Snapshot{
+		id:     bug.id,
+		Status: OpenStatus,
+	}
 
 	it := NewOperationIterator(bug)
 

bug/operation.go 🔗

@@ -3,10 +3,11 @@ package bug
 type OperationType int
 
 const (
-	UNKNOWN OperationType = iota
-	CREATE
-	SET_TITLE
-	ADD_COMMENT
+	_ OperationType = iota
+	CreateOp
+	SetTitleOp
+	AddCommentOp
+	SetStatusOp
 )
 
 type Operation interface {

bug/operations/add_comment.go 🔗

@@ -16,7 +16,7 @@ type AddCommentOperation struct {
 
 func NewAddCommentOp(author bug.Person, message string) AddCommentOperation {
 	return AddCommentOperation{
-		OpBase:  bug.OpBase{OperationType: bug.ADD_COMMENT},
+		OpBase:  bug.OpBase{OperationType: bug.AddCommentOp},
 		Message: message,
 		Author:  author,
 		Time:    time.Now().Unix(),

bug/operations/create.go 🔗

@@ -2,7 +2,6 @@ package operations
 
 import (
 	"github.com/MichaelMure/git-bug/bug"
-	"reflect"
 	"time"
 )
 
@@ -20,7 +19,7 @@ type CreateOperation struct {
 
 func NewCreateOp(author bug.Person, title, message string) CreateOperation {
 	return CreateOperation{
-		OpBase:  bug.OpBase{OperationType: bug.CREATE},
+		OpBase:  bug.OpBase{OperationType: bug.CreateOp},
 		Title:   title,
 		Message: message,
 		Author:  author,
@@ -29,12 +28,6 @@ func NewCreateOp(author bug.Person, title, message string) CreateOperation {
 }
 
 func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
-	empty := bug.Snapshot{}
-
-	if !reflect.DeepEqual(snapshot, empty) {
-		panic("Create operation should never be applied on a non-empty snapshot")
-	}
-
 	snapshot.Title = op.Title
 	snapshot.Comments = []bug.Comment{
 		{

bug/operations/operations.go 🔗

@@ -7,4 +7,5 @@ func init() {
 	gob.Register(AddCommentOperation{})
 	gob.Register(CreateOperation{})
 	gob.Register(SetTitleOperation{})
+	gob.Register(SetStatusOperation{})
 }

bug/operations/set_status.go 🔗

@@ -0,0 +1,24 @@
+package operations
+
+import "github.com/MichaelMure/git-bug/bug"
+
+// SetStatusOperation will change the status of a bug
+
+var _ bug.Operation = SetStatusOperation{}
+
+type SetStatusOperation struct {
+	bug.OpBase
+	Status bug.Status
+}
+
+func NewSetStatusOp(status bug.Status) SetStatusOperation {
+	return SetStatusOperation{
+		OpBase: bug.OpBase{OperationType: bug.SetStatusOp},
+		Status: status,
+	}
+}
+
+func (op SetStatusOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
+	snapshot.Status = op.Status
+	return snapshot
+}

bug/operations/set_title.go 🔗

@@ -11,7 +11,7 @@ type SetTitleOperation struct {
 
 func NewSetTitleOp(title string) SetTitleOperation {
 	return SetTitleOperation{
-		OpBase: bug.OpBase{OperationType: bug.SET_TITLE},
+		OpBase: bug.OpBase{OperationType: bug.SetTitleOp},
 		Title:  title,
 	}
 }

bug/snapshot.go 🔗

@@ -7,11 +7,23 @@ import (
 
 // Snapshot is a compiled form of the Bug data structure used for storage and merge
 type Snapshot struct {
+	id       string
+	Status   Status
 	Title    string
 	Comments []Comment
 	Labels   []Label
 }
 
+// Return the Bug identifier
+func (snap Snapshot) Id() string {
+	return snap.id
+}
+
+// Return the Bug identifier truncated for human consumption
+func (snap Snapshot) HumanId() string {
+	return fmt.Sprintf("%.8s", snap.id)
+}
+
 func (snap Snapshot) Summary() string {
 	return fmt.Sprintf("c:%d l:%d %s",
 		len(snap.Comments)-1,

bug/status.go 🔗

@@ -0,0 +1,20 @@
+package bug
+
+type Status int
+
+const (
+	_ Status = iota
+	OpenStatus
+	ClosedStatus
+)
+
+func (s Status) String() string {
+	switch s {
+	case OpenStatus:
+		return "open"
+	case ClosedStatus:
+		return "closed"
+	default:
+		return "unknown status"
+	}
+}

commands/close.go 🔗

@@ -0,0 +1,39 @@
+package commands
+
+import (
+	"errors"
+	"github.com/MichaelMure/git-bug/bug"
+	"github.com/MichaelMure/git-bug/bug/operations"
+	"github.com/MichaelMure/git-bug/repository"
+)
+
+func runCloseBug(repo repository.Repo, args []string) error {
+	if len(args) > 1 {
+		return errors.New("Only closing one bug at a time is supported")
+	}
+
+	if len(args) == 0 {
+		return errors.New("You must provide a bug id")
+	}
+
+	prefix := args[0]
+
+	b, err := bug.FindBug(repo, prefix)
+	if err != nil {
+		return err
+	}
+
+	op := operations.NewSetStatusOp(bug.ClosedStatus)
+
+	b.Append(op)
+
+	err = b.Commit(repo)
+
+	return err
+}
+
+var closeCmd = &Command{
+	Description: "Mark the bug as closed",
+	Usage:       "<id>",
+	RunMethod:   runCloseBug,
+}

commands/command.go 🔗

@@ -44,10 +44,12 @@ var CommandMap map[string]*Command
 // We use init() to avoid a cycle in the data initialization because of the "commands" command
 func init() {
 	CommandMap = map[string]*Command{
+		"close":    closeCmd,
 		"commands": commandsCmd,
 		"comment":  commentCmd,
 		"ls":       lsCmd,
 		"new":      newCmd,
+		"open":     openCmd,
 		"pull":     pullCmd,
 		"push":     pushCmd,
 		"webui":    webUICmd,

commands/comment.go 🔗

@@ -22,11 +22,12 @@ func runComment(repo repository.Repo, args []string) error {
 
 	var err error
 
-	if len(args) == 0 {
-		return errors.New("No bug id provided")
-	}
 	if len(args) > 1 {
-		return errors.New("Only accepting one bug id is supported")
+		return errors.New("Only one bug id is supported")
+	}
+
+	if len(args) == 0 {
+		return errors.New("You must provide a bug id")
 	}
 
 	prefix := args[0]

commands/ls.go 🔗

@@ -22,7 +22,7 @@ func runLsBug(repo repository.Repo, args []string) error {
 
 		snapshot := bug.Compile()
 
-		fmt.Printf("%s %s\t%s\n", bug.HumanId(), snapshot.Title, snapshot.Summary())
+		fmt.Printf("%s %s\t%s\t%s\n", bug.HumanId(), snapshot.Status, snapshot.Title, snapshot.Summary())
 	}
 
 	return nil

commands/open.go 🔗

@@ -0,0 +1,39 @@
+package commands
+
+import (
+	"errors"
+	"github.com/MichaelMure/git-bug/bug"
+	"github.com/MichaelMure/git-bug/bug/operations"
+	"github.com/MichaelMure/git-bug/repository"
+)
+
+func runOpenBug(repo repository.Repo, args []string) error {
+	if len(args) > 1 {
+		return errors.New("Only opening one bug at a time is supported")
+	}
+
+	if len(args) == 0 {
+		return errors.New("You must provide a bug id")
+	}
+
+	prefix := args[0]
+
+	b, err := bug.FindBug(repo, prefix)
+	if err != nil {
+		return err
+	}
+
+	op := operations.NewSetStatusOp(bug.OpenStatus)
+
+	b.Append(op)
+
+	err = b.Commit(repo)
+
+	return err
+}
+
+var openCmd = &Command{
+	Description: "Mark the bug as open",
+	Usage:       "<id>",
+	RunMethod:   runOpenBug,
+}

tests/bug_test.go 🔗

@@ -29,19 +29,19 @@ func TestBugValidity(t *testing.T) {
 	bug1.Append(createOp)
 
 	if !bug1.IsValid() {
-		t.Fatal("Bug with just a CREATE should be valid")
+		t.Fatal("Bug with just a CreateOp should be valid")
 	}
 
 	bug1.Append(createOp)
 
 	if bug1.IsValid() {
-		t.Fatal("Bug with multiple CREATE should be invalid")
+		t.Fatal("Bug with multiple CreateOp should be invalid")
 	}
 
 	bug1.Commit(mockRepo)
 
 	if bug1.IsValid() {
-		t.Fatal("Bug with multiple CREATE should be invalid")
+		t.Fatal("Bug with multiple CreateOp should be invalid")
 	}
 }
 

tests/operation_iterator_test.go 🔗

@@ -16,6 +16,7 @@ var (
 	createOp     = operations.NewCreateOp(rene, "title", "message")
 	setTitleOp   = operations.NewSetTitleOp("title2")
 	addCommentOp = operations.NewAddCommentOp(rene, "message2")
+	SetStatusOp  = operations.NewSetStatusOp(bug.ClosedStatus)
 	mockRepo     = repository.NewMockRepoForTest()
 )
 
@@ -29,6 +30,7 @@ func TestOpIterator(t *testing.T) {
 
 	bug1.Append(createOp)
 	bug1.Append(setTitleOp)
+	bug1.Append(SetStatusOp)
 	bug1.Commit(mockRepo)
 
 	bug1.Append(setTitleOp)