Add comment edit command

vince created

This commit adds the comment edit command, which provides a CLI tool that allows a user to edit a comment.

Change summary

bug/op_add_comment.go    |   5 +
bug/op_create.go         |   5 +
commands/comment.go      |   1 
commands/comment_edit.go | 102 ++++++++++++++++++++++++++++++++++++++++++
commands/show.go         |   3 
5 files changed, 111 insertions(+), 5 deletions(-)

Detailed changes

bug/op_add_comment.go 🔗

@@ -36,8 +36,9 @@ func (op *AddCommentOperation) Apply(snapshot *Snapshot) {
 	snapshot.addActor(op.Author)
 	snapshot.addParticipant(op.Author)
 
+	commentId := entity.Id(CompileCommentId(snapshot.Id().String(), op.Id().String()))
 	comment := Comment{
-		id:       entity.Id(CompileCommentId(snapshot.Id().String(), op.Id().String())),
+		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)

bug/op_create.go 🔗

@@ -39,8 +39,9 @@ func (op *CreateOperation) Apply(snapshot *Snapshot) {
 
 	snapshot.Title = op.Title
 
+	commentId := entity.Id(CompileCommentId(snapshot.Id().String(), op.Id().String()))
 	comment := Comment{
-		id:       entity.Id(CompileCommentId(snapshot.Id().String(), op.Id().String())),
+		id:       commentId,
 		Message:  op.Message,
 		Author:   op.Author,
 		UnixTime: timestamp.Timestamp(op.UnixTime),
@@ -52,7 +53,7 @@ func (op *CreateOperation) Apply(snapshot *Snapshot) {
 
 	snapshot.Timeline = []TimelineItem{
 		&CreateTimelineItem{
-			CommentTimelineItem: NewCommentTimelineItem(op.Id(), comment),
+			CommentTimelineItem: NewCommentTimelineItem(commentId, comment),
 		},
 	}
 }

commands/comment.go 🔗

@@ -22,6 +22,7 @@ func newCommentCommand() *cobra.Command {
 	}
 
 	cmd.AddCommand(newCommentAddCommand())
+	cmd.AddCommand(newCommentEditCommand())
 
 	return cmd
 }

commands/comment_edit.go 🔗

@@ -0,0 +1,102 @@
+package commands
+
+import (
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+
+	"github.com/MichaelMure/git-bug/bug"
+	"github.com/MichaelMure/git-bug/cache"
+	_select "github.com/MichaelMure/git-bug/commands/select"
+	"github.com/MichaelMure/git-bug/entity"
+	"github.com/MichaelMure/git-bug/input"
+)
+
+type commentEditOptions struct {
+	messageFile string
+	message     string
+}
+
+func newCommentEditCommand() *cobra.Command {
+	env := newEnv()
+	options := commentEditOptions{}
+
+	cmd := &cobra.Command{
+		Use:      "edit [<id>]",
+		Short:    "Edit an existing comment on a bug.",
+		PreRunE:  loadBackendEnsureUser(env),
+		PostRunE: closeBackend(env),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runCommentEdit(env, options, args)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.SortFlags = false
+
+	flags.StringVarP(&options.messageFile, "file", "F", "",
+		"Take the message from the given file. Use - to read the message from the standard input")
+
+	flags.StringVarP(&options.message, "message", "m", "",
+		"Provide the new message from the command line")
+
+	return cmd
+}
+
+func ResolveComment(repo *cache.RepoCache, args []string) (*cache.BugCache, entity.Id, error) {
+	fullId := args[0]
+	bugId, _ := bug.UnpackCommentId(args[0])
+	args[0] = bugId
+	b, args, err := _select.ResolveBug(repo, args)
+	if err != nil {
+		return nil, entity.UnsetId, err
+	}
+
+	matching := make([]entity.Id, 0, 5)
+
+	for _, comment := range b.Snapshot().Comments {
+		if comment.Id().HasPrefix(fullId) {
+			matching = append(matching, comment.Id())
+		}
+	}
+
+	if len(matching) > 1 {
+		return nil, entity.UnsetId, entity.NewErrMultipleMatch("comment", matching)
+	} else if len(matching) == 0 {
+		return nil, entity.UnsetId, errors.New("comment doesn't exist")
+	}
+
+	return b, matching[0], nil
+}
+
+func runCommentEdit(env *Env, opts commentEditOptions, args []string) error {
+	b, c, err := ResolveComment(env.backend, args)
+
+	if err != nil {
+		return err
+	}
+
+	if opts.messageFile != "" && opts.message == "" {
+		opts.message, err = input.BugCommentFileInput(opts.messageFile)
+		if err != nil {
+			return err
+		}
+	}
+
+	if opts.messageFile == "" && opts.message == "" {
+		opts.message, err = input.BugCommentEditorInput(env.backend, "")
+		if err == input.ErrEmptyMessage {
+			env.err.Println("Empty message, aborting.")
+			return nil
+		}
+		if err != nil {
+			return err
+		}
+	}
+
+	_, err = b.EditComment(c, opts.message)
+	if err != nil {
+		return err
+	}
+
+	return b.Commit()
+}

commands/show.go 🔗

@@ -158,8 +158,9 @@ func showDefaultFormatter(env *Env, snapshot *bug.Snapshot) error {
 
 	for i, comment := range snapshot.Comments {
 		var message string
-		env.out.Printf("%s#%d %s <%s>\n\n",
+		env.out.Printf("%s%s #%d %s <%s>\n\n",
 			indent,
+			comment.Id().Human(),
 			i,
 			comment.Author.DisplayName(),
 			comment.Author.Email(),