diff options
Diffstat (limited to 'bug')
-rw-r--r-- | bug/bug.go | 59 | ||||
-rw-r--r-- | bug/bug_actions.go | 11 | ||||
-rw-r--r-- | bug/bug_test.go | 2 | ||||
-rw-r--r-- | bug/comment.go | 10 | ||||
-rw-r--r-- | bug/interface.go | 6 | ||||
-rw-r--r-- | bug/op_add_comment.go | 45 | ||||
-rw-r--r-- | bug/op_add_comment_test.go | 7 | ||||
-rw-r--r-- | bug/op_create.go | 48 | ||||
-rw-r--r-- | bug/op_create_test.go | 12 | ||||
-rw-r--r-- | bug/op_edit_comment.go | 59 | ||||
-rw-r--r-- | bug/op_edit_comment_test.go | 22 | ||||
-rw-r--r-- | bug/op_label_change.go | 54 | ||||
-rw-r--r-- | bug/op_label_change_test.go | 4 | ||||
-rw-r--r-- | bug/op_noop.go | 28 | ||||
-rw-r--r-- | bug/op_noop_test.go | 4 | ||||
-rw-r--r-- | bug/op_set_metadata.go | 57 | ||||
-rw-r--r-- | bug/op_set_metadata_test.go | 18 | ||||
-rw-r--r-- | bug/op_set_status.go | 50 | ||||
-rw-r--r-- | bug/op_set_status_test.go | 4 | ||||
-rw-r--r-- | bug/op_set_title.go | 50 | ||||
-rw-r--r-- | bug/op_set_title_test.go | 4 | ||||
-rw-r--r-- | bug/operation.go | 79 | ||||
-rw-r--r-- | bug/operation_pack.go | 3 | ||||
-rw-r--r-- | bug/operation_pack_test.go | 10 | ||||
-rw-r--r-- | bug/operation_test.go | 20 | ||||
-rw-r--r-- | bug/snapshot.go | 27 | ||||
-rw-r--r-- | bug/timeline.go | 15 |
27 files changed, 264 insertions, 444 deletions
@@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/git" @@ -26,20 +27,18 @@ const createClockEntryPattern = "create-clock-%d" const editClockEntryPrefix = "edit-clock-" const editClockEntryPattern = "edit-clock-%d" -const idLength = 40 -const humanIdLength = 7 - var ErrBugNotExist = errors.New("bug doesn't exist") -type ErrMultipleMatch struct { - Matching []string +func NewErrMultipleMatchBug(matching []entity.Id) *entity.ErrMultipleMatch { + return entity.NewErrMultipleMatch("bug", matching) } -func (e ErrMultipleMatch) Error() string { - return fmt.Sprintf("Multiple matching bug found:\n%s", strings.Join(e.Matching, "\n")) +func NewErrMultipleMatchOp(matching []entity.Id) *entity.ErrMultipleMatch { + return entity.NewErrMultipleMatch("operation", matching) } var _ Interface = &Bug{} +var _ entity.Interface = &Bug{} // Bug hold the data of a bug thread, organized in a way close to // how it will be persisted inside Git. This is the data structure @@ -53,7 +52,7 @@ type Bug struct { editTime lamport.Time // Id used as unique identifier - id string + id entity.Id lastCommit git.Hash rootPack git.Hash @@ -82,10 +81,10 @@ func FindLocalBug(repo repository.ClockedRepo, prefix string) (*Bug, error) { } // preallocate but empty - matching := make([]string, 0, 5) + matching := make([]entity.Id, 0, 5) for _, id := range ids { - if strings.HasPrefix(id, prefix) { + if id.HasPrefix(prefix) { matching = append(matching, id) } } @@ -95,15 +94,15 @@ func FindLocalBug(repo repository.ClockedRepo, prefix string) (*Bug, error) { } if len(matching) > 1 { - return nil, ErrMultipleMatch{Matching: matching} + return nil, NewErrMultipleMatchBug(matching) } return ReadLocalBug(repo, matching[0]) } // ReadLocalBug will read a local bug from its hash -func ReadLocalBug(repo repository.ClockedRepo, id string) (*Bug, error) { - ref := bugsRefPattern + id +func ReadLocalBug(repo repository.ClockedRepo, id entity.Id) (*Bug, error) { + ref := bugsRefPattern + id.String() return readBug(repo, ref) } @@ -116,10 +115,10 @@ func ReadRemoteBug(repo repository.ClockedRepo, remote string, id string) (*Bug, // readBug will read and parse a Bug from git func readBug(repo repository.ClockedRepo, ref string) (*Bug, error) { refSplit := strings.Split(ref, "/") - id := refSplit[len(refSplit)-1] + id := entity.Id(refSplit[len(refSplit)-1]) - if len(id) != idLength { - return nil, fmt.Errorf("invalid ref length") + if err := id.Validate(); err != nil { + return nil, errors.Wrap(err, "invalid ref ") } hashes, err := repo.ListCommits(ref) @@ -278,7 +277,7 @@ func readAllBugs(repo repository.ClockedRepo, refPrefix string) <-chan StreamedB } // ListLocalIds list all the available local bug ids -func ListLocalIds(repo repository.Repo) ([]string, error) { +func ListLocalIds(repo repository.Repo) ([]entity.Id, error) { refs, err := repo.ListRefs(bugsRefPattern) if err != nil { return nil, err @@ -287,12 +286,12 @@ func ListLocalIds(repo repository.Repo) ([]string, error) { return refsToIds(refs), nil } -func refsToIds(refs []string) []string { - ids := make([]string, len(refs)) +func refsToIds(refs []string) []entity.Id { + ids := make([]entity.Id, len(refs)) for i, ref := range refs { split := strings.Split(ref, "/") - ids[i] = split[len(split)-1] + ids[i] = entity.Id(split[len(split)-1]) } return ids @@ -325,8 +324,8 @@ func (bug *Bug) Validate() error { return fmt.Errorf("first operation should be a Create op") } - // The bug ID should be the hash of the first commit - if len(bug.packs) > 0 && string(bug.packs[0].commitHash) != bug.id { + // The bug Id should be the hash of the first commit + if len(bug.packs) > 0 && string(bug.packs[0].commitHash) != bug.id.String() { return fmt.Errorf("bug id should be the first commit hash") } @@ -456,7 +455,7 @@ func (bug *Bug) Commit(repo repository.ClockedRepo) error { // if it was the first commit, use the commit hash as bug id if bug.id == "" { - bug.id = string(hash) + bug.id = entity.Id(hash) } // Create or update the Git reference for this bug @@ -594,7 +593,7 @@ func (bug *Bug) Merge(repo repository.Repo, other Interface) (bool, error) { } // Update the git ref - err = repo.UpdateRef(bugsRefPattern+bug.id, bug.lastCommit) + err = repo.UpdateRef(bugsRefPattern+bug.id.String(), bug.lastCommit) if err != nil { return false, err } @@ -603,7 +602,7 @@ func (bug *Bug) Merge(repo repository.Repo, other Interface) (bool, error) { } // Id return the Bug identifier -func (bug *Bug) Id() string { +func (bug *Bug) Id() entity.Id { if bug.id == "" { // simply panic as it would be a coding error // (using an id of a bug not stored yet) @@ -612,16 +611,6 @@ func (bug *Bug) Id() string { return bug.id } -// HumanId return the Bug identifier truncated for human consumption -func (bug *Bug) HumanId() string { - return FormatHumanID(bug.Id()) -} - -func FormatHumanID(id string) string { - format := fmt.Sprintf("%%.%ds", humanIdLength) - return fmt.Sprintf(format, id) -} - // CreateLamportTime return the Lamport time of creation func (bug *Bug) CreateLamportTime() lamport.Time { return bug.createTime diff --git a/bug/bug_actions.go b/bug/bug_actions.go index b26d080f..cb0d0f7d 100644 --- a/bug/bug_actions.go +++ b/bug/bug_actions.go @@ -65,8 +65,13 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeRes } for _, remoteRef := range remoteRefs { - refSplitted := strings.Split(remoteRef, "/") - id := refSplitted[len(refSplitted)-1] + refSplit := strings.Split(remoteRef, "/") + id := entity.Id(refSplit[len(refSplit)-1]) + + if err := id.Validate(); err != nil { + out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error()) + continue + } remoteBug, err := readBug(repo, remoteRef) @@ -81,7 +86,7 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeRes continue } - localRef := bugsRefPattern + remoteBug.Id() + localRef := bugsRefPattern + remoteBug.Id().String() localExist, err := repo.RefExist(localRef) if err != nil { diff --git a/bug/bug_test.go b/bug/bug_test.go index 4c3952eb..4e8a9440 100644 --- a/bug/bug_test.go +++ b/bug/bug_test.go @@ -103,7 +103,7 @@ func equivalentBug(t *testing.T, expected, actual *Bug) { for i := range expected.packs { for j := range expected.packs[i].Operations { - actual.packs[i].Operations[j].base().hash = expected.packs[i].Operations[j].base().hash + actual.packs[i].Operations[j].base().id = expected.packs[i].Operations[j].base().id } } diff --git a/bug/comment.go b/bug/comment.go index 5db0b18d..47c1ff05 100644 --- a/bug/comment.go +++ b/bug/comment.go @@ -1,6 +1,7 @@ package bug import ( + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/timestamp" @@ -9,7 +10,7 @@ import ( // Comment represent a comment in a Bug type Comment struct { - id string + id entity.Id Author identity.Interface Message string Files []git.Hash @@ -20,7 +21,7 @@ type Comment struct { } // Id return the Comment identifier -func (c Comment) Id() string { +func (c Comment) Id() entity.Id { if c.id == "" { // simply panic as it would be a coding error // (using an id of an identity not stored yet) @@ -29,11 +30,6 @@ func (c Comment) Id() string { return c.id } -// HumanId return the Comment identifier truncated for human consumption -func (c Comment) HumanId() string { - return FormatHumanID(c.Id()) -} - // FormatTimeRel format the UnixTime of the comment for human consumption func (c Comment) FormatTimeRel() string { return humanize.Time(c.UnixTime.Time()) diff --git a/bug/interface.go b/bug/interface.go index 186c26fc..8266e99e 100644 --- a/bug/interface.go +++ b/bug/interface.go @@ -1,16 +1,14 @@ package bug import ( + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/lamport" ) type Interface interface { // Id return the Bug identifier - Id() string - - // HumanId return the Bug identifier truncated for human consumption - HumanId() string + Id() entity.Id // Validate check if the Bug data is valid Validate() error diff --git a/bug/op_add_comment.go b/bug/op_add_comment.go index 095b7f43..e16ea0dd 100644 --- a/bug/op_add_comment.go +++ b/bug/op_add_comment.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/text" @@ -15,32 +16,25 @@ var _ Operation = &AddCommentOperation{} // AddCommentOperation will add a new comment in the bug type AddCommentOperation struct { OpBase - Message string + Message string `json:"message"` // TODO: change for a map[string]util.hash to store the filename ? - Files []git.Hash + Files []git.Hash `json:"files"` } func (op *AddCommentOperation) base() *OpBase { return &op.OpBase } -func (op *AddCommentOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *AddCommentOperation) Id() entity.Id { + return idOperation(op) } func (op *AddCommentOperation) Apply(snapshot *Snapshot) { snapshot.addActor(op.Author) snapshot.addParticipant(op.Author) - hash, err := op.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - comment := Comment{ - id: string(hash), + id: op.Id(), Message: op.Message, Author: op.Author, Files: op.Files, @@ -50,7 +44,7 @@ func (op *AddCommentOperation) Apply(snapshot *Snapshot) { snapshot.Comments = append(snapshot.Comments, comment) item := &AddCommentTimelineItem{ - CommentTimelineItem: NewCommentTimelineItem(hash, comment), + CommentTimelineItem: NewCommentTimelineItem(op.Id(), comment), } snapshot.Timeline = append(snapshot.Timeline, item) @@ -72,28 +66,9 @@ func (op *AddCommentOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *AddCommentOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["message"] = op.Message - data["files"] = op.Files - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *AddCommentOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately diff --git a/bug/op_add_comment_test.go b/bug/op_add_comment_test.go index a38d0228..60364cf1 100644 --- a/bug/op_add_comment_test.go +++ b/bug/op_add_comment_test.go @@ -5,8 +5,9 @@ import ( "testing" "time" - "github.com/MichaelMure/git-bug/identity" "github.com/stretchr/testify/assert" + + "github.com/MichaelMure/git-bug/identity" ) func TestAddCommentSerialize(t *testing.T) { @@ -21,5 +22,9 @@ func TestAddCommentSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_create.go b/bug/op_create.go index e52e6254..0da95d4d 100644 --- a/bug/op_create.go +++ b/bug/op_create.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/text" @@ -16,34 +17,27 @@ var _ Operation = &CreateOperation{} // CreateOperation define the initial creation of a bug type CreateOperation struct { OpBase - Title string - Message string - Files []git.Hash + Title string `json:"title"` + Message string `json:"message"` + Files []git.Hash `json:"files"` } func (op *CreateOperation) base() *OpBase { return &op.OpBase } -func (op *CreateOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *CreateOperation) Id() entity.Id { + return idOperation(op) } func (op *CreateOperation) Apply(snapshot *Snapshot) { snapshot.addActor(op.Author) snapshot.addParticipant(op.Author) - hash, err := op.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - snapshot.Title = op.Title comment := Comment{ - id: string(hash), + id: op.Id(), Message: op.Message, Author: op.Author, UnixTime: timestamp.Timestamp(op.UnixTime), @@ -55,7 +49,7 @@ func (op *CreateOperation) Apply(snapshot *Snapshot) { snapshot.Timeline = []TimelineItem{ &CreateTimelineItem{ - CommentTimelineItem: NewCommentTimelineItem(hash, comment), + CommentTimelineItem: NewCommentTimelineItem(op.Id(), comment), }, } } @@ -88,29 +82,9 @@ func (op *CreateOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *CreateOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["title"] = op.Title - data["message"] = op.Message - data["files"] = op.Files - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *CreateOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately diff --git a/bug/op_create_test.go b/bug/op_create_test.go index 2a88f256..ec53b04b 100644 --- a/bug/op_create_test.go +++ b/bug/op_create_test.go @@ -20,11 +20,11 @@ func TestCreate(t *testing.T) { create.Apply(&snapshot) - hash, err := create.Hash() - assert.NoError(t, err) + id := create.Id() + assert.NoError(t, id.Validate()) comment := Comment{ - id: string(hash), + id: id, Author: rene, Message: "message", UnixTime: timestamp.Timestamp(create.UnixTime), @@ -41,7 +41,7 @@ func TestCreate(t *testing.T) { CreatedAt: create.Time(), Timeline: []TimelineItem{ &CreateTimelineItem{ - CommentTimelineItem: NewCommentTimelineItem(hash, comment), + CommentTimelineItem: NewCommentTimelineItem(id, comment), }, }, } @@ -61,5 +61,9 @@ func TestCreateSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_edit_comment.go b/bug/op_edit_comment.go index 01832959..a37ce8f7 100644 --- a/bug/op_edit_comment.go +++ b/bug/op_edit_comment.go @@ -4,6 +4,9 @@ import ( "encoding/json" "fmt" + "github.com/pkg/errors" + + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/timestamp" @@ -16,17 +19,17 @@ var _ Operation = &EditCommentOperation{} // EditCommentOperation will change a comment in the bug type EditCommentOperation struct { OpBase - Target git.Hash - Message string - Files []git.Hash + Target entity.Id `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) Id() entity.Id { + return idOperation(op) } func (op *EditCommentOperation) Apply(snapshot *Snapshot) { @@ -38,9 +41,7 @@ func (op *EditCommentOperation) Apply(snapshot *Snapshot) { var target TimelineItem for i, item := range snapshot.Timeline { - h := item.Hash() - - if h == op.Target { + if item.Id() == op.Target { target = snapshot.Timeline[i] break } @@ -52,7 +53,7 @@ func (op *EditCommentOperation) Apply(snapshot *Snapshot) { } comment := Comment{ - id: string(op.Target), + id: op.Target, Message: op.Message, Files: op.Files, UnixTime: timestamp.Timestamp(op.UnixTime), @@ -71,7 +72,7 @@ func (op *EditCommentOperation) Apply(snapshot *Snapshot) { // Updating the corresponding comment for i := range snapshot.Comments { - if snapshot.Comments[i].Id() == string(op.Target) { + if snapshot.Comments[i].Id() == op.Target { snapshot.Comments[i].Message = op.Message snapshot.Comments[i].Files = op.Files break @@ -88,8 +89,8 @@ func (op *EditCommentOperation) Validate() error { return err } - if !op.Target.IsValid() { - return fmt.Errorf("target hash is invalid") + if err := op.Target.Validate(); err != nil { + return errors.Wrap(err, "target hash is invalid") } if !text.Safe(op.Message) { @@ -99,29 +100,9 @@ func (op *EditCommentOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *EditCommentOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["target"] = op.Target - data["message"] = op.Message - data["files"] = op.Files - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *EditCommentOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately @@ -132,7 +113,7 @@ func (op *EditCommentOperation) UnmarshalJSON(data []byte) error { } aux := struct { - Target git.Hash `json:"target"` + Target entity.Id `json:"target"` Message string `json:"message"` Files []git.Hash `json:"files"` }{} @@ -153,7 +134,7 @@ func (op *EditCommentOperation) UnmarshalJSON(data []byte) error { // Sign post method for gqlgen func (op *EditCommentOperation) IsAuthored() {} -func NewEditCommentOp(author identity.Interface, unixTime int64, target git.Hash, message string, files []git.Hash) *EditCommentOperation { +func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.Id, message string, files []git.Hash) *EditCommentOperation { return &EditCommentOperation{ OpBase: newOpBase(EditCommentOp, author, unixTime), Target: target, @@ -163,11 +144,11 @@ func NewEditCommentOp(author identity.Interface, unixTime int64, target git.Hash } // Convenience function to apply the operation -func EditComment(b Interface, author identity.Interface, unixTime int64, target git.Hash, message string) (*EditCommentOperation, error) { +func EditComment(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string) (*EditCommentOperation, error) { return EditCommentWithFiles(b, author, unixTime, target, message, nil) } -func EditCommentWithFiles(b Interface, author identity.Interface, unixTime int64, target git.Hash, message string, files []git.Hash) (*EditCommentOperation, error) { +func EditCommentWithFiles(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string, files []git.Hash) (*EditCommentOperation, error) { editCommentOp := NewEditCommentOp(author, unixTime, target, message, files) if err := editCommentOp.Validate(); err != nil { return nil, err diff --git a/bug/op_edit_comment_test.go b/bug/op_edit_comment_test.go index ab0f2d21..abd550cb 100644 --- a/bug/op_edit_comment_test.go +++ b/bug/op_edit_comment_test.go @@ -20,14 +20,14 @@ func TestEdit(t *testing.T) { create := NewCreateOp(rene, unix, "title", "create", nil) create.Apply(&snapshot) - hash1, err := create.Hash() - require.NoError(t, err) + id1 := create.Id() + require.NoError(t, id1.Validate()) comment1 := NewAddCommentOp(rene, unix, "comment 1", nil) comment1.Apply(&snapshot) - hash2, err := comment1.Hash() - require.NoError(t, err) + id2 := comment1.Id() + require.NoError(t, id2.Validate()) // add another unrelated op in between setTitle := NewSetTitleOp(rene, unix, "edited title", "title") @@ -36,10 +36,10 @@ func TestEdit(t *testing.T) { comment2 := NewAddCommentOp(rene, unix, "comment 2", nil) comment2.Apply(&snapshot) - hash3, err := comment2.Hash() - require.NoError(t, err) + id3 := comment2.Id() + require.NoError(t, id3.Validate()) - edit := NewEditCommentOp(rene, unix, hash1, "create edited", nil) + edit := NewEditCommentOp(rene, unix, id1, "create edited", nil) edit.Apply(&snapshot) assert.Equal(t, len(snapshot.Timeline), 4) @@ -50,7 +50,7 @@ func TestEdit(t *testing.T) { assert.Equal(t, snapshot.Comments[1].Message, "comment 1") assert.Equal(t, snapshot.Comments[2].Message, "comment 2") - edit2 := NewEditCommentOp(rene, unix, hash2, "comment 1 edited", nil) + edit2 := NewEditCommentOp(rene, unix, id2, "comment 1 edited", nil) edit2.Apply(&snapshot) assert.Equal(t, len(snapshot.Timeline), 4) @@ -61,7 +61,7 @@ func TestEdit(t *testing.T) { assert.Equal(t, snapshot.Comments[1].Message, "comment 1 edited") assert.Equal(t, snapshot.Comments[2].Message, "comment 2") - edit3 := NewEditCommentOp(rene, unix, hash3, "comment 2 edited", nil) + edit3 := NewEditCommentOp(rene, unix, id3, "comment 2 edited", nil) edit3.Apply(&snapshot) assert.Equal(t, len(snapshot.Timeline), 4) @@ -85,5 +85,9 @@ func TestEditCommentSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_label_change.go b/bug/op_label_change.go index 4c019d67..c911de26 100644 --- a/bug/op_label_change.go +++ b/bug/op_label_change.go @@ -5,11 +5,11 @@ import ( "fmt" "sort" + "github.com/pkg/errors" + + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/timestamp" - - "github.com/MichaelMure/git-bug/util/git" - "github.com/pkg/errors" ) var _ Operation = &LabelChangeOperation{} @@ -17,16 +17,16 @@ var _ Operation = &LabelChangeOperation{} // LabelChangeOperation define a Bug operation to add or remove labels type LabelChangeOperation struct { OpBase - Added []Label - Removed []Label + Added []Label `json:"added"` + Removed []Label `json:"removed"` } func (op *LabelChangeOperation) base() *OpBase { return &op.OpBase } -func (op *LabelChangeOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *LabelChangeOperation) Id() entity.Id { + return idOperation(op) } // Apply apply the operation @@ -61,15 +61,8 @@ AddLoop: return string(snapshot.Labels[i]) < string(snapshot.Labels[j]) }) - hash, err := op.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - item := &LabelChangeTimelineItem{ - hash: hash, + id: op.Id(), Author: op.Author, UnixTime: timestamp.Timestamp(op.UnixTime), Added: op.Added, @@ -103,28 +96,9 @@ func (op *LabelChangeOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *LabelChangeOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["added"] = op.Added - data["removed"] = op.Removed - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *LabelChangeOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately @@ -163,15 +137,15 @@ func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, r } type LabelChangeTimelineItem struct { - hash git.Hash + id entity.Id Author identity.Interface UnixTime timestamp.Timestamp Added []Label Removed []Label } -func (l LabelChangeTimelineItem) Hash() git.Hash { - return l.hash +func (l LabelChangeTimelineItem) Id() entity.Id { + return l.id } // Sign post method for gqlgen diff --git a/bug/op_label_change_test.go b/bug/op_label_change_test.go index f5550b72..2a93e362 100644 --- a/bug/op_label_change_test.go +++ b/bug/op_label_change_test.go @@ -21,5 +21,9 @@ func TestLabelChangeSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_noop.go b/bug/op_noop.go index 3cd9f39a..16d32297 100644 --- a/bug/op_noop.go +++ b/bug/op_noop.go @@ -3,8 +3,8 @@ package bug import ( "encoding/json" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/util/git" ) var _ Operation = &NoOpOperation{} @@ -20,8 +20,8 @@ func (op *NoOpOperation) base() *OpBase { return &op.OpBase } -func (op *NoOpOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *NoOpOperation) Id() entity.Id { + return idOperation(op) } func (op *NoOpOperation) Apply(snapshot *Snapshot) { @@ -32,25 +32,9 @@ func (op *NoOpOperation) Validate() error { return opBaseValidate(op, NoOpOp) } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *NoOpOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *NoOpOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately diff --git a/bug/op_noop_test.go b/bug/op_noop_test.go index 385bc914..ea815948 100644 --- a/bug/op_noop_test.go +++ b/bug/op_noop_test.go @@ -21,5 +21,9 @@ func TestNoopSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_set_metadata.go b/bug/op_set_metadata.go index 7616a591..f99f836b 100644 --- a/bug/op_set_metadata.go +++ b/bug/op_set_metadata.go @@ -2,38 +2,32 @@ package bug import ( "encoding/json" - "fmt" + "github.com/pkg/errors" + + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/util/git" ) var _ Operation = &SetMetadataOperation{} type SetMetadataOperation struct { OpBase - Target git.Hash - NewMetadata map[string]string + Target entity.Id `json:"target"` + NewMetadata map[string]string `json:"new_metadata"` } func (op *SetMetadataOperation) base() *OpBase { return &op.OpBase } -func (op *SetMetadataOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *SetMetadataOperation) Id() entity.Id { + return idOperation(op) } func (op *SetMetadataOperation) Apply(snapshot *Snapshot) { for _, target := range snapshot.Operations { - hash, err := target.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - - if hash == op.Target { + if target.Id() == op.Target { base := target.base() if base.extraMetadata == nil { @@ -56,35 +50,16 @@ func (op *SetMetadataOperation) Validate() error { return err } - if !op.Target.IsValid() { - return fmt.Errorf("target hash is invalid") + if err := op.Target.Validate(); err != nil { + return errors.Wrap(err, "target invalid") } return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *SetMetadataOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["target"] = op.Target - data["new_metadata"] = op.NewMetadata - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *SetMetadataOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately @@ -95,7 +70,7 @@ func (op *SetMetadataOperation) UnmarshalJSON(data []byte) error { } aux := struct { - Target git.Hash `json:"target"` + Target entity.Id `json:"target"` NewMetadata map[string]string `json:"new_metadata"` }{} @@ -114,7 +89,7 @@ func (op *SetMetadataOperation) UnmarshalJSON(data []byte) error { // Sign post method for gqlgen func (op *SetMetadataOperation) IsAuthored() {} -func NewSetMetadataOp(author identity.Interface, unixTime int64, target git.Hash, newMetadata map[string]string) *SetMetadataOperation { +func NewSetMetadataOp(author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation { return &SetMetadataOperation{ OpBase: newOpBase(SetMetadataOp, author, unixTime), Target: target, @@ -123,7 +98,7 @@ func NewSetMetadataOp(author identity.Interface, unixTime int64, target git.Hash } // Convenience function to apply the operation -func SetMetadata(b Interface, author identity.Interface, unixTime int64, target git.Hash, newMetadata map[string]string) (*SetMetadataOperation, error) { +func SetMetadata(b Interface, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*SetMetadataOperation, error) { SetMetadataOp := NewSetMetadataOp(author, unixTime, target, newMetadata) if err := SetMetadataOp.Validate(); err != nil { return nil, err diff --git a/bug/op_set_metadata_test.go b/bug/op_set_metadata_test.go index a7f9f313..389e91ac 100644 --- a/bug/op_set_metadata_test.go +++ b/bug/op_set_metadata_test.go @@ -21,18 +21,18 @@ func TestSetMetadata(t *testing.T) { create.Apply(&snapshot) snapshot.Operations = append(snapshot.Operations, create) - hash1, err := create.Hash() - require.NoError(t, err) + id1 := create.Id() + require.NoError(t, id1.Validate()) comment := NewAddCommentOp(rene, unix, "comment", nil) comment.SetMetadata("key2", "value2") comment.Apply(&snapshot) snapshot.Operations = append(snapshot.Operations, comment) - hash2, err := comment.Hash() - require.NoError(t, err) + id2 := comment.Id() + require.NoError(t, id2.Validate()) - op1 := NewSetMetadataOp(rene, unix, hash1, map[string]string{ + op1 := NewSetMetadataOp(rene, unix, id1, map[string]string{ "key": "override", "key2": "value", }) @@ -51,7 +51,7 @@ func TestSetMetadata(t *testing.T) { assert.Equal(t, len(commentMetadata), 1) assert.Equal(t, commentMetadata["key2"], "value2") - op2 := NewSetMetadataOp(rene, unix, hash2, map[string]string{ + op2 := NewSetMetadataOp(rene, unix, id2, map[string]string{ "key2": "value", "key3": "value3", }) @@ -71,7 +71,7 @@ func TestSetMetadata(t *testing.T) { // new key is set assert.Equal(t, commentMetadata["key3"], "value3") - op3 := NewSetMetadataOp(rene, unix, hash1, map[string]string{ + op3 := NewSetMetadataOp(rene, unix, id1, map[string]string{ "key": "override", "key2": "override", }) @@ -107,5 +107,9 @@ func TestSetMetadataSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_set_status.go b/bug/op_set_status.go index 52ba8135..8a245184 100644 --- a/bug/op_set_status.go +++ b/bug/op_set_status.go @@ -3,10 +3,11 @@ package bug import ( "encoding/json" + "github.com/pkg/errors" + + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/timestamp" - "github.com/pkg/errors" ) var _ Operation = &SetStatusOperation{} @@ -14,30 +15,23 @@ var _ Operation = &SetStatusOperation{} // SetStatusOperation will change the status of a bug type SetStatusOperation struct { OpBase - Status Status + Status Status `json:"status"` } func (op *SetStatusOperation) base() *OpBase { return &op.OpBase } -func (op *SetStatusOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *SetStatusOperation) Id() entity.Id { + return idOperation(op) } func (op *SetStatusOperation) Apply(snapshot *Snapshot) { snapshot.Status = op.Status snapshot.addActor(op.Author) - hash, err := op.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - item := &SetStatusTimelineItem{ - hash: hash, + id: op.Id(), Author: op.Author, UnixTime: timestamp.Timestamp(op.UnixTime), Status: op.Status, @@ -58,27 +52,9 @@ func (op *SetStatusOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *SetStatusOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["status"] = op.Status - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *SetStatusOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately @@ -114,14 +90,14 @@ func NewSetStatusOp(author identity.Interface, unixTime int64, status Status) *S } type SetStatusTimelineItem struct { - hash git.Hash + id entity.Id Author identity.Interface UnixTime timestamp.Timestamp Status Status } -func (s SetStatusTimelineItem) Hash() git.Hash { - return s.hash +func (s SetStatusTimelineItem) Id() entity.Id { + return s.id } // Sign post method for gqlgen diff --git a/bug/op_set_status_test.go b/bug/op_set_status_test.go index 2506b947..ea032184 100644 --- a/bug/op_set_status_test.go +++ b/bug/op_set_status_test.go @@ -21,5 +21,9 @@ func TestSetStatusSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_set_title.go b/bug/op_set_title.go index 31113943..fadd29a9 100644 --- a/bug/op_set_title.go +++ b/bug/op_set_title.go @@ -5,10 +5,10 @@ import ( "fmt" "strings" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/timestamp" - "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/text" ) @@ -17,31 +17,24 @@ var _ Operation = &SetTitleOperation{} // SetTitleOperation will change the title of a bug type SetTitleOperation struct { OpBase - Title string - Was string + Title string `json:"title"` + Was string `json:"was"` } func (op *SetTitleOperation) base() *OpBase { return &op.OpBase } -func (op *SetTitleOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *SetTitleOperation) Id() entity.Id { + return idOperation(op) } func (op *SetTitleOperation) Apply(snapshot *Snapshot) { snapshot.Title = op.Title snapshot.addActor(op.Author) - hash, err := op.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - item := &SetTitleTimelineItem{ - hash: hash, + id: op.Id(), Author: op.Author, UnixTime: timestamp.Timestamp(op.UnixTime), Title: op.Title, @@ -79,28 +72,9 @@ func (op *SetTitleOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *SetTitleOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["title"] = op.Title - data["was"] = op.Was - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *SetTitleOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately @@ -139,15 +113,15 @@ func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was } type SetTitleTimelineItem struct { - hash git.Hash + id entity.Id Author identity.Interface UnixTime timestamp.Timestamp Title string Was string } -func (s SetTitleTimelineItem) Hash() git.Hash { - return s.hash +func (s SetTitleTimelineItem) Id() entity.Id { + return s.id } // Sign post method for gqlgen diff --git a/bug/op_set_title_test.go b/bug/op_set_title_test.go index 1f730596..19cbb12b 100644 --- a/bug/op_set_title_test.go +++ b/bug/op_set_title_test.go @@ -21,5 +21,9 @@ func TestSetTitleSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/operation.go b/bug/operation.go index daef7b8c..dd95e096 100644 --- a/bug/operation.go +++ b/bug/operation.go @@ -6,10 +6,11 @@ import ( "fmt" "time" - "github.com/MichaelMure/git-bug/identity" + "github.com/pkg/errors" + "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/git" - "github.com/pkg/errors" ) // OperationType is an operation type identifier @@ -31,8 +32,8 @@ const ( type Operation interface { // base return the OpBase of the Operation, for package internal use base() *OpBase - // Hash return the hash of the operation, to be used for back references - Hash() (git.Hash, error) + // Id return the identifier of the operation, to be used for back references + Id() entity.Id // Time return the time when the operation was added Time() time.Time // GetUnixTime return the unix timestamp when the operation was added @@ -53,42 +54,42 @@ type Operation interface { GetAuthor() identity.Interface } -func hashRaw(data []byte) git.Hash { - hasher := sha256.New() - // Write can't fail - _, _ = hasher.Write(data) - return git.Hash(fmt.Sprintf("%x", hasher.Sum(nil))) +func deriveId(data []byte) entity.Id { + sum := sha256.Sum256(data) + return entity.Id(fmt.Sprintf("%x", sum)) } -// hash compute the hash of the serialized operation -func hashOperation(op Operation) (git.Hash, error) { - // TODO: this might not be the best idea: if a single bit change in the output of json.Marshal, this will break. - // Idea: hash the segment of serialized data (= immutable) instead of the go object in memory - +func idOperation(op Operation) entity.Id { base := op.base() - if base.hash != "" { - return base.hash, nil + if base.id == "" { + // something went really wrong + panic("op's id not set") } + if base.id == entity.UnsetId { + // This means we are trying to get the op's Id *before* it has been stored, for instance when + // adding multiple ops in one go in an OperationPack. + // As the Id is computed based on the actual bytes written on the disk, we are going to predict + // those and then get the Id. This is safe as it will be the exact same code writing on disk later. + + data, err := json.Marshal(op) + if err != nil { + panic(err) + } - data, err := json.Marshal(op) - if err != nil { - return "", err + base.id = deriveId(data) } - - base.hash = hashRaw(data) - - return base.hash, nil + return base.id } // OpBase implement the common code for all operations type OpBase struct { - OperationType OperationType - Author identity.Interface - UnixTime int64 - Metadata map[string]string - // Not serialized. Store the op's hash in memory. - hash git.Hash + OperationType OperationType `json:"type"` + Author identity.Interface `json:"author"` + UnixTime int64 `json:"timestamp"` + Metadata map[string]string `json:"metadata,omitempty"` + // Not serialized. Store the op's id in memory. + id entity.Id // Not serialized. Store the extra metadata in memory, // compiled from SetMetadataOperation. extraMetadata map[string]string @@ -100,24 +101,14 @@ func newOpBase(opType OperationType, author identity.Interface, unixTime int64) OperationType: opType, Author: author, UnixTime: unixTime, + id: entity.UnsetId, } } -func (op OpBase) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - OperationType OperationType `json:"type"` - Author identity.Interface `json:"author"` - UnixTime int64 `json:"timestamp"` - Metadata map[string]string `json:"metadata,omitempty"` - }{ - OperationType: op.OperationType, - Author: op.Author, - UnixTime: op.UnixTime, - Metadata: op.Metadata, - }) -} - func (op *OpBase) UnmarshalJSON(data []byte) error { + // Compute the Id when loading the op from disk. + op.id = deriveId(data) + aux := struct { OperationType OperationType `json:"type"` Author json.RawMessage `json:"author"` @@ -192,7 +183,7 @@ func (op *OpBase) SetMetadata(key string, value string) { } op.Metadata[key] = value - op.hash = "" + op.id = entity.UnsetId } // GetMetadata retrieve arbitrary metadata about the operation diff --git a/bug/operation_pack.go b/bug/operation_pack.go index 5f3e9da8..86e4178e 100644 --- a/bug/operation_pack.go +++ b/bug/operation_pack.go @@ -63,9 +63,6 @@ func (opp *OperationPack) UnmarshalJSON(data []byte) error { return err } - // Compute the hash of the operation - op.base().hash = hashRaw(raw) - opp.Operations = append(opp.Operations, op) } diff --git a/bug/operation_pack_test.go b/bug/operation_pack_test.go index 09d159af..21ac0a00 100644 --- a/bug/operation_pack_test.go +++ b/bug/operation_pack_test.go @@ -48,14 +48,16 @@ func TestOperationPackSerialize(t *testing.T) { err = json.Unmarshal(data, &opp2) assert.NoError(t, err) - ensureHash(t, opp) + ensureIDs(t, opp) assert.Equal(t, opp, opp2) } -func ensureHash(t *testing.T, opp *OperationPack) { +func ensureIDs(t *testing.T, opp *OperationPack) { for _, op := range opp.Operations { - _, err := op.Hash() - require.NoError(t, err) + id := op.Id() + require.NoError(t, id.Validate()) + id = op.GetAuthor().Id() + require.NoError(t, id.Validate()) } } diff --git a/bug/operation_test.go b/bug/operation_test.go index 0ddb61c2..69c66dc8 100644 --- a/bug/operation_test.go +++ b/bug/operation_test.go @@ -79,7 +79,7 @@ func TestMetadata(t *testing.T) { require.Equal(t, val, "value") } -func TestHash(t *testing.T) { +func TestID(t *testing.T) { repo := repository.CreateTestRepo(false) defer repository.CleanupTestRepos(t, repo) @@ -94,27 +94,27 @@ func TestHash(t *testing.T) { b, op, err := Create(rene, time.Now().Unix(), "title", "message") require.Nil(t, err) - h1, err := op.Hash() - require.Nil(t, err) + id1 := op.Id() + require.NoError(t, id1.Validate()) err = b.Commit(repo) require.Nil(t, err) op2 := b.FirstOp() - h2, err := op2.Hash() - require.Nil(t, err) + id2 := op2.Id() + require.NoError(t, id2.Validate()) - require.Equal(t, h1, h2) + require.Equal(t, id1, id2) - b2, err := ReadLocalBug(repo, b.id) + b2, err := ReadLocalBug(repo, b.Id()) require.Nil(t, err) op3 := b2.FirstOp() - h3, err := op3.Hash() - require.Nil(t, err) + id3 := op3.Id() + require.NoError(t, id3.Validate()) - require.Equal(t, h1, h3) + require.Equal(t, id1, id3) } } diff --git a/bug/snapshot.go b/bug/snapshot.go index f1da8099..39366c6d 100644 --- a/bug/snapshot.go +++ b/bug/snapshot.go @@ -4,13 +4,13 @@ import ( "fmt" "time" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/util/git" ) // Snapshot is a compiled form of the Bug data structure used for storage and merge type Snapshot struct { - id string + id entity.Id Status Status Title string @@ -27,15 +27,10 @@ type Snapshot struct { } // Return the Bug identifier -func (snap *Snapshot) Id() string { +func (snap *Snapshot) Id() entity.Id { return snap.id } -// Return the Bug identifier truncated for human consumption -func (snap *Snapshot) HumanId() string { - return FormatHumanID(snap.id) -} - // Return the last time a bug was modified func (snap *Snapshot) LastEditTime() time.Time { if len(snap.Operations) == 0 { @@ -60,9 +55,9 @@ func (snap *Snapshot) GetCreateMetadata(key string) (string, bool) { } // SearchTimelineItem will search in the timeline for an item matching the given hash -func (snap *Snapshot) SearchTimelineItem(hash git.Hash) (TimelineItem, error) { +func (snap *Snapshot) SearchTimelineItem(id entity.Id) (TimelineItem, error) { for i := range snap.Timeline { - if snap.Timeline[i].Hash() == hash { + if snap.Timeline[i].Id() == id { return snap.Timeline[i], nil } } @@ -71,9 +66,9 @@ func (snap *Snapshot) SearchTimelineItem(hash git.Hash) (TimelineItem, error) { } // SearchComment will search for a comment matching the given hash -func (snap *Snapshot) SearchComment(hash git.Hash) (*Comment, error) { +func (snap *Snapshot) SearchComment(id entity.Id) (*Comment, error) { for _, c := range snap.Comments { - if c.id == hash.String() { + if c.id == id { return &c, nil } } @@ -104,7 +99,7 @@ func (snap *Snapshot) addParticipant(participant identity.Interface) { } // HasParticipant return true if the id is a participant -func (snap *Snapshot) HasParticipant(id string) bool { +func (snap *Snapshot) HasParticipant(id entity.Id) bool { for _, p := range snap.Participants { if p.Id() == id { return true @@ -114,7 +109,7 @@ func (snap *Snapshot) HasParticipant(id string) bool { } // HasAnyParticipant return true if one of the ids is a participant -func (snap *Snapshot) HasAnyParticipant(ids ...string) bool { +func (snap *Snapshot) HasAnyParticipant(ids ...entity.Id) bool { for _, id := range ids { if snap.HasParticipant(id) { return true @@ -124,7 +119,7 @@ func (snap *Snapshot) HasAnyParticipant(ids ...string) bool { } // HasActor return true if the id is a actor -func (snap *Snapshot) HasActor(id string) bool { +func (snap *Snapshot) HasActor(id entity.Id) bool { for _, p := range snap.Actors { if p.Id() == id { return true @@ -134,7 +129,7 @@ func (snap *Snapshot) HasActor(id string) bool { } // HasAnyActor return true if one of the ids is a actor -func (snap *Snapshot) HasAnyActor(ids ...string) bool { +func (snap *Snapshot) HasAnyActor(ids ...entity.Id) bool { for _, id := range ids { if snap.HasActor(id) { return true diff --git a/bug/timeline.go b/bug/timeline.go index d8ee2c6b..4af1b92a 100644 --- a/bug/timeline.go +++ b/bug/timeline.go @@ -3,14 +3,15 @@ package bug import ( "strings" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/timestamp" ) type TimelineItem interface { - // Hash return the hash of the item - Hash() git.Hash + // ID return the identifier of the item + Id() entity.Id } // CommentHistoryStep hold one version of a message in the history @@ -25,7 +26,7 @@ type CommentHistoryStep struct { // CommentTimelineItem is a TimelineItem that holds a Comment and its edition history type CommentTimelineItem struct { - hash git.Hash + id entity.Id Author identity.Interface Message string Files []git.Hash @@ -34,9 +35,9 @@ type CommentTimelineItem struct { History []CommentHistoryStep } -func NewCommentTimelineItem(hash git.Hash, comment Comment) CommentTimelineItem { +func NewCommentTimelineItem(ID entity.Id, comment Comment) CommentTimelineItem { return CommentTimelineItem{ - hash: hash, + id: ID, Author: comment.Author, Message: comment.Message, Files: comment.Files, @@ -51,8 +52,8 @@ func NewCommentTimelineItem(hash git.Hash, comment Comment) CommentTimelineItem } } -func (c *CommentTimelineItem) Hash() git.Hash { - return c.hash +func (c *CommentTimelineItem) Id() entity.Id { + return c.id } // Append will append a new comment in the history and update the other values |