Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@babakks
Copy link
Member

@babakks babakks commented Jul 15, 2025

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:

  • The underlying issue is resolved with a test case to confirm it. Unexpected of side effects of the initDefaultTitleBody is now fixed.
  • A number of new test cases are added for the web mode operation to make sure we're covering more scenarios; I can't say all, though.
  • An old web mode test that didn't capture the breaking changes of the last PR is now corrected to fully verify the pr create --web function. 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):

{
name: "web",
tty: true,
setup: func(opts *CreateOptions, t *testing.T) func() {
opts.WebMode = true
return func() {}
},
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
reg.StubRepoResponse("OWNER", "REPO")
reg.Register(
httpmock.GraphQL(`query UserCurrent\b`),
httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`))
},
cmdStubs: func(cs *run.CommandStubber) {
cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "")
cs.Register("git rev-parse --symbolic-full-name feature@{push}", 0, "refs/remotes/origin/feature")
cs.Register(`git show-ref --verify -- HEAD refs/remotes/origin/feature`, 1, "")
cs.Register(`git show-ref --verify -- HEAD refs/remotes/origin/feature`, 1, "")
cs.Register(`git push --set-upstream origin HEAD:refs/heads/feature`, 0, "")
},
promptStubs: func(pm *prompter.PrompterMock) {
pm.SelectFunc = func(p, _ string, opts []string) (int, error) {
if p == "Where should we push the 'feature' branch?" {
return 0, nil
} else {
return -1, prompter.NoSuchPromptErr(p)
}
}
},
expectedErrOut: "Opening https://github.com/OWNER/REPO/compare/master...feature in your browser.\n",
expectedBrowse: "https://github.com/OWNER/REPO/compare/master...feature?body=&expand=1",
},

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.

cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "")

Copilot AI review requested due to automatic review settings July 15, 2025 12:16
@babakks babakks requested a review from a team as a code owner July 15, 2025 12:16
@babakks babakks linked an issue Jul 15, 2025 that may be closed by this pull request
@babakks babakks requested a review from BagToad July 15, 2025 12:17
@babakks babakks temporarily deployed to cli-automation July 15, 2025 12:17 — with GitHub Actions Inactive
@babakks babakks requested a review from andyfeller July 15, 2025 12:17
Copy link
Contributor

Copilot AI left a 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

Comment on lines +656 to 663
} else {
if !opts.TitleProvided {
state.Title = title
}
if !opts.BodyProvided {
state.Body = body
}
}
Copy link

Copilot AI Jul 15, 2025

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.

Suggested change
} 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
}

Copilot uses AI. Check for mistakes.
Copy link
Member

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
Copy link

Copilot AI Jul 15, 2025

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.

Suggested change
// 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

Copilot uses AI. Check for mistakes.
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.
Copy link

Copilot AI Jul 15, 2025

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.

Suggested change
// 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.

Copilot uses AI. Check for mistakes.
Copy link
Member

@andyfeller andyfeller left a 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.

Comment on lines +599 to 626
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
}
Copy link
Member

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.

Comment on lines +656 to 663
} else {
if !opts.TitleProvided {
state.Title = title
}
if !opts.BodyProvided {
state.Body = body
}
}
Copy link
Member

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.

Comment on lines 652 to 663
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
}
}
Copy link
Member

@andyfeller andyfeller Jul 15, 2025

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:

Suggested change
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:

  1. gh pr create should use --title and/or --body over any other options when provided

  2. gh pr create should provide the default title and/or body where --title and/or --body are not specified under the given circumstances:

    1. --fill is provided to use commit info
    2. --fill-first is provided to use first commit
    3. --fill-verbose is provided to use commit message and body
    4. --web is 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:

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")
}

if opts.WebMode {
if !(opts.Autofill || opts.FillFirst) {
state.Title = opts.Title
state.Body = opts.Body
}
if opts.Template != "" {
state.Template = opts.Template
}

@babakks babakks changed the title Respect --title and --body in pr create web mode Respect --title and --body in pr create web mode (second attempt) Aug 7, 2025
@babakks
Copy link
Member Author

babakks commented Aug 7, 2025

Closing this due to #10527 (comment).

@babakks babakks closed this Aug 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gh pr create --web does not respect title and body arguments

3 participants