diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 4c0c6286b..009bcd03f 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -45,6 +45,7 @@ type CreateOptions struct { RepoOverride string Autofill bool + FillFirst bool WebMode bool RecoverFile string @@ -167,8 +168,8 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co return errors.New("`--template` is not supported when using `--body` or `--body-file`") } - if !opts.IO.CanPrompt() && !opts.WebMode && !opts.Autofill && (!opts.TitleProvided || !opts.BodyProvided) { - return cmdutil.FlagErrorf("must provide `--title` and `--body` (or `--fill`) when not running interactively") + if !opts.IO.CanPrompt() && !opts.WebMode && !(opts.Autofill || opts.FillFirst) && (!opts.TitleProvided || !opts.BodyProvided) { + return cmdutil.FlagErrorf("must provide `--title` and `--body` (or `--fill` or `fill-first`) when not running interactively") } if runF != nil { @@ -187,6 +188,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co fl.StringVarP(&opts.HeadBranch, "head", "H", "", "The `branch` that contains commits for your pull request (default: current branch)") fl.BoolVarP(&opts.WebMode, "web", "w", false, "Open the web browser to create a pull request") fl.BoolVarP(&opts.Autofill, "fill", "f", false, "Do not prompt for title/body and just use commit info") + fl.BoolVar(&opts.FillFirst, "fill-first", false, "Do not prompt for title/body and just use first commit info") fl.StringSliceVarP(&opts.Reviewers, "reviewer", "r", nil, "Request reviews from people or teams by their `handle`") fl.StringSliceVarP(&opts.Assignees, "assignee", "a", nil, "Assign people by their `login`. Use \"@me\" to self-assign.") fl.StringSliceVarP(&opts.Labels, "label", "l", nil, "Add labels by `name`") @@ -196,6 +198,8 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co fl.StringVar(&opts.RecoverFile, "recover", "", "Recover input from a failed run of create") fl.StringVarP(&opts.Template, "template", "T", "", "Template `file` to use as starting body text") + cmd.MarkFlagsMutuallyExclusive("fill", "fill-first") + _ = cmdutil.RegisterBranchCompletionFlags(f.GitClient, cmd, "base", "head") _ = cmd.RegisterFlagCompletionFunc("reviewer", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -281,7 +285,7 @@ func createRun(opts *CreateOptions) (err error) { ghrepo.FullName(ctx.BaseRepo)) } - if opts.Autofill || (opts.TitleProvided && opts.BodyProvided) { + if opts.Autofill || opts.FillFirst || (opts.TitleProvided && opts.BodyProvided) { err = handlePush(*opts, *ctx) if err != nil { return @@ -392,7 +396,7 @@ func createRun(opts *CreateOptions) (err error) { return } -func initDefaultTitleBody(ctx CreateContext, state *shared.IssueMetadataState) error { +func initDefaultTitleBody(ctx CreateContext, state *shared.IssueMetadataState, opts *CreateOptions) error { baseRef := ctx.BaseTrackingBranch headRef := ctx.HeadBranch gitClient := ctx.GitClient @@ -401,8 +405,15 @@ func initDefaultTitleBody(ctx CreateContext, state *shared.IssueMetadataState) e if err != nil { return err } - - if len(commits) == 1 { + if opts.FillFirst { + firstCommitIndex := len(commits) - 1 + state.Title = commits[firstCommitIndex].Title + body, err := gitClient.CommitBody(context.Background(), commits[firstCommitIndex].Sha) + if err != nil { + return err + } + state.Body = body + } else if len(commits) == 1 { state.Title = commits[0].Title body, err := gitClient.CommitBody(context.Background(), commits[0].Sha) if err != nil { @@ -485,9 +496,9 @@ func NewIssueState(ctx CreateContext, opts CreateOptions) (*shared.IssueMetadata Draft: opts.IsDraft, } - if opts.Autofill || !opts.TitleProvided || !opts.BodyProvided { - err := initDefaultTitleBody(ctx, state) - if err != nil && opts.Autofill { + if opts.Autofill || opts.FillFirst || !opts.TitleProvided || !opts.BodyProvided { + err := initDefaultTitleBody(ctx, state, &opts) + if err != nil && (opts.Autofill || opts.FillFirst) { return nil, fmt.Errorf("could not compute title or body defaults: %w", err) } } diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go index 5b5fe2ee4..cb2acd03a 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -168,6 +168,32 @@ func TestNewCmdCreate(t *testing.T) { cli: "-t mytitle --template bug_fix.md --body-file body_file.md", wantsErr: true, }, + { + name: "with fill-first option", + tty: false, + cli: "--fill-first", + wantsErr: false, + wantsOpts: CreateOptions{ + Title: "", + TitleProvided: false, + Body: "", + BodyProvided: false, + Autofill: false, + FillFirst: true, + RecoverFile: "", + WebMode: false, + IsDraft: false, + BaseBranch: "", + HeadBranch: "", + MaintainerCanModify: true, + }, + }, + { + name: "fill and fill-first is mutually exclusive", + tty: false, + cli: "--fill --fill-first", + wantsErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -210,6 +236,7 @@ func TestNewCmdCreate(t *testing.T) { assert.Equal(t, tt.wantsOpts.Title, opts.Title) assert.Equal(t, tt.wantsOpts.TitleProvided, opts.TitleProvided) assert.Equal(t, tt.wantsOpts.Autofill, opts.Autofill) + assert.Equal(t, tt.wantsOpts.FillFirst, opts.FillFirst) assert.Equal(t, tt.wantsOpts.WebMode, opts.WebMode) assert.Equal(t, tt.wantsOpts.RecoverFile, opts.RecoverFile) assert.Equal(t, tt.wantsOpts.IsDraft, opts.IsDraft) @@ -969,6 +996,48 @@ func Test_createRun(t *testing.T) { }, expectedOut: "https://github.com/OWNER/REPO/pull/12\n", }, + { + name: "fill-first flag provided", + tty: true, + setup: func(opts *CreateOptions, t *testing.T) func() { + opts.TitleProvided = false + opts.FillFirst = true + opts.HeadBranch = "feature" + return func() {} + }, + cmdStubs: func(cs *run.CommandStubber) { + cs.Register( + "git -c log.ShowSignature=false log --pretty=format:%H,%s --cherry origin/master...feature", + 0, + "56b6f8bb7c9e3a30093cd17e48934ce354148e80,second commit of pr\n"+ + "343jdfe47c9e3a30093cd17e48934ce354148e80,first commit of pr", + ) + cs.Register( + "git -c log.ShowSignature=false show -s --pretty=format:%b 343jdfe47c9e3a30093cd17e48934ce354148e80", + 0, + "first commit description", + ) + }, + httpStubs: func(reg *httpmock.Registry, t *testing.T) { + reg.Register( + httpmock.GraphQL(`mutation PullRequestCreate\b`), + httpmock.GraphQLMutation(` + { + "data": { "createPullRequest": { "pullRequest": { + "URL": "https://github.com/OWNER/REPO/pull/12" + } } } + } + `, + func(input map[string]interface{}) { + assert.Equal(t, "first commit of pr", input["title"], "pr title should be first commit message") + assert.Equal(t, "first commit description", input["body"], "pr body should be first commit description") + }, + ), + ) + }, + expectedOut: "https://github.com/OWNER/REPO/pull/12\n", + expectedErrOut: "\nCreating pull request for feature into master in OWNER/REPO\n\n", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {