From d96284da646cc1d3e3d7d3b2f7a1ab0e8e7a4d88 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 9 Jul 2020 14:59:47 +0800 Subject: Change the comment ID to use both bug and comment ID references. Add comment edit command This commit adds the comment edit command, which provides a CLI tool that allows a user to edit a comment. --- bug/comment.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ bug/comment_test.go | 27 +++++++++++++++++++++++++++ bug/op_add_comment.go | 5 +++-- bug/op_create.go | 5 +++-- bug/snapshot.go | 5 +++++ 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 bug/comment_test.go (limited to 'bug') diff --git a/bug/comment.go b/bug/comment.go index 4c9d118e..1a9ca05a 100644 --- a/bug/comment.go +++ b/bug/comment.go @@ -1,6 +1,8 @@ package bug import ( + "strings" + "github.com/dustin/go-humanize" "github.com/MichaelMure/git-bug/entity" @@ -31,6 +33,50 @@ func (c Comment) Id() entity.Id { return c.id } +const compiledCommentIdFormat = "BCBCBCBBBCBBBBCBBBBCBBBBCBBBBCBBBBCBBBBC" + +// DeriveCommentId compute a merged Id for a comment holding information from +// both the Bug's Id and the Comment's Id. This allow to later find efficiently +// a Comment because we can access the bug directly instead of searching for a +// Bug that has a Comment matching the Id. +// +// To allow the use of an arbitrary length prefix of this merged Id, Ids from Bug +// and Comment are interleaved with this irregular pattern to give the best chance +// to find the Comment even with a 7 character prefix. +// +// A complete merged Id hold 30 characters for the Bug and 10 for the Comment, +// which give a key space of 36^30 for the Bug (~5 * 10^46) and 36^10 for the +// Comment (~3 * 10^15). This asymmetry assume a reasonable number of Comment +// within a Bug, while still allowing for a vast key space for Bug (that is, a +// globally merged bug database) with a low risk of collision. +func DeriveCommentId(bugId entity.Id, commentId entity.Id) entity.Id { + var id strings.Builder + for _, char := range compiledCommentIdFormat { + if char == 'B' { + id.WriteByte(bugId[0]) + bugId = bugId[1:] + } else { + id.WriteByte(commentId[0]) + commentId = commentId[1:] + } + } + return entity.Id(id.String()) +} + +func SplitCommentId(prefix string) (bugPrefix string, commentPrefix string) { + var bugIdPrefix strings.Builder + var commentIdPrefix strings.Builder + + for i, char := range prefix { + if compiledCommentIdFormat[i] == 'B' { + bugIdPrefix.WriteRune(char) + } else { + commentIdPrefix.WriteRune(char) + } + } + return bugIdPrefix.String(), commentIdPrefix.String() +} + // 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/comment_test.go b/bug/comment_test.go new file mode 100644 index 00000000..423d10d8 --- /dev/null +++ b/bug/comment_test.go @@ -0,0 +1,27 @@ +package bug + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/MichaelMure/git-bug/entity" +) + +func TestCommentId(t *testing.T) { + bugId := entity.Id("abcdefghijklmnopqrstuvwxyz1234__________") + opId := entity.Id("ABCDEFGHIJ______________________________") + expectedId := entity.Id("aAbBcCdefDghijEklmnFopqrGstuvHwxyzI1234J") + + mergedId := DeriveCommentId(bugId, opId) + require.Equal(t, expectedId, mergedId) + + // full length + splitBugId, splitCommentId := SplitCommentId(mergedId.String()) + require.Equal(t, string(bugId[:30]), splitBugId) + require.Equal(t, string(opId[:10]), splitCommentId) + + splitBugId, splitCommentId = SplitCommentId(string(expectedId[:6])) + require.Equal(t, string(bugId[:3]), splitBugId) + require.Equal(t, string(opId[:3]), splitCommentId) +} diff --git a/bug/op_add_comment.go b/bug/op_add_comment.go index 3f19e42e..df426ee0 100644 --- a/bug/op_add_comment.go +++ b/bug/op_add_comment.go @@ -36,8 +36,9 @@ func (op *AddCommentOperation) Apply(snapshot *Snapshot) { snapshot.addActor(op.Author) snapshot.addParticipant(op.Author) + commentId := DeriveCommentId(snapshot.Id(), op.Id()) comment := Comment{ - id: op.Id(), + id: commentId, Message: op.Message, Author: op.Author, Files: op.Files, @@ -47,7 +48,7 @@ func (op *AddCommentOperation) Apply(snapshot *Snapshot) { snapshot.Comments = append(snapshot.Comments, comment) item := &AddCommentTimelineItem{ - CommentTimelineItem: NewCommentTimelineItem(op.Id(), comment), + CommentTimelineItem: NewCommentTimelineItem(commentId, comment), } snapshot.Timeline = append(snapshot.Timeline, item) diff --git a/bug/op_create.go b/bug/op_create.go index 41e0fca1..15fb69b5 100644 --- a/bug/op_create.go +++ b/bug/op_create.go @@ -59,8 +59,9 @@ func (op *CreateOperation) Apply(snapshot *Snapshot) { snapshot.Title = op.Title + commentId := DeriveCommentId(snapshot.Id(), op.Id()) comment := Comment{ - id: op.Id(), + id: commentId, Message: op.Message, Author: op.Author, UnixTime: timestamp.Timestamp(op.UnixTime), @@ -72,7 +73,7 @@ func (op *CreateOperation) Apply(snapshot *Snapshot) { snapshot.Timeline = []TimelineItem{ &CreateTimelineItem{ - CommentTimelineItem: NewCommentTimelineItem(op.Id(), comment), + CommentTimelineItem: NewCommentTimelineItem(commentId, comment), }, } } diff --git a/bug/snapshot.go b/bug/snapshot.go index 11df04b2..0005b930 100644 --- a/bug/snapshot.go +++ b/bug/snapshot.go @@ -28,6 +28,11 @@ type Snapshot struct { // Return the Bug identifier func (snap *Snapshot) Id() entity.Id { + if snap.id == "" { + // simply panic as it would be a coding error + // (using an id of a bug not stored yet) + panic("no id yet") + } return snap.id } -- cgit