-
Notifications
You must be signed in to change notification settings - Fork 7.4k
Respect --title and --body in pr create web mode (second attempt)
#11306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Signed-off-by: Babak K. Shandiz <[email protected]>
Signed-off-by: Babak K. Shandiz <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes an issue where the --title and --body flags were not being properly respected in pr create web mode. The fix ensures that when these flags are provided, they take precedence over auto-generated values from git history.
Key changes:
- Refactored default title/body computation logic to separate concerns
- Fixed the condition logic to properly respect provided title/body values
- Added comprehensive test coverage for various web mode scenarios
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| pkg/cmd/pr/create/create.go | Refactored initDefaultTitleBody to computeDefaultTitleBody and fixed the logic to respect provided title/body values |
| pkg/cmd/pr/create/create_test.go | Added extensive test cases for web mode scenarios and improved existing test to use realistic git history |
| } else { | ||
| if !opts.TitleProvided { | ||
| state.Title = title | ||
| } | ||
| if !opts.BodyProvided { | ||
| state.Body = body | ||
| } | ||
| } |
Copilot
AI
Jul 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The else block after a return statement is unnecessary. Consider removing the else and dedenting the code block for better readability.
| } else { | |
| if !opts.TitleProvided { | |
| state.Title = title | |
| } | |
| if !opts.BodyProvided { | |
| state.Body = body | |
| } | |
| } | |
| } | |
| if !opts.TitleProvided { | |
| state.Title = title | |
| } | |
| if !opts.BodyProvided { | |
| state.Body = body | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to suggest this but held off while trying to understand these changes in context of the regression.
| httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) | ||
| }, | ||
| cmdStubs: func(cs *run.CommandStubber) { | ||
| // Since both --title and --body options are provided, and also --fill is set, then no git log history |
Copilot
AI
Jul 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment contains a grammatical error. 'and also --fill is set' should be 'but no --fill is set' or similar, as this test case doesn't set any fill options.
| // Since both --title and --body options are provided, and also --fill is set, then no git log history | |
| // Since both --title and --body options are provided, but no --fill is set, then no git log history |
| return func() {} | ||
| }, | ||
| cmdStubs: func(cs *run.CommandStubber) { | ||
| // Here we simulate a non-empty git history, and we want to see that the title/body fields are not populated from it, since no fill option is provided. |
Copilot
AI
Jul 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is misleading. This test case has opts.Autofill = true set, so a fill option IS provided. The comment should reflect that the explicitly provided title/body values should take precedence over the autofilled values.
| // Here we simulate a non-empty git history, and we want to see that the title/body fields are not populated from it, since no fill option is provided. | |
| // Here we simulate a non-empty git history. Although the fill option is enabled (opts.Autofill = true), the explicitly provided title and body values (opts.Title and opts.Body) take precedence and are used instead of autofilled values. |
andyfeller
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for jumping on top of this, @babakks! ❤️
I'd like to make sure I understand the various use cases where gh pr create is determining the title and body for the user and clean up the logic a bit so we can avoid future problems.
I apologize as my suggestion might be a little difficult to follow because it has implications on a few other areas in the code.
| func computeDefaultTitleBody(ctx CreateContext, useFirstCommit bool, addBody bool) (string, string, error) { | ||
| commits, err := ctx.GitClient.Commits(context.Background(), ctx.BaseTrackingBranch, ctx.PRRefs.UnqualifiedHeadRef()) | ||
| if err != nil { | ||
| return err | ||
| return "", "", err | ||
| } | ||
|
|
||
| if len(commits) == 1 || useFirstCommit { | ||
| state.Title = commits[len(commits)-1].Title | ||
| state.Body = commits[len(commits)-1].Body | ||
| } else { | ||
| state.Title = humanize(ctx.PRRefs.UnqualifiedHeadRef()) | ||
| var body strings.Builder | ||
| for i := len(commits) - 1; i >= 0; i-- { | ||
| fmt.Fprintf(&body, "- **%s**\n", commits[i].Title) | ||
| if addBody { | ||
| x := regexPattern.ReplaceAllString(commits[i].Body, " ") | ||
| fmt.Fprintf(&body, "%s", x) | ||
|
|
||
| if i > 0 { | ||
| fmt.Fprintln(&body) | ||
| fmt.Fprintln(&body) | ||
| } | ||
| return commits[len(commits)-1].Title, commits[len(commits)-1].Body, nil | ||
| } | ||
|
|
||
| title := humanize(ctx.PRRefs.UnqualifiedHeadRef()) | ||
| var sb strings.Builder | ||
| for i := len(commits) - 1; i >= 0; i-- { | ||
| fmt.Fprintf(&sb, "- **%s**\n", commits[i].Title) | ||
| if addBody { | ||
| x := regexPattern.ReplaceAllString(commits[i].Body, " ") | ||
| fmt.Fprintf(&sb, "%s", x) | ||
|
|
||
| if i > 0 { | ||
| fmt.Fprintln(&sb) | ||
| fmt.Fprintln(&sb) | ||
| } | ||
| } | ||
| state.Body = body.String() | ||
| } | ||
| body := sb.String() | ||
|
|
||
| return nil | ||
| return title, body, nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: I like how this is now focused on returning a title and body based on the state of commits, leaving it to the caller what to do with them.
| } else { | ||
| if !opts.TitleProvided { | ||
| state.Title = title | ||
| } | ||
| if !opts.BodyProvided { | ||
| state.Body = body | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to suggest this but held off while trying to understand these changes in context of the regression.
| if opts.FillVerbose || opts.Autofill || opts.FillFirst || !opts.TitleProvided || !opts.BodyProvided { | ||
| err := initDefaultTitleBody(ctx, state, opts.FillFirst, opts.FillVerbose) | ||
| title, body, err := computeDefaultTitleBody(ctx, opts.FillFirst, opts.FillVerbose) | ||
| if err != nil && (opts.FillVerbose || opts.Autofill || opts.FillFirst) { | ||
| return nil, fmt.Errorf("could not compute title or body defaults: %w", err) | ||
| } else { | ||
| if !opts.TitleProvided { | ||
| state.Title = title | ||
| } | ||
| if !opts.BodyProvided { | ||
| state.Body = body | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: I think --web logic that follows NewIssueState(...) should be consolidated and rewritten into this function in order to make it easier to understand what this command will do along with plain language explanation of what should happen in these different circumstances:
| if opts.FillVerbose || opts.Autofill || opts.FillFirst || !opts.TitleProvided || !opts.BodyProvided { | |
| err := initDefaultTitleBody(ctx, state, opts.FillFirst, opts.FillVerbose) | |
| title, body, err := computeDefaultTitleBody(ctx, opts.FillFirst, opts.FillVerbose) | |
| if err != nil && (opts.FillVerbose || opts.Autofill || opts.FillFirst) { | |
| return nil, fmt.Errorf("could not compute title or body defaults: %w", err) | |
| } else { | |
| if !opts.TitleProvided { | |
| state.Title = title | |
| } | |
| if !opts.BodyProvided { | |
| state.Body = body | |
| } | |
| } | |
| if opts.FillVerbose || opts.Autofill || opts.FillFirst || !opts.WebMode { | |
| title, body, err := computeDefaultTitleBody(ctx, opts.FillFirst, opts.FillVerbose) | |
| if err != nil && (opts.FillVerbose || opts.Autofill || opts.FillFirst) { | |
| return nil, fmt.Errorf("could not compute title or body defaults: %w", err) | |
| } | |
| if !opts.TitleProvided { | |
| state.Title = title | |
| } | |
| if !opts.BodyProvided { | |
| state.Body = body | |
| } |
Here's how I think about this logic:
-
gh pr createshould use--titleand/or--bodyover any other options when provided -
gh pr createshould provide the default title and/or body where--titleand/or--bodyare not specified under the given circumstances:--fillis provided to use commit info--fill-firstis provided to use first commit--fill-verboseis provided to use commit message and body--webis absent (the GitHub.com UI takes over the default title and body responsibility)
My concern is that the state being overridden in the other places makes this hard to follow when not consolidated:
cli/pkg/cmd/pr/create/create.go
Lines 314 to 316 in dbff7c5
| if !opts.IO.CanPrompt() && !opts.WebMode && !(opts.FillVerbose || opts.Autofill || opts.FillFirst) && (!opts.TitleProvided || !opts.BodyProvided) { | |
| return cmdutil.FlagErrorf("must provide `--title` and `--body` (or `--fill` or `fill-first` or `--fillverbose`) when not running interactively") | |
| } |
cli/pkg/cmd/pr/create/create.go
Lines 393 to 400 in ca68f44
| if opts.WebMode { | |
| if !(opts.Autofill || opts.FillFirst) { | |
| state.Title = opts.Title | |
| state.Body = opts.Body | |
| } | |
| if opts.Template != "" { | |
| state.Template = opts.Template | |
| } |
--title and --body in pr create web mode--title and --body in pr create web mode (second attempt)
|
Closing this due to #10527 (comment). |
Fixes #10527
This is a second attempt to fix the issue. The last PR (#10547) caused a regression by breaking the behaviour of
pr create --web(i.e. with no--title,--body, or-fill*options).In this PR:
initDefaultTitleBodyis now fixed.pr create --webfunction. More on this specific test case in the following.Retrospective on the test case
The reason we didn't notice the breaking change in the last PR was landed, was this ineffective test case that was supposed to verify the behaviour of
pr create --web(with no further options):cli/pkg/cmd/pr/create/create_test.go
Lines 1118 to 1149 in dbff7c5
Specifically, the below neutral stub is the reason why the breaking change didn't surface. Because it's simulating an empty git log, so with or without trying to fill the title and body fields the behaviour would be the same.
cli/pkg/cmd/pr/create/create_test.go
Line 1132 in dbff7c5