aboutsummaryrefslogtreecommitdiffstats
path: root/bug
diff options
context:
space:
mode:
Diffstat (limited to 'bug')
-rw-r--r--bug/bug_test.go7
-rw-r--r--bug/op_add_comment.go28
-rw-r--r--bug/op_create.go43
-rw-r--r--bug/op_create_test.go20
-rw-r--r--bug/op_edit_comment.go123
-rw-r--r--bug/op_edit_comment_test.go53
-rw-r--r--bug/op_label_change.go16
-rw-r--r--bug/op_set_status.go18
-rw-r--r--bug/op_set_title.go22
-rw-r--r--bug/operation.go10
-rw-r--r--bug/operation_pack.go14
-rw-r--r--bug/operation_test.go2
-rw-r--r--bug/snapshot.go30
-rw-r--r--bug/timeline.go62
14 files changed, 378 insertions, 70 deletions
diff --git a/bug/bug_test.go b/bug/bug_test.go
index 6504da1a..a7759ad8 100644
--- a/bug/bug_test.go
+++ b/bug/bug_test.go
@@ -78,6 +78,13 @@ func TestBugSerialisation(t *testing.T) {
bug2.packs[0].Operations[i].base().hash = bug1.packs[0].Operations[i].base().hash
}
+ // check hashes
+ for i := range bug1.packs[0].Operations {
+ if !bug2.packs[0].Operations[i].base().hash.IsValid() {
+ t.Fatal("invalid hash")
+ }
+ }
+
deep.CompareUnexportedFields = true
if diff := deep.Equal(bug1, bug2); diff != nil {
t.Fatal(diff)
diff --git a/bug/op_add_comment.go b/bug/op_add_comment.go
index e5622073..4594ba70 100644
--- a/bug/op_add_comment.go
+++ b/bug/op_add_comment.go
@@ -7,10 +7,9 @@ import (
"github.com/MichaelMure/git-bug/util/text"
)
-// AddCommentOperation will add a new comment in the bug
-
-var _ Operation = AddCommentOperation{}
+var _ Operation = &AddCommentOperation{}
+// AddCommentOperation will add a new comment in the bug
type AddCommentOperation struct {
*OpBase
Message string `json:"message"`
@@ -18,15 +17,15 @@ type AddCommentOperation struct {
Files []git.Hash `json:"files"`
}
-func (op AddCommentOperation) base() *OpBase {
+func (op *AddCommentOperation) base() *OpBase {
return op.OpBase
}
-func (op AddCommentOperation) Hash() (git.Hash, error) {
+func (op *AddCommentOperation) Hash() (git.Hash, error) {
return hashOperation(op)
}
-func (op AddCommentOperation) Apply(snapshot *Snapshot) {
+func (op *AddCommentOperation) Apply(snapshot *Snapshot) {
comment := Comment{
Message: op.Message,
Author: op.Author,
@@ -35,13 +34,22 @@ func (op AddCommentOperation) Apply(snapshot *Snapshot) {
}
snapshot.Comments = append(snapshot.Comments, comment)
+
+ hash, err := op.Hash()
+ if err != nil {
+ // Should never error unless a programming error happened
+ // (covered in OpBase.Validate())
+ panic(err)
+ }
+
+ snapshot.Timeline = append(snapshot.Timeline, NewCommentTimelineItem(hash, comment))
}
-func (op AddCommentOperation) GetFiles() []git.Hash {
+func (op *AddCommentOperation) GetFiles() []git.Hash {
return op.Files
}
-func (op AddCommentOperation) Validate() error {
+func (op *AddCommentOperation) Validate() error {
if err := opBaseValidate(op, AddCommentOp); err != nil {
return err
}
@@ -57,8 +65,8 @@ func (op AddCommentOperation) Validate() error {
return nil
}
-func NewAddCommentOp(author Person, unixTime int64, message string, files []git.Hash) AddCommentOperation {
- return AddCommentOperation{
+func NewAddCommentOp(author Person, unixTime int64, message string, files []git.Hash) *AddCommentOperation {
+ return &AddCommentOperation{
OpBase: newOpBase(AddCommentOp, author, unixTime),
Message: message,
Files: files,
diff --git a/bug/op_create.go b/bug/op_create.go
index 70ca7242..5c41eb7c 100644
--- a/bug/op_create.go
+++ b/bug/op_create.go
@@ -8,10 +8,9 @@ import (
"github.com/MichaelMure/git-bug/util/text"
)
-// CreateOperation define the initial creation of a bug
-
-var _ Operation = CreateOperation{}
+var _ Operation = &CreateOperation{}
+// CreateOperation define the initial creation of a bug
type CreateOperation struct {
*OpBase
Title string `json:"title"`
@@ -19,32 +18,44 @@ type CreateOperation struct {
Files []git.Hash `json:"files"`
}
-func (op CreateOperation) base() *OpBase {
+func (op *CreateOperation) base() *OpBase {
return op.OpBase
}
-func (op CreateOperation) Hash() (git.Hash, error) {
+func (op *CreateOperation) Hash() (git.Hash, error) {
return hashOperation(op)
}
-func (op CreateOperation) Apply(snapshot *Snapshot) {
+func (op *CreateOperation) Apply(snapshot *Snapshot) {
snapshot.Title = op.Title
- snapshot.Comments = []Comment{
- {
- Message: op.Message,
- Author: op.Author,
- UnixTime: op.UnixTime,
- },
+
+ comment := Comment{
+ Message: op.Message,
+ Author: op.Author,
+ UnixTime: op.UnixTime,
}
+
+ snapshot.Comments = []Comment{comment}
snapshot.Author = op.Author
snapshot.CreatedAt = op.Time()
+
+ hash, err := op.Hash()
+ if err != nil {
+ // Should never error unless a programming error happened
+ // (covered in OpBase.Validate())
+ panic(err)
+ }
+
+ snapshot.Timeline = []TimelineItem{
+ NewCreateTimelineItem(hash, comment),
+ }
}
-func (op CreateOperation) GetFiles() []git.Hash {
+func (op *CreateOperation) GetFiles() []git.Hash {
return op.Files
}
-func (op CreateOperation) Validate() error {
+func (op *CreateOperation) Validate() error {
if err := opBaseValidate(op, CreateOp); err != nil {
return err
}
@@ -68,8 +79,8 @@ func (op CreateOperation) Validate() error {
return nil
}
-func NewCreateOp(author Person, unixTime int64, title, message string, files []git.Hash) CreateOperation {
- return CreateOperation{
+func NewCreateOp(author Person, unixTime int64, title, message string, files []git.Hash) *CreateOperation {
+ return &CreateOperation{
OpBase: newOpBase(CreateOp, author, unixTime),
Title: title,
Message: message,
diff --git a/bug/op_create_test.go b/bug/op_create_test.go
index 2c5ae35b..e9a36cf8 100644
--- a/bug/op_create_test.go
+++ b/bug/op_create_test.go
@@ -1,9 +1,10 @@
package bug
import (
- "reflect"
"testing"
"time"
+
+ "github.com/go-test/deep"
)
func TestCreate(t *testing.T) {
@@ -20,16 +21,27 @@ func TestCreate(t *testing.T) {
create.Apply(&snapshot)
+ hash, err := create.Hash()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ comment := Comment{Author: rene, Message: "message", UnixTime: create.UnixTime}
+
expected := Snapshot{
Title: "title",
Comments: []Comment{
- {Author: rene, Message: "message", UnixTime: create.UnixTime},
+ comment,
},
Author: rene,
CreatedAt: create.Time(),
+ Timeline: []TimelineItem{
+ NewCreateTimelineItem(hash, comment),
+ },
}
- if !reflect.DeepEqual(snapshot, expected) {
- t.Fatalf("%v different than %v", snapshot, expected)
+ deep.CompareUnexportedFields = true
+ if diff := deep.Equal(snapshot, expected); diff != nil {
+ t.Fatal(diff)
}
}
diff --git a/bug/op_edit_comment.go b/bug/op_edit_comment.go
new file mode 100644
index 00000000..c6cfab2b
--- /dev/null
+++ b/bug/op_edit_comment.go
@@ -0,0 +1,123 @@
+package bug
+
+import (
+ "fmt"
+
+ "github.com/MichaelMure/git-bug/util/git"
+ "github.com/MichaelMure/git-bug/util/text"
+)
+
+var _ Operation = &EditCommentOperation{}
+
+// EditCommentOperation will change a comment in the bug
+type EditCommentOperation struct {
+ *OpBase
+ Target git.Hash `json:"target"`
+ Message string `json:"message"`
+ Files []git.Hash `json:"files"`
+}
+
+func (op *EditCommentOperation) base() *OpBase {
+ return op.OpBase
+}
+
+func (op *EditCommentOperation) Hash() (git.Hash, error) {
+ return hashOperation(op)
+}
+
+func (op *EditCommentOperation) Apply(snapshot *Snapshot) {
+ // Todo: currently any message can be edited, even by a different author
+ // crypto signature are needed.
+
+ var target TimelineItem
+ var commentIndex int
+
+ for i, item := range snapshot.Timeline {
+ h, err := item.Hash()
+
+ if err != nil {
+ // Should never happen, we control what goes into the timeline
+ panic(err)
+ }
+
+ if h == op.Target {
+ target = snapshot.Timeline[i]
+ break
+ }
+
+ // Track the index in the []Comment
+ switch item.(type) {
+ case *CreateTimelineItem, *CommentTimelineItem:
+ commentIndex++
+ }
+ }
+
+ if target == nil {
+ // Target not found, edit is a no-op
+ return
+ }
+
+ switch target.(type) {
+ case *CreateTimelineItem:
+ item := target.(*CreateTimelineItem)
+ newComment := item.LastState()
+ newComment.Message = op.Message
+ item.History = append(item.History, newComment)
+
+ case *CommentTimelineItem:
+ item := target.(*CommentTimelineItem)
+ newComment := item.LastState()
+ newComment.Message = op.Message
+ item.History = append(item.History, newComment)
+ }
+
+ snapshot.Comments[commentIndex].Message = op.Message
+ snapshot.Comments[commentIndex].Files = op.Files
+}
+
+func (op *EditCommentOperation) GetFiles() []git.Hash {
+ return op.Files
+}
+
+func (op *EditCommentOperation) Validate() error {
+ if err := opBaseValidate(op, EditCommentOp); err != nil {
+ return err
+ }
+
+ if !op.Target.IsValid() {
+ return fmt.Errorf("target hash is invalid")
+ }
+
+ if text.Empty(op.Message) {
+ return fmt.Errorf("message is empty")
+ }
+
+ if !text.Safe(op.Message) {
+ return fmt.Errorf("message is not fully printable")
+ }
+
+ return nil
+}
+
+func NewEditCommentOp(author Person, unixTime int64, target git.Hash, message string, files []git.Hash) *EditCommentOperation {
+ return &EditCommentOperation{
+ OpBase: newOpBase(EditCommentOp, author, unixTime),
+ Target: target,
+ Message: message,
+ Files: files,
+ }
+}
+
+// Convenience function to apply the operation
+func EditComment(b Interface, author Person, unixTime int64, target git.Hash, message string) error {
+ return EditCommentWithFiles(b, author, unixTime, target, message, nil)
+}
+
+func EditCommentWithFiles(b Interface, author Person, unixTime int64, target git.Hash, message string, files []git.Hash) error {
+ editCommentOp := NewEditCommentOp(author, unixTime, target, message, files)
+ if err := editCommentOp.Validate(); err != nil {
+ return err
+ }
+ b.Append(editCommentOp)
+ return nil
+}
diff --git a/bug/op_edit_comment_test.go b/bug/op_edit_comment_test.go
new file mode 100644
index 00000000..9c32051d
--- /dev/null
+++ b/bug/op_edit_comment_test.go
@@ -0,0 +1,53 @@
+package bug
+
+import (
+ "testing"
+ "time"
+
+ "gotest.tools/assert"
+)
+
+func TestEdit(t *testing.T) {
+ snapshot := Snapshot{}
+
+ var rene = Person{
+ Name: "René Descartes",
+ Email: "rene@descartes.fr",
+ }
+
+ unix := time.Now().Unix()
+
+ create := NewCreateOp(rene, unix, "title", "create", nil)
+ create.Apply(&snapshot)
+
+ hash1, err := create.Hash()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ comment := NewAddCommentOp(rene, unix, "comment", nil)
+ comment.Apply(&snapshot)
+
+ hash2, err := comment.Hash()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ edit := NewEditCommentOp(rene, unix, hash1, "create edited", nil)
+ edit.Apply(&snapshot)
+
+ assert.Equal(t, len(snapshot.Timeline), 2)
+ assert.Equal(t, len(snapshot.Timeline[0].(*CreateTimelineItem).History), 2)
+ assert.Equal(t, len(snapshot.Timeline[1].(*CommentTimelineItem).History), 1)
+ assert.Equal(t, snapshot.Comments[0].Message, "create edited")
+ assert.Equal(t, snapshot.Comments[1].Message, "comment")
+
+ edit2 := NewEditCommentOp(rene, unix, hash2, "comment edited", nil)
+ edit2.Apply(&snapshot)
+
+ assert.Equal(t, len(snapshot.Timeline), 2)
+ assert.Equal(t, len(snapshot.Timeline[0].(*CreateTimelineItem).History), 2)
+ assert.Equal(t, len(snapshot.Timeline[1].(*CommentTimelineItem).History), 2)
+ assert.Equal(t, snapshot.Comments[0].Message, "create edited")
+ assert.Equal(t, snapshot.Comments[1].Message, "comment edited")
+}
diff --git a/bug/op_label_change.go b/bug/op_label_change.go
index 8909cf56..5f2dbd6f 100644
--- a/bug/op_label_change.go
+++ b/bug/op_label_change.go
@@ -8,7 +8,7 @@ import (
"github.com/pkg/errors"
)
-var _ Operation = LabelChangeOperation{}
+var _ Operation = &LabelChangeOperation{}
// LabelChangeOperation define a Bug operation to add or remove labels
type LabelChangeOperation struct {
@@ -17,16 +17,16 @@ type LabelChangeOperation struct {
Removed []Label `json:"removed"`
}
-func (op LabelChangeOperation) base() *OpBase {
+func (op *LabelChangeOperation) base() *OpBase {
return op.OpBase
}
-func (op LabelChangeOperation) Hash() (git.Hash, error) {
+func (op *LabelChangeOperation) Hash() (git.Hash, error) {
return hashOperation(op)
}
// Apply apply the operation
-func (op LabelChangeOperation) Apply(snapshot *Snapshot) {
+func (op *LabelChangeOperation) Apply(snapshot *Snapshot) {
// Add in the set
AddLoop:
for _, added := range op.Added {
@@ -54,9 +54,11 @@ AddLoop:
sort.Slice(snapshot.Labels, func(i, j int) bool {
return string(snapshot.Labels[i]) < string(snapshot.Labels[j])
})
+
+ snapshot.Timeline = append(snapshot.Timeline, op)
}
-func (op LabelChangeOperation) Validate() error {
+func (op *LabelChangeOperation) Validate() error {
if err := opBaseValidate(op, LabelChangeOp); err != nil {
return err
}
@@ -80,8 +82,8 @@ func (op LabelChangeOperation) Validate() error {
return nil
}
-func NewLabelChangeOperation(author Person, unixTime int64, added, removed []Label) LabelChangeOperation {
- return LabelChangeOperation{
+func NewLabelChangeOperation(author Person, unixTime int64, added, removed []Label) *LabelChangeOperation {
+ return &LabelChangeOperation{
OpBase: newOpBase(LabelChangeOp, author, unixTime),
Added: added,
Removed: removed,
diff --git a/bug/op_set_status.go b/bug/op_set_status.go
index 1fb9cab6..cdfa25e7 100644
--- a/bug/op_set_status.go
+++ b/bug/op_set_status.go
@@ -5,28 +5,28 @@ import (
"github.com/pkg/errors"
)
-// SetStatusOperation will change the status of a bug
-
-var _ Operation = SetStatusOperation{}
+var _ Operation = &SetStatusOperation{}
+// SetStatusOperation will change the status of a bug
type SetStatusOperation struct {
*OpBase
Status Status `json:"status"`
}
-func (op SetStatusOperation) base() *OpBase {
+func (op *SetStatusOperation) base() *OpBase {
return op.OpBase
}
-func (op SetStatusOperation) Hash() (git.Hash, error) {
+func (op *SetStatusOperation) Hash() (git.Hash, error) {
return hashOperation(op)
}
-func (op SetStatusOperation) Apply(snapshot *Snapshot) {
+func (op *SetStatusOperation) Apply(snapshot *Snapshot) {
snapshot.Status = op.Status
+ snapshot.Timeline = append(snapshot.Timeline, op)
}
-func (op SetStatusOperation) Validate() error {
+func (op *SetStatusOperation) Validate() error {
if err := opBaseValidate(op, SetStatusOp); err != nil {
return err
}
@@ -38,8 +38,8 @@ func (op SetStatusOperation) Validate() error {
return nil
}
-func NewSetStatusOp(author Person, unixTime int64, status Status) SetStatusOperation {
- return SetStatusOperation{
+func NewSetStatusOp(author Person, unixTime int64, status Status) *SetStatusOperation {
+ return &SetStatusOperation{
OpBase: newOpBase(SetStatusOp, author, unixTime),
Status: status,
}
diff --git a/bug/op_set_title.go b/bug/op_set_title.go
index 6049ebd0..74467ec2 100644
--- a/bug/op_set_title.go
+++ b/bug/op_set_title.go
@@ -8,29 +8,29 @@ import (
"github.com/MichaelMure/git-bug/util/text"
)
-// SetTitleOperation will change the title of a bug
-
-var _ Operation = SetTitleOperation{}
+var _ Operation = &SetTitleOperation{}
+// SetTitleOperation will change the title of a bug
type SetTitleOperation struct {
*OpBase
Title string `json:"title"`
Was string `json:"was"`
}
-func (op SetTitleOperation) base() *OpBase {
+func (op *SetTitleOperation) base() *OpBase {
return op.OpBase
}
-func (op SetTitleOperation) Hash() (git.Hash, error) {
+func (op *SetTitleOperation) Hash() (git.Hash, error) {
return hashOperation(op)
}
-func (op SetTitleOperation) Apply(snapshot *Snapshot) {
+func (op *SetTitleOperation) Apply(snapshot *Snapshot) {
snapshot.Title = op.Title
+ snapshot.Timeline = append(snapshot.Timeline, op)
}
-func (op SetTitleOperation) Validate() error {
+func (op *SetTitleOperation) Validate() error {
if err := opBaseValidate(op, SetTitleOp); err != nil {
return err
}
@@ -58,8 +58,8 @@ func (op SetTitleOperation) Validate() error {
return nil
}
-func NewSetTitleOp(author Person, unixTime int64, title string, was string) SetTitleOperation {
- return SetTitleOperation{
+func NewSetTitleOp(author Person, unixTime int64, title string, was string) *SetTitleOperation {
+ return &SetTitleOperation{
OpBase: newOpBase(SetTitleOp, author, unixTime),
Title: title,
Was: was,
@@ -80,9 +80,9 @@ func SetTitle(b Interface, author Person, unixTime int64, title string) error {
var was string
if lastTitleOp != nil {
- was = lastTitleOp.(SetTitleOperation).Title
+ was = lastTitleOp.(*SetTitleOperation).Title
} else {
- was = b.FirstOp().(CreateOperation).Title
+ was = b.FirstOp().(*CreateOperation).Title
}
setTitleOp := NewSetTitleOp(author, unixTime, title, was)
diff --git a/bug/operation.go b/bug/operation.go
index d7d5af51..84dd91ee 100644
--- a/bug/operation.go
+++ b/bug/operation.go
@@ -20,13 +20,14 @@ const (
AddCommentOp
SetStatusOp
LabelChangeOp
+ EditCommentOp
)
// Operation define the interface to fulfill for an edit operation of a Bug
type Operation interface {
// base return the OpBase of the Operation, for package internal use
base() *OpBase
- // Hash return the hash of the operation
+ // Hash return the hash of the operation, to be used for back references
Hash() (git.Hash, error)
// Time return the time when the operation was added
Time() time.Time
@@ -46,7 +47,8 @@ type Operation interface {
func hashRaw(data []byte) git.Hash {
hasher := sha256.New()
- return git.Hash(fmt.Sprintf("%x", hasher.Sum(data)))
+ hasher.Write(data)
+ return git.Hash(fmt.Sprintf("%x", hasher.Sum(nil)))
}
// hash compute the hash of the serialized operation
@@ -106,6 +108,10 @@ func opBaseValidate(op Operation, opType OperationType) error {
return fmt.Errorf("incorrect operation type (expected: %v, actual: %v)", opType, op.base().OperationType)
}
+ if _, err := op.Hash(); err != nil {
+ return errors.Wrap(err, "op is not serializable")
+ }
+
if op.GetUnixTime() == 0 {
return fmt.Errorf("time not set")
}
diff --git a/bug/operation_pack.go b/bug/operation_pack.go
index 5238ea60..f33d94bf 100644
--- a/bug/operation_pack.go
+++ b/bug/operation_pack.go
@@ -74,23 +74,27 @@ func (opp *OperationPack) UnmarshalJSON(data []byte) error {
func (opp *OperationPack) unmarshalOp(raw []byte, _type OperationType) (Operation, error) {
switch _type {
case CreateOp:
- op := CreateOperation{}
+ op := &CreateOperation{}
err := json.Unmarshal(raw, &op)
return op, err
case SetTitleOp:
- op := SetTitleOperation{}
+ op := &SetTitleOperation{}
err := json.Unmarshal(raw, &op)
return op, err
case AddCommentOp:
- op := AddCommentOperation{}
+ op := &AddCommentOperation{}
err := json.Unmarshal(raw, &op)
return op, err
case SetStatusOp:
- op := SetStatusOperation{}
+ op := &SetStatusOperation{}
err := json.Unmarshal(raw, &op)
return op, err
case LabelChangeOp:
- op := LabelChangeOperation{}
+ op := &LabelChangeOperation{}
+ err := json.Unmarshal(raw, &op)
+ return op, err
+ case EditCommentOp:
+ op := &EditCommentOperation{}
err := json.Unmarshal(raw, &op)
return op, err
default:
diff --git a/bug/operation_test.go b/bug/operation_test.go
index 8561adf3..098cf138 100644
--- a/bug/operation_test.go
+++ b/bug/operation_test.go
@@ -36,7 +36,7 @@ func TestValidate(t *testing.T) {
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{
+ &CreateOperation{OpBase: &OpBase{
Author: rene,
UnixTime: 0,
OperationType: CreateOp,
diff --git a/bug/snapshot.go b/bug/snapshot.go
index 59dcae7e..df39ff46 100644
--- a/bug/snapshot.go
+++ b/bug/snapshot.go
@@ -3,6 +3,8 @@ package bug
import (
"fmt"
"time"
+
+ "github.com/MichaelMure/git-bug/util/git"
)
// Snapshot is a compiled form of the Bug data structure used for storage and merge
@@ -16,20 +18,22 @@ type Snapshot struct {
Author Person
CreatedAt time.Time
+ Timeline []TimelineItem
+
Operations []Operation
}
// Return the Bug identifier
-func (snap Snapshot) Id() string {
+func (snap *Snapshot) Id() string {
return snap.id
}
// Return the Bug identifier truncated for human consumption
-func (snap Snapshot) HumanId() string {
+func (snap *Snapshot) HumanId() string {
return fmt.Sprintf("%.8s", snap.id)
}
-func (snap Snapshot) Summary() string {
+func (snap *Snapshot) Summary() string {
return fmt.Sprintf("C:%d L:%d",
len(snap.Comments)-1,
len(snap.Labels),
@@ -37,7 +41,7 @@ func (snap Snapshot) Summary() string {
}
// Return the last time a bug was modified
-func (snap Snapshot) LastEditTime() time.Time {
+func (snap *Snapshot) LastEditTime() time.Time {
if len(snap.Operations) == 0 {
return time.Unix(0, 0)
}
@@ -46,10 +50,26 @@ func (snap Snapshot) LastEditTime() time.Time {
}
// Return the last timestamp a bug was modified
-func (snap Snapshot) LastEditUnix() int64 {
+func (snap *Snapshot) LastEditUnix() int64 {
if len(snap.Operations) == 0 {
return 0
}
return snap.Operations[len(snap.Operations)-1].GetUnixTime()
}
+
+// SearchTimelineItem will search in the timeline for an item matching the given hash
+func (snap *Snapshot) SearchTimelineItem(hash git.Hash) (TimelineItem, error) {
+ for i := range snap.Timeline {
+ h, err := snap.Timeline[i].Hash()
+ if err != nil {
+ return nil, err
+ }
+
+ if h == hash {
+ return snap.Timeline[i], nil
+ }
+ }
+
+ return nil, fmt.Errorf("timeline item not found")
+}
diff --git a/bug/timeline.go b/bug/timeline.go
new file mode 100644
index 00000000..0f79958b
--- /dev/null
+++ b/bug/timeline.go
@@ -0,0 +1,62 @@
+package bug
+
+import "github.com/MichaelMure/git-bug/util/git"
+
+type TimelineItem interface {
+ // Hash return the hash of the item
+ Hash() (git.Hash, error)
+}
+
+// CreateTimelineItem replace a Create operation in the Timeline and hold its edition history
+type CreateTimelineItem struct {
+ hash git.Hash
+ History []Comment
+}
+
+func NewCreateTimelineItem(hash git.Hash, comment Comment) *CreateTimelineItem {
+ return &CreateTimelineItem{
+ hash: hash,
+ History: []Comment{
+ comment,
+ },
+ }
+}
+
+func (c *CreateTimelineItem) Hash() (git.Hash, error) {
+ return c.hash, nil
+}
+
+func (c *CreateTimelineItem) LastState() Comment {
+ if len(c.History) == 0 {
+ panic("no history yet")
+ }
+
+ return c.History[len(c.History)-1]
+}
+
+// CommentTimelineItem replace a Comment in the Timeline and hold its edition history
+type CommentTimelineItem struct {
+ hash git.Hash
+ History []Comment
+}
+
+func NewCommentTimelineItem(hash git.Hash, comment Comment) *CommentTimelineItem {
+ return &CommentTimelineItem{
+ hash: hash,
+ History: []Comment{
+ comment,
+ },
+ }
+}
+
+func (c *CommentTimelineItem) Hash() (git.Hash, error) {
+ return c.hash, nil
+}
+
+func (c *CommentTimelineItem) LastState() Comment {
+ if len(c.History) == 0 {
+ panic("no history yet")
+ }
+
+ return c.History[len(c.History)-1]
+}