From 8bfe64d59328ae8dc6549822aeab3142b3501c57 Mon Sep 17 00:00:00 2001 From: Cristian Dominguez Date: Wed, 10 Mar 2021 14:32:20 -0300 Subject: [PATCH] Accept `--body-file` flag if `--body` is supported --- pkg/cmd/issue/comment/comment.go | 9 +++++ pkg/cmd/issue/comment/comment_test.go | 42 +++++++++++++++++++++- pkg/cmd/issue/edit/edit.go | 29 ++++++++++++++-- pkg/cmd/issue/edit/edit_test.go | 44 ++++++++++++++++++++++- pkg/cmd/pr/comment/comment.go | 9 +++++ pkg/cmd/pr/comment/comment_test.go | 42 +++++++++++++++++++++- pkg/cmd/pr/edit/edit.go | 25 ++++++++++++++ pkg/cmd/pr/edit/edit_test.go | 44 ++++++++++++++++++++++- pkg/cmd/pr/merge/merge.go | 26 +++++++++++++- pkg/cmd/pr/merge/merge_test.go | 50 ++++++++++++++++++++++++++- pkg/cmd/pr/review/review.go | 21 +++++++++++ pkg/cmd/pr/review/review_test.go | 40 ++++++++++++++++++++- pkg/cmd/pr/shared/commentable.go | 11 ++++-- 13 files changed, 379 insertions(+), 13 deletions(-) diff --git a/pkg/cmd/issue/comment/comment.go b/pkg/cmd/issue/comment/comment.go index 1e4264574..b1be88bb4 100644 --- a/pkg/cmd/issue/comment/comment.go +++ b/pkg/cmd/issue/comment/comment.go @@ -35,6 +35,14 @@ func NewCmdComment(f *cmdutil.Factory, runF func(*prShared.CommentableOptions) e return prShared.CommentablePreRun(cmd, opts) }, RunE: func(_ *cobra.Command, args []string) error { + if opts.BodyFile != "" { + b, err := cmdutil.ReadFile(opts.BodyFile, opts.IO.In) + if err != nil { + return err + } + opts.Body = string(b) + } + if runF != nil { return runF(opts) } @@ -43,6 +51,7 @@ func NewCmdComment(f *cmdutil.Factory, runF func(*prShared.CommentableOptions) e } cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "Supply a body. Will prompt for one otherwise.") + cmd.Flags().StringVarP(&opts.BodyFile, "body-file", "F", "", "Read body text from `file`") cmd.Flags().BoolP("editor", "e", false, "Add body using editor") cmd.Flags().BoolP("web", "w", false, "Add body in browser") diff --git a/pkg/cmd/issue/comment/comment_test.go b/pkg/cmd/issue/comment/comment_test.go index bfbdd6690..5274e7ad0 100644 --- a/pkg/cmd/issue/comment/comment_test.go +++ b/pkg/cmd/issue/comment/comment_test.go @@ -2,7 +2,10 @@ package comment import ( "bytes" + "fmt" + "io/ioutil" "net/http" + "path/filepath" "testing" "github.com/cli/cli/internal/ghrepo" @@ -12,12 +15,18 @@ import ( "github.com/cli/cli/pkg/iostreams" "github.com/google/shlex" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewCmdComment(t *testing.T) { + tmpFile := filepath.Join(t.TempDir(), "my-body.md") + err := ioutil.WriteFile(tmpFile, []byte("a body from file"), 0600) + require.NoError(t, err) + tests := []struct { name string input string + stdin string output shared.CommentableOptions wantsErr bool }{ @@ -57,6 +66,27 @@ func TestNewCmdComment(t *testing.T) { }, wantsErr: false, }, + { + name: "body from stdin", + input: "1 --body-file -", + stdin: "this is on standard input", + output: shared.CommentableOptions{ + Interactive: false, + InputType: shared.InputTypeInline, + Body: "this is on standard input", + }, + wantsErr: false, + }, + { + name: "body from file", + input: fmt.Sprintf("1 --body-file '%s'", tmpFile), + output: shared.CommentableOptions{ + Interactive: false, + InputType: shared.InputTypeInline, + Body: "a body from file", + }, + wantsErr: false, + }, { name: "editor flag", input: "1 --editor", @@ -77,6 +107,12 @@ func TestNewCmdComment(t *testing.T) { }, wantsErr: false, }, + { + name: "body and body-file flags", + input: "1 --body 'test' --body-file 'test-file.txt'", + output: shared.CommentableOptions{}, + wantsErr: true, + }, { name: "editor and web flags", input: "1 --editor --web", @@ -105,11 +141,15 @@ func TestNewCmdComment(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - io, _, _, _ := iostreams.Test() + io, stdin, _, _ := iostreams.Test() io.SetStdoutTTY(true) io.SetStdinTTY(true) io.SetStderrTTY(true) + if tt.stdin != "" { + _, _ = stdin.WriteString(tt.stdin) + } + f := &cmdutil.Factory{ IOStreams: io, } diff --git a/pkg/cmd/issue/edit/edit.go b/pkg/cmd/issue/edit/edit.go index e83784359..b3f85a36b 100644 --- a/pkg/cmd/issue/edit/edit.go +++ b/pkg/cmd/issue/edit/edit.go @@ -42,6 +42,8 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman FetchOptions: prShared.FetchOptions, } + var bodyFile string + cmd := &cobra.Command{ Use: "edit { | }", Short: "Edit an issue", @@ -51,6 +53,7 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman $ gh issue edit 23 --add-assignee @me --remove-assignee monalisa,hubot $ gh issue edit 23 --add-project "Roadmap" --remove-project v1,v2 $ gh issue edit 23 --milestone "Version 1" + $ gh issue edit 23 --body-file body.txt `), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { @@ -60,12 +63,31 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman opts.SelectorArg = args[0] flags := cmd.Flags() + + bodyProvided := flags.Changed("body") + bodyFileProvided := bodyFile != "" + + if err := cmdutil.MutuallyExclusive( + "specify only one of `--body` or `--body-file`", + bodyProvided, + bodyFileProvided, + ); err != nil { + return err + } + if bodyProvided || bodyFileProvided { + opts.Editable.Body.Edited = true + if bodyFileProvided { + b, err := cmdutil.ReadFile(bodyFile, opts.IO.In) + if err != nil { + return err + } + opts.Editable.Body.Value = string(b) + } + } + if flags.Changed("title") { opts.Editable.Title.Edited = true } - if flags.Changed("body") { - opts.Editable.Body.Edited = true - } if flags.Changed("add-assignee") || flags.Changed("remove-assignee") { opts.Editable.Assignees.Edited = true } @@ -97,6 +119,7 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman cmd.Flags().StringVarP(&opts.Editable.Title.Value, "title", "t", "", "Set the new title.") cmd.Flags().StringVarP(&opts.Editable.Body.Value, "body", "b", "", "Set the new body.") + cmd.Flags().StringVarP(&bodyFile, "body-file", "F", "", "Read body text from `file`") cmd.Flags().StringSliceVar(&opts.Editable.Assignees.Add, "add-assignee", nil, "Add assigned users by their `login`. Use \"@me\" to assign yourself.") cmd.Flags().StringSliceVar(&opts.Editable.Assignees.Remove, "remove-assignee", nil, "Remove assigned users by their `login`. Use \"@me\" to unassign yourself.") cmd.Flags().StringSliceVar(&opts.Editable.Labels.Add, "add-label", nil, "Add labels by `name`") diff --git a/pkg/cmd/issue/edit/edit_test.go b/pkg/cmd/issue/edit/edit_test.go index d21997516..50b6389bd 100644 --- a/pkg/cmd/issue/edit/edit_test.go +++ b/pkg/cmd/issue/edit/edit_test.go @@ -2,7 +2,10 @@ package edit import ( "bytes" + "fmt" + "io/ioutil" "net/http" + "path/filepath" "testing" "github.com/cli/cli/internal/ghrepo" @@ -12,12 +15,18 @@ import ( "github.com/cli/cli/pkg/iostreams" "github.com/google/shlex" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewCmdEdit(t *testing.T) { + tmpFile := filepath.Join(t.TempDir(), "my-body.md") + err := ioutil.WriteFile(tmpFile, []byte("a body from file"), 0600) + require.NoError(t, err) + tests := []struct { name string input string + stdin string output EditOptions wantsErr bool }{ @@ -64,6 +73,35 @@ func TestNewCmdEdit(t *testing.T) { }, wantsErr: false, }, + { + name: "body from stdin", + input: "23 --body-file -", + stdin: "this is on standard input", + output: EditOptions{ + SelectorArg: "23", + Editable: prShared.Editable{ + Body: prShared.EditableString{ + Value: "this is on standard input", + Edited: true, + }, + }, + }, + wantsErr: false, + }, + { + name: "body from file", + input: fmt.Sprintf("23 --body-file '%s'", tmpFile), + output: EditOptions{ + SelectorArg: "23", + Editable: prShared.Editable{ + Body: prShared.EditableString{ + Value: "a body from file", + Edited: true, + }, + }, + }, + wantsErr: false, + }, { name: "add-assignee flag", input: "23 --add-assignee monalisa,hubot", @@ -165,11 +203,15 @@ func TestNewCmdEdit(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - io, _, _, _ := iostreams.Test() + io, stdin, _, _ := iostreams.Test() io.SetStdoutTTY(true) io.SetStdinTTY(true) io.SetStderrTTY(true) + if tt.stdin != "" { + _, _ = stdin.WriteString(tt.stdin) + } + f := &cmdutil.Factory{ IOStreams: io, } diff --git a/pkg/cmd/pr/comment/comment.go b/pkg/cmd/pr/comment/comment.go index 8e7c98cb1..c2d16c889 100644 --- a/pkg/cmd/pr/comment/comment.go +++ b/pkg/cmd/pr/comment/comment.go @@ -43,6 +43,14 @@ func NewCmdComment(f *cmdutil.Factory, runF func(*shared.CommentableOptions) err return shared.CommentablePreRun(cmd, opts) }, RunE: func(cmd *cobra.Command, args []string) error { + if opts.BodyFile != "" { + b, err := cmdutil.ReadFile(opts.BodyFile, opts.IO.In) + if err != nil { + return err + } + opts.Body = string(b) + } + if runF != nil { return runF(opts) } @@ -51,6 +59,7 @@ func NewCmdComment(f *cmdutil.Factory, runF func(*shared.CommentableOptions) err } cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "Supply a body. Will prompt for one otherwise.") + cmd.Flags().StringVarP(&opts.BodyFile, "body-file", "F", "", "Read body text from `file`") cmd.Flags().BoolP("editor", "e", false, "Add body using editor") cmd.Flags().BoolP("web", "w", false, "Add body in browser") diff --git a/pkg/cmd/pr/comment/comment_test.go b/pkg/cmd/pr/comment/comment_test.go index 94c58a720..56277874e 100644 --- a/pkg/cmd/pr/comment/comment_test.go +++ b/pkg/cmd/pr/comment/comment_test.go @@ -2,7 +2,10 @@ package comment import ( "bytes" + "fmt" + "io/ioutil" "net/http" + "path/filepath" "testing" "github.com/cli/cli/context" @@ -13,12 +16,18 @@ import ( "github.com/cli/cli/pkg/iostreams" "github.com/google/shlex" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewCmdComment(t *testing.T) { + tmpFile := filepath.Join(t.TempDir(), "my-body.md") + err := ioutil.WriteFile(tmpFile, []byte("a body from file"), 0600) + require.NoError(t, err) + tests := []struct { name string input string + stdin string output shared.CommentableOptions wantsErr bool }{ @@ -78,6 +87,27 @@ func TestNewCmdComment(t *testing.T) { }, wantsErr: false, }, + { + name: "body from stdin", + input: "1 --body-file -", + stdin: "this is on standard input", + output: shared.CommentableOptions{ + Interactive: false, + InputType: shared.InputTypeInline, + Body: "this is on standard input", + }, + wantsErr: false, + }, + { + name: "body from file", + input: fmt.Sprintf("1 --body-file '%s'", tmpFile), + output: shared.CommentableOptions{ + Interactive: false, + InputType: shared.InputTypeInline, + Body: "a body from file", + }, + wantsErr: false, + }, { name: "editor flag", input: "1 --editor", @@ -98,6 +128,12 @@ func TestNewCmdComment(t *testing.T) { }, wantsErr: false, }, + { + name: "body and body-file flags", + input: "1 --body 'test' --body-file 'test-file.txt'", + output: shared.CommentableOptions{}, + wantsErr: true, + }, { name: "editor and web flags", input: "1 --editor --web", @@ -126,11 +162,15 @@ func TestNewCmdComment(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - io, _, _, _ := iostreams.Test() + io, stdin, _, _ := iostreams.Test() io.SetStdoutTTY(true) io.SetStdinTTY(true) io.SetStderrTTY(true) + if tt.stdin != "" { + _, _ = stdin.WriteString(tt.stdin) + } + f := &cmdutil.Factory{ IOStreams: io, } diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index 7319b55b4..7a01f5125 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -45,6 +45,8 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman EditorRetriever: editorRetriever{config: f.Config}, } + var bodyFile string + cmd := &cobra.Command{ Use: "edit [ | | ]", Short: "Edit a pull request", @@ -66,6 +68,28 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman } flags := cmd.Flags() + + bodyProvided := flags.Changed("body") + bodyFileProvided := bodyFile != "" + + if err := cmdutil.MutuallyExclusive( + "specify only one of `--body` or `--body-file`", + bodyProvided, + bodyFileProvided, + ); err != nil { + return err + } + if bodyProvided || bodyFileProvided { + opts.Editable.Body.Edited = true + if bodyFileProvided { + b, err := cmdutil.ReadFile(bodyFile, opts.IO.In) + if err != nil { + return err + } + opts.Editable.Body.Value = string(b) + } + } + if flags.Changed("title") { opts.Editable.Title.Edited = true } @@ -109,6 +133,7 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman cmd.Flags().StringVarP(&opts.Editable.Title.Value, "title", "t", "", "Set the new title.") cmd.Flags().StringVarP(&opts.Editable.Body.Value, "body", "b", "", "Set the new body.") + cmd.Flags().StringVarP(&bodyFile, "body-file", "F", "", "Read body text from `file`") cmd.Flags().StringVarP(&opts.Editable.Base.Value, "base", "B", "", "Change the base `branch` for this pull request") cmd.Flags().StringSliceVar(&opts.Editable.Reviewers.Add, "add-reviewer", nil, "Add reviewers by their `login`.") cmd.Flags().StringSliceVar(&opts.Editable.Reviewers.Remove, "remove-reviewer", nil, "Remove reviewers by their `login`.") diff --git a/pkg/cmd/pr/edit/edit_test.go b/pkg/cmd/pr/edit/edit_test.go index ae1cc72c8..586036910 100644 --- a/pkg/cmd/pr/edit/edit_test.go +++ b/pkg/cmd/pr/edit/edit_test.go @@ -2,7 +2,10 @@ package edit import ( "bytes" + "fmt" + "io/ioutil" "net/http" + "path/filepath" "testing" "github.com/cli/cli/api" @@ -13,12 +16,18 @@ import ( "github.com/cli/cli/pkg/iostreams" "github.com/google/shlex" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewCmdEdit(t *testing.T) { + tmpFile := filepath.Join(t.TempDir(), "my-body.md") + err := ioutil.WriteFile(tmpFile, []byte("a body from file"), 0600) + require.NoError(t, err) + tests := []struct { name string input string + stdin string output EditOptions wantsErr bool }{ @@ -74,6 +83,35 @@ func TestNewCmdEdit(t *testing.T) { }, wantsErr: false, }, + { + name: "body from stdin", + input: "23 --body-file -", + stdin: "this is on standard input", + output: EditOptions{ + SelectorArg: "23", + Editable: shared.Editable{ + Body: shared.EditableString{ + Value: "this is on standard input", + Edited: true, + }, + }, + }, + wantsErr: false, + }, + { + name: "body from file", + input: fmt.Sprintf("23 --body-file '%s'", tmpFile), + output: EditOptions{ + SelectorArg: "23", + Editable: shared.Editable{ + Body: shared.EditableString{ + Value: "a body from file", + Edited: true, + }, + }, + }, + wantsErr: false, + }, { name: "base flag", input: "23 --base base-branch-name", @@ -217,11 +255,15 @@ func TestNewCmdEdit(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - io, _, _, _ := iostreams.Test() + io, stdin, _, _ := iostreams.Test() io.SetStdoutTTY(true) io.SetStdinTTY(true) io.SetStderrTTY(true) + if tt.stdin != "" { + _, _ = stdin.WriteString(tt.stdin) + } + f := &cmdutil.Factory{ IOStreams: io, } diff --git a/pkg/cmd/pr/merge/merge.go b/pkg/cmd/pr/merge/merge.go index 3b5a10b5b..96fb2b7f6 100644 --- a/pkg/cmd/pr/merge/merge.go +++ b/pkg/cmd/pr/merge/merge.go @@ -63,6 +63,8 @@ func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Comm flagRebase bool ) + var bodyFile string + cmd := &cobra.Command{ Use: "merge [ | | ]", Short: "Merge a pull request", @@ -106,7 +108,28 @@ func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Comm opts.IsDeleteBranchIndicated = cmd.Flags().Changed("delete-branch") opts.CanDeleteLocalBranch = !cmd.Flags().Changed("repo") - opts.BodySet = cmd.Flags().Changed("body") + + bodyProvided := cmd.Flags().Changed("body") + bodyFileProvided := bodyFile != "" + + if err := cmdutil.MutuallyExclusive( + "specify only one of `--body` or `--body-file`", + bodyProvided, + bodyFileProvided, + ); err != nil { + return err + } + if bodyProvided || bodyFileProvided { + opts.BodySet = true + if bodyFileProvided { + b, err := cmdutil.ReadFile(bodyFile, opts.IO.In) + if err != nil { + return err + } + opts.Body = string(b) + } + + } opts.Editor = &userEditor{ io: opts.IO, @@ -122,6 +145,7 @@ func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Comm cmd.Flags().BoolVarP(&opts.DeleteBranch, "delete-branch", "d", false, "Delete the local and remote branch after merge") cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "Body `text` for the merge commit") + cmd.Flags().StringVarP(&bodyFile, "body-file", "F", "", "Read body text from `file`") cmd.Flags().BoolVarP(&flagMerge, "merge", "m", false, "Merge the commits with the base branch") cmd.Flags().BoolVarP(&flagRebase, "rebase", "r", false, "Rebase the commits onto the base branch") cmd.Flags().BoolVarP(&flagSquash, "squash", "s", false, "Squash the commits into one commit and merge it into the base branch") diff --git a/pkg/cmd/pr/merge/merge_test.go b/pkg/cmd/pr/merge/merge_test.go index 78f195497..16ad0ed6d 100644 --- a/pkg/cmd/pr/merge/merge_test.go +++ b/pkg/cmd/pr/merge/merge_test.go @@ -3,8 +3,10 @@ package merge import ( "bytes" "errors" + "fmt" "io/ioutil" "net/http" + "path/filepath" "regexp" "strings" "testing" @@ -27,9 +29,14 @@ import ( ) func Test_NewCmdMerge(t *testing.T) { + tmpFile := filepath.Join(t.TempDir(), "my-body.md") + err := ioutil.WriteFile(tmpFile, []byte("a body from file"), 0600) + require.NoError(t, err) + tests := []struct { name string args string + stdin string isTTY bool want MergeOptions wantErr string @@ -64,6 +71,37 @@ func Test_NewCmdMerge(t *testing.T) { BodySet: false, }, }, + { + name: "body from file", + args: fmt.Sprintf("123 --body-file '%s'", tmpFile), + isTTY: true, + want: MergeOptions{ + SelectorArg: "123", + DeleteBranch: false, + IsDeleteBranchIndicated: false, + CanDeleteLocalBranch: true, + MergeMethod: PullRequestMergeMethodMerge, + InteractiveMode: true, + Body: "a body from file", + BodySet: true, + }, + }, + { + name: "body from stdin", + args: "123 --body-file -", + stdin: "this is on standard input", + isTTY: true, + want: MergeOptions{ + SelectorArg: "123", + DeleteBranch: false, + IsDeleteBranchIndicated: false, + CanDeleteLocalBranch: true, + MergeMethod: PullRequestMergeMethodMerge, + InteractiveMode: true, + Body: "this is on standard input", + BodySet: true, + }, + }, { name: "body", args: "123 -bcool", @@ -79,6 +117,12 @@ func Test_NewCmdMerge(t *testing.T) { BodySet: true, }, }, + { + name: "body and body-file flags", + args: "123 --body 'test' --body-file 'test-file.txt'", + isTTY: true, + wantErr: "specify only one of `--body` or `--body-file`", + }, { name: "no argument with --repo override", args: "-R owner/repo", @@ -106,11 +150,15 @@ func Test_NewCmdMerge(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - io, _, _, _ := iostreams.Test() + io, stdin, _, _ := iostreams.Test() io.SetStdoutTTY(tt.isTTY) io.SetStdinTTY(tt.isTTY) io.SetStderrTTY(tt.isTTY) + if tt.stdin != "" { + _, _ = stdin.WriteString(tt.stdin) + } + f := &cmdutil.Factory{ IOStreams: io, } diff --git a/pkg/cmd/pr/review/review.go b/pkg/cmd/pr/review/review.go index 4fb23ec97..79b23043a 100644 --- a/pkg/cmd/pr/review/review.go +++ b/pkg/cmd/pr/review/review.go @@ -49,6 +49,8 @@ func NewCmdReview(f *cmdutil.Factory, runF func(*ReviewOptions) error) *cobra.Co flagComment bool ) + var bodyFile string + cmd := &cobra.Command{ Use: "review [ | | ]", Short: "Add a review to a pull request", @@ -83,6 +85,24 @@ func NewCmdReview(f *cmdutil.Factory, runF func(*ReviewOptions) error) *cobra.Co opts.SelectorArg = args[0] } + bodyProvided := cmd.Flags().Changed("body") + bodyFileProvided := bodyFile != "" + + if err := cmdutil.MutuallyExclusive( + "specify only one of `--body` or `--body-file`", + bodyProvided, + bodyFileProvided, + ); err != nil { + return err + } + if bodyFileProvided { + b, err := cmdutil.ReadFile(bodyFile, opts.IO.In) + if err != nil { + return err + } + opts.Body = string(b) + } + found := 0 if flagApprove { found++ @@ -125,6 +145,7 @@ func NewCmdReview(f *cmdutil.Factory, runF func(*ReviewOptions) error) *cobra.Co cmd.Flags().BoolVarP(&flagRequestChanges, "request-changes", "r", false, "Request changes on a pull request") cmd.Flags().BoolVarP(&flagComment, "comment", "c", false, "Comment on a pull request") cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "Specify the body of a review") + cmd.Flags().StringVarP(&bodyFile, "body-file", "F", "", "Read body text from `file`") return cmd } diff --git a/pkg/cmd/pr/review/review_test.go b/pkg/cmd/pr/review/review_test.go index 1f2023b02..84d526368 100644 --- a/pkg/cmd/pr/review/review_test.go +++ b/pkg/cmd/pr/review/review_test.go @@ -2,8 +2,10 @@ package review import ( "bytes" + "fmt" "io/ioutil" "net/http" + "path/filepath" "regexp" "testing" @@ -22,9 +24,14 @@ import ( ) func Test_NewCmdReview(t *testing.T) { + tmpFile := filepath.Join(t.TempDir(), "my-body.md") + err := ioutil.WriteFile(tmpFile, []byte("a body from file"), 0600) + require.NoError(t, err) + tests := []struct { name string args string + stdin string isTTY bool want ReviewOptions wantErr string @@ -49,6 +56,27 @@ func Test_NewCmdReview(t *testing.T) { Body: "", }, }, + { + name: "body from stdin", + args: "123 --request-changes --body-file -", + stdin: "this is on standard input", + isTTY: true, + want: ReviewOptions{ + SelectorArg: "123", + ReviewType: 1, + Body: "this is on standard input", + }, + }, + { + name: "body from file", + args: fmt.Sprintf("123 --request-changes --body-file '%s'", tmpFile), + isTTY: true, + want: ReviewOptions{ + SelectorArg: "123", + ReviewType: 1, + Body: "a body from file", + }, + }, { name: "no argument with --repo override", args: "-R owner/repo", @@ -85,14 +113,24 @@ func Test_NewCmdReview(t *testing.T) { isTTY: true, wantErr: "--body unsupported without --approve, --request-changes, or --comment", }, + { + name: "body and body-file flags", + args: "--body 'test' --body-file 'test-file.txt'", + isTTY: true, + wantErr: "specify only one of `--body` or `--body-file`", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - io, _, _, _ := iostreams.Test() + io, stdin, _, _ := iostreams.Test() io.SetStdoutTTY(tt.isTTY) io.SetStdinTTY(tt.isTTY) io.SetStderrTTY(tt.isTTY) + if tt.stdin != "" { + _, _ = stdin.WriteString(tt.stdin) + } + f := &cmdutil.Factory{ IOStreams: io, } diff --git a/pkg/cmd/pr/shared/commentable.go b/pkg/cmd/pr/shared/commentable.go index 55edccbcb..e8f3836b0 100644 --- a/pkg/cmd/pr/shared/commentable.go +++ b/pkg/cmd/pr/shared/commentable.go @@ -42,6 +42,7 @@ type CommentableOptions struct { Interactive bool InputType InputType Body string + BodyFile string } func CommentablePreRun(cmd *cobra.Command, opts *CommentableOptions) error { @@ -50,6 +51,10 @@ func CommentablePreRun(cmd *cobra.Command, opts *CommentableOptions) error { opts.InputType = InputTypeInline inputFlags++ } + if cmd.Flags().Changed("body-file") { + opts.InputType = InputTypeInline + inputFlags++ + } if web, _ := cmd.Flags().GetBool("web"); web { opts.InputType = InputTypeWeb inputFlags++ @@ -61,15 +66,15 @@ func CommentablePreRun(cmd *cobra.Command, opts *CommentableOptions) error { if inputFlags == 0 { if !opts.IO.CanPrompt() { - return &cmdutil.FlagError{Err: errors.New("--body or --web required when not running interactively")} + return &cmdutil.FlagError{Err: errors.New("`--body`, `--body-file` or `--web` required when not running interactively")} } opts.Interactive = true } else if inputFlags == 1 { if !opts.IO.CanPrompt() && opts.InputType == InputTypeEditor { - return &cmdutil.FlagError{Err: errors.New("--body or --web required when not running interactively")} + return &cmdutil.FlagError{Err: errors.New("`--body`, `--body-file` or `--web` required when not running interactively")} } } else if inputFlags > 1 { - return &cmdutil.FlagError{Err: fmt.Errorf("specify only one of --body, --editor, or --web")} + return &cmdutil.FlagError{Err: fmt.Errorf("specify only one of `--body`, `--body-file`, `--editor`, or `--web`")} } return nil