Repository: cli/cli
Pull request #8423 (2023-12-05 22:33:00)

autofill with body
- git: Return body from list of commits
    This allow to use body of every commits between two ref.

- pr: Add flag fillverbose
    This is used to fill the body of PR with all commits msg + body


Motivation: Usually I have lot of information within the body of commit msg, as you know many project had this situation, i.e. kernel. With this flag we can keep compatibility to previous flags but add a special behaviour if a developer put lot of effort in writing good commits.
(This pr is opened with this flag :) )


Bonus: Another thing that I would love to see is the same feature but when a PR is already opened, in this way we can backfill description of PR with the same content of commits. 

Fixes: #8445

cliAutomation: Hi! Thanks for the pull request. Please ensure that this change is linked to an issue by mentioning an issue number in the description of the pull request. If this pull request would close the issue, please put the word 'Fixes' before the issue number somewhere in the pull request body. If this is a tiny change like fixing a typo, feel free to ignore this message.
guerinoni: > @guerinoni: could you add a `fixes #...` line at the beginning of your PR body please? 🙇 Helping to link this up to the issue also it to be automatically closed when the PR is closed and merged.
> 
> If there is no existing issue, then this PR request will likely wait until such an issue is created and discussed with the core maintainers. 🙇

Oh thanks! I did the issue, didn't know about the right workflow :)
andyfeller: @guerinoni : was doing some testing of this locally from your branch and see `gh` panicking

```shell
$ ~/Documents/workspace/cli/cli/bin/gh pr create --fill-verbose
? Where should we push the 'test' branch? tinyfists/actions-experiments
panic: runtime error: index out of range [0] with length 0

goroutine 1 [running]:
github.com/cli/cli/v2/pkg/cmd/pr/create.initDefaultTitleBody({0xc000215280, 0xc00037c380, {0x30451f0, 0xc0006da000}, {0xc00055fb50, 0xb}, {0xc00042aa80, 0x4}, {0xc00042aa9b, 0x4}, ...}, ...)
	github.com/cli/cli/v2/pkg/cmd/pr/create/create.go:470 +0x3fe
github.com/cli/cli/v2/pkg/cmd/pr/create.NewIssueState({0xc000215280, 0xc00037c380, {0x30451f0, 0xc0006da000}, {0xc00055fb50, 0xb}, {0xc00042aa80, 0x4}, {0xc00042aa9b, 0x4}, ...}, ...)
	github.com/cli/cli/v2/pkg/cmd/pr/create/create.go:555 +0x318
github.com/cli/cli/v2/pkg/cmd/pr/create.createRun(0xc000274d80)
	github.com/cli/cli/v2/pkg/cmd/pr/create/create.go:241 +0xfe
github.com/cli/cli/v2/pkg/cmd/pr/create.NewCmdCreate.func1(0xc00044ba00?, {0xc0000467d0?, 0x4?, 0x20d5302?})
	github.com/cli/cli/v2/pkg/cmd/pr/create/create.go:196 +0x8cc
github.com/spf13/cobra.(*Command).execute(0xc000465200, {0xc0000467c0, 0x1, 0x1})
	github.com/spf13/cobra@v1.6.1/command.go:916 +0x87c
github.com/spf13/cobra.(*Command).ExecuteC(0xc000004300)
	github.com/spf13/cobra@v1.6.1/command.go:1044 +0x3a5
github.com/spf13/cobra.(*Command).ExecuteContextC(...)
	github.com/spf13/cobra@v1.6.1/command.go:977
main.mainRun()
	github.com/cli/cli/v2/cmd/gh/main.go:119 +0x5db
main.main()
	github.com/cli/cli/v2/cmd/gh/main.go:46 +0x13
```

These are the only 2 commits on the branch I was using for testing:

```shell
commit 56b2c0c702e4e89fab48009fa082d7b581a49fbf
Author: Andy Feller <andyfeller@github.com>
Date:   Tue Jan 9 14:21:50 2024 -0500

    Arbitrary change for testing
    
    This adds an arbitrary test, defining run-name on the workflow, to see how multiple commits are handled.

commit 8ba8b27b1607e77b7194945ba68ea7346c288743
Author: Andy Feller <andyfeller@github.com>
Date:   Tue Jan 9 14:20:21 2024 -0500

    This is a test of new gh pr create --fill-verbose PR
    
    This is a random commit that I can use for testing out changes to gh pr create --fill-verbose.
    
    This has multiple lines, which this may or may not break about but will also try adding a really long line without breaking it to see how it handles verbose content.
```
guerinoni: > @guerinoni : was doing some testing of this locally from your branch and see `gh` panicking
> 
> ```shell
> $ ~/Documents/workspace/cli/cli/bin/gh pr create --fill-verbose
> ? Where should we push the 'test' branch? tinyfists/actions-experiments
> panic: runtime error: index out of range [0] with length 0
> 
> goroutine 1 [running]:
> github.com/cli/cli/v2/pkg/cmd/pr/create.initDefaultTitleBody({0xc000215280, 0xc00037c380, {0x30451f0, 0xc0006da000}, {0xc00055fb50, 0xb}, {0xc00042aa80, 0x4}, {0xc00042aa9b, 0x4}, ...}, ...)
> 	github.com/cli/cli/v2/pkg/cmd/pr/create/create.go:470 +0x3fe
> github.com/cli/cli/v2/pkg/cmd/pr/create.NewIssueState({0xc000215280, 0xc00037c380, {0x30451f0, 0xc0006da000}, {0xc00055fb50, 0xb}, {0xc00042aa80, 0x4}, {0xc00042aa9b, 0x4}, ...}, ...)
> 	github.com/cli/cli/v2/pkg/cmd/pr/create/create.go:555 +0x318
> github.com/cli/cli/v2/pkg/cmd/pr/create.createRun(0xc000274d80)
> 	github.com/cli/cli/v2/pkg/cmd/pr/create/create.go:241 +0xfe
> github.com/cli/cli/v2/pkg/cmd/pr/create.NewCmdCreate.func1(0xc00044ba00?, {0xc0000467d0?, 0x4?, 0x20d5302?})
> 	github.com/cli/cli/v2/pkg/cmd/pr/create/create.go:196 +0x8cc
> github.com/spf13/cobra.(*Command).execute(0xc000465200, {0xc0000467c0, 0x1, 0x1})
> 	github.com/spf13/cobra@v1.6.1/command.go:916 +0x87c
> github.com/spf13/cobra.(*Command).ExecuteC(0xc000004300)
> 	github.com/spf13/cobra@v1.6.1/command.go:1044 +0x3a5
> github.com/spf13/cobra.(*Command).ExecuteContextC(...)
> 	github.com/spf13/cobra@v1.6.1/command.go:977
> main.mainRun()
> 	github.com/cli/cli/v2/cmd/gh/main.go:119 +0x5db
> main.main()
> 	github.com/cli/cli/v2/cmd/gh/main.go:46 +0x13
> ```
> 
> These are the only 2 commits on the branch I was using for testing:
> 
> ```shell
> commit 56b2c0c702e4e89fab48009fa082d7b581a49fbf
> Author: Andy Feller <andyfeller@github.com>
> Date:   Tue Jan 9 14:21:50 2024 -0500
> 
>     Arbitrary change for testing
>     
>     This adds an arbitrary test, defining run-name on the workflow, to see how multiple commits are handled.
> 
> commit 8ba8b27b1607e77b7194945ba68ea7346c288743
> Author: Andy Feller <andyfeller@github.com>
> Date:   Tue Jan 9 14:20:21 2024 -0500
> 
>     This is a test of new gh pr create --fill-verbose PR
>     
>     This is a random commit that I can use for testing out changes to gh pr create --fill-verbose.
>     
>     This has multiple lines, which this may or may not break about but will also try adding a really long line without breaking it to see how it handles verbose content.
> ```

I fixed it :)
andyfeller: Retrying my local testing and the results:

![Screenshot 2024-01-23 at 11 20 52 AM](https://github.com/cli/cli/assets/2089743/d9733b00-ebc9-4d02-ad7a-4137907068e1)

Not sure why the new lines aren't preserved 🤔 
guerinoni: > `gh pr create --fill-verbose`:
> 
> ![Screenshot 2024-01-23 at 11 20 52 AM](https://private-user-images.githubusercontent.com/2089743/299018517-d9733b00-ebc9-4d02-ad7a-4137907068e1.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDY0Mzk1NzksIm5iZiI6MTcwNjQzOTI3OSwicGF0aCI6Ii8yMDg5NzQzLzI5OTAxODUxNy1kOTczM2IwMC1lYmM5LTRkMDItYWQ3YS00MTM3OTA3MDY4ZTEucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDEyOCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDAxMjhUMTA1NDM5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NzJmOWRmZjBjNjYxNTFjZmM4ZmYwNzdhNTdlZjliYmFmZDQ2MDRiM2Y1Y2EwNTYxYTQzMmY3ZmU2MTkxODEzZCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.Ur951brgaLAbnmMkFgmXrMjWBAKeWvdfFn4o2usWvJQ)
> 
> Not sure why the new lines aren't preserved 🤔
> 
> `gh pr create --fill`:
> 
> ![Screenshot 2024-01-23 at 11 26 37 AM](https://private-user-images.githubusercontent.com/2089743/299021401-4135b19f-922f-43bd-9a7b-9a012de3abca.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDY0Mzk1NzksIm5iZiI6MTcwNjQzOTI3OSwicGF0aCI6Ii8yMDg5NzQzLzI5OTAyMTQwMS00MTM1YjE5Zi05MjJmLTQzYmQtOWE3Yi05YTAxMmRlM2FiY2EucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDEyOCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDAxMjhUMTA1NDM5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YjEyOGUzNmUyZjBkYmJlMDk0MGU2MjcyNTIzMWY2YzQ5ODY4NzI1ZjA3NGFhNmIxNjdiYmM0MWMwYmUyNzA3MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.SNz8rUQtad072_gNtf71e7TIIgPD2Cu0fP1XRknIB30)

About this one, after my changes i tested the command trying to opening this PR (again) but adding "print" of the body + a panic in order to block the real PR create, because it is already open, and the output is:
```
√ cli autofill-with-body % ./bin/gh pr create --fill-verbose
Warning: 1 uncommitted change
- **git: Return body from list of commits**
  This allow to use body of every commits between two ref.

- **pr: Add flag fillverbose**
  This is used to fill the body of PR with all commits msg + body
  of every commits because there can be lot of useful information.

  Signed-off-by: Federico Guerinoni <guerinoni.federico@gmail.com>


- **Make commit title in bold**

panic: stop
```

From this output I would say there aren't extra spaces at the beginning of the body, not sure why on GH has this behaviour

Patch:
diff --git a/git/client.go b/git/client.go
index 3ac6a4754..21adc8bb1 100644
--- a/git/client.go
+++ b/git/client.go
@@ -227,8 +227,14 @@ func (c *Client) UncommittedChangeCount(ctx context.Context) (int, error) {
 	return count, nil
 }
 
+func isGitSha(s string) bool {
+	shaRegex := regexp.MustCompile(`^[0-9a-fA-F]{7,40}$`)
+	ret := shaRegex.MatchString(s)
+	return ret
+}
+
 func (c *Client) Commits(ctx context.Context, baseRef, headRef string) ([]*Commit, error) {
-	args := []string{"-c", "log.ShowSignature=false", "log", "--pretty=format:%H,%s", "--cherry", fmt.Sprintf("%s...%s", baseRef, headRef)}
+	args := []string{"-c", "log.ShowSignature=false", "log", "--pretty=format:%H,%s,%b", "--cherry", fmt.Sprintf("%s...%s", baseRef, headRef)}
 	cmd, err := c.Command(ctx, args...)
 	if err != nil {
 		return nil, err
@@ -240,15 +246,39 @@ func (c *Client) Commits(ctx context.Context, baseRef, headRef string) ([]*Commi
 	commits := []*Commit{}
 	sha := 0
 	title := 1
-	for _, line := range outputLines(out) {
-		split := strings.SplitN(line, ",", 2)
-		if len(split) != 2 {
-			continue
+	body := 2
+	lines := outputLines(out)
+	for i := 0; i < len(lines); i++ {
+		split := strings.SplitN(lines[i], ",", 3)
+		if isGitSha(split[sha]) {
+			c := &Commit{
+				Sha:   split[sha],
+				Title: split[title],
+			}
+
+			if len(split) == 2 {
+				commits = append(commits, c)
+				continue
+			}
+
+			c.Body = split[body]
+			// This consumes all lines until the next commit and adds them to the body.
+			for {
+				i++
+				if i >= len(lines) {
+					break
+				}
+
+				possibleSplit := strings.SplitN(lines[i], ",", 3)
+				if len(possibleSplit) > 2 && isGitSha(possibleSplit[sha]) {
+					i--
+					break
+				}
+				c.Body += "\n"
+				c.Body += lines[i]
+			}
+			commits = append(commits, c)
 		}
-		commits = append(commits, &Commit{
-			Sha:   split[sha],
-			Title: split[title],
-		})
 	}
 	if len(commits) == 0 {
 		return nil, fmt.Errorf("could not find any commits between %s and %s", baseRef, headRef)
diff --git a/git/objects.go b/git/objects.go
index 952b6c335..c33d92b7c 100644
--- a/git/objects.go
+++ b/git/objects.go
@@ -67,6 +67,7 @@ func (r TrackingRef) String() string {
 type Commit struct {
 	Sha   string
 	Title string
+	Body  string
 }
 
 type BranchConfig struct {
diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go
index a8907aa3f..4b37001bb 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
+	FillVerbose bool
 	FillFirst   bool
 	WebMode     bool
 	RecoverFile string
@@ -163,6 +164,14 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
 				return cmdutil.FlagErrorf("`--fill` is not supported with `--fill-first`")
 			}
 
+			if opts.FillVerbose && opts.FillFirst {
+				return cmdutil.FlagErrorf("`--fill-verbose` is not supported with `--fill-first`")
+			}
+
+			if opts.FillVerbose && opts.Autofill {
+				return cmdutil.FlagErrorf("`--fill-verbose` is not supported with `--fill`")
+			}
+
 			opts.BodyProvided = cmd.Flags().Changed("body")
 			if bodyFile != "" {
 				b, err := cmdutil.ReadFile(bodyFile, opts.IO.In)
@@ -177,8 +186,8 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
 				return cmdutil.FlagErrorf("`--template` is not supported when using `--body` or `--body-file`")
 			}
 
-			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 !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 runF != nil {
@@ -196,6 +205,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
 	fl.StringVarP(&opts.BaseBranch, "base", "B", "", "The `branch` into which you want your code merged")
 	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.FillVerbose, "fill-verbose", "", false, "Use commits msg+body for description")
 	fl.BoolVarP(&opts.Autofill, "fill", "f", false, "Use commit info for title and body")
 	fl.BoolVar(&opts.FillFirst, "fill-first", false, "Use first commit info for title and body")
 	fl.StringSliceVarP(&opts.Reviewers, "reviewer", "r", nil, "Request reviews from people or teams by their `handle`")
@@ -292,7 +302,7 @@ func createRun(opts *CreateOptions) (err error) {
 			ghrepo.FullName(ctx.BaseRepo))
 	}
 
-	if opts.Autofill || opts.FillFirst || (opts.TitleProvided && opts.BodyProvided) {
+	if opts.FillVerbose || opts.Autofill || opts.FillFirst || (opts.TitleProvided && opts.BodyProvided) {
 		err = handlePush(*opts, *ctx)
 		if err != nil {
 			return
@@ -403,7 +413,9 @@ func createRun(opts *CreateOptions) (err error) {
 	return
 }
 
-func initDefaultTitleBody(ctx CreateContext, state *shared.IssueMetadataState, useFirstCommit bool) error {
+var regexPattern = regexp.MustCompile(`(?m)^`)
+
+func initDefaultTitleBody(ctx CreateContext, state *shared.IssueMetadataState, useFirstCommit bool, addBody bool) error {
 	baseRef := ctx.BaseTrackingBranch
 	headRef := ctx.HeadBranch
 	gitClient := ctx.GitClient
@@ -412,19 +424,24 @@ func initDefaultTitleBody(ctx CreateContext, state *shared.IssueMetadataState, u
 	if err != nil {
 		return err
 	}
-	if len(commits) == 1 || useFirstCommit {
-		commitIndex := len(commits) - 1
-		state.Title = commits[commitIndex].Title
-		body, err := gitClient.CommitBody(context.Background(), commits[commitIndex].Sha)
-		if err != nil {
-			return err
-		}
-		state.Body = body
+
+	if useFirstCommit {
+		state.Title = commits[len(commits)-1].Title
+		state.Body = commits[len(commits)-1].Body
 	} else {
 		state.Title = humanize(headRef)
 		var body strings.Builder
 		for i := len(commits) - 1; i >= 0; i-- {
-			fmt.Fprintf(&body, "- %s\n", commits[i].Title)
+			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)
+				}
+			}
 		}
 		state.Body = body.String()
 	}
@@ -495,9 +512,9 @@ func NewIssueState(ctx CreateContext, opts CreateOptions) (*shared.IssueMetadata
 		Draft:      opts.IsDraft,
 	}
 
-	if opts.Autofill || opts.FillFirst || !opts.TitleProvided || !opts.BodyProvided {
-		err := initDefaultTitleBody(ctx, state, opts.FillFirst)
-		if err != nil && (opts.Autofill || opts.FillFirst) {
+	if opts.FillVerbose || opts.Autofill || opts.FillFirst || !opts.TitleProvided || !opts.BodyProvided {
+		err := initDefaultTitleBody(ctx, state, 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)
 		}
 	}
@@ -659,7 +676,6 @@ func NewCreateContext(opts *CreateOptions) (*CreateContext, error) {
 		Client:             client,
 		GitClient:          gitClient,
 	}, nil
-
 }
 
 func getRemotes(opts *CreateOptions) (ghContext.Remotes, error) {
@@ -716,7 +732,6 @@ func previewPR(opts CreateOptions, openURL string) error {
 		fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
 	}
 	return opts.Browser.Browse(openURL)
-
 }
 
 func handlePush(opts CreateOptions, ctx CreateContext) error {


Test patch:
diff --git a/git/client_test.go b/git/client_test.go
index 841a496da..a1183b08d 100644
--- a/git/client_test.go
+++ b/git/client_test.go
@@ -471,22 +471,32 @@ func TestClientCommits(t *testing.T) {
 		{
 			name:        "get commits",
 			cmdStdout:   "6a6872b918c601a0e730710ad8473938a7516d30,testing testability test",
-			wantCmdArgs: `path/to/git -c log.ShowSignature=false log --pretty=format:%H,%s --cherry SHA1...SHA2`,
+			wantCmdArgs: `path/to/git -c log.ShowSignature=false log --pretty=format:%H,%s,%b --cherry SHA1...SHA2`,
 			wantCommits: []*Commit{{
 				Sha:   "6a6872b918c601a0e730710ad8473938a7516d30",
 				Title: "testing testability test",
 			}},
 		},
+		{
+			name:        "get commits with body",
+			cmdStdout:   "6a6872b918c601a0e730710ad8473938a7516d30,testing testability test,This is the body",
+			wantCmdArgs: `path/to/git -c log.ShowSignature=false log --pretty=format:%H,%s,%b --cherry SHA1...SHA2`,
+			wantCommits: []*Commit{{
+				Sha:   "6a6872b918c601a0e730710ad8473938a7516d30",
+				Title: "testing testability test",
+				Body:  "This is the body",
+			}},
+		},
 		{
 			name:         "no commits between SHAs",
-			wantCmdArgs:  `path/to/git -c log.ShowSignature=false log --pretty=format:%H,%s --cherry SHA1...SHA2`,
+			wantCmdArgs:  `path/to/git -c log.ShowSignature=false log --pretty=format:%H,%s,%b --cherry SHA1...SHA2`,
 			wantErrorMsg: "could not find any commits between SHA1 and SHA2",
 		},
 		{
 			name:          "git error",
 			cmdExitStatus: 1,
 			cmdStderr:     "git error message",
-			wantCmdArgs:   `path/to/git -c log.ShowSignature=false log --pretty=format:%H,%s --cherry SHA1...SHA2`,
+			wantCmdArgs:   `path/to/git -c log.ShowSignature=false log --pretty=format:%H,%s,%b --cherry SHA1...SHA2`,
 			wantErrorMsg:  "failed to run git: git error message",
 		},
 	}
diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go
index 2c71ab394..bd878a8c4 100644
--- a/pkg/cmd/pr/create/create_test.go
+++ b/pkg/cmd/pr/create/create_test.go
@@ -279,9 +279,9 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.GraphQL(`mutation PullRequestCreate\b`),
 					httpmock.GraphQLMutation(`
-					{ "data": { "createPullRequest": { "pullRequest": {
-						"URL": "https://github.com/OWNER/REPO/pull/12"
-					} } } }`,
+						{ "data": { "createPullRequest": { "pullRequest": {
+							"URL": "https://github.com/OWNER/REPO/pull/12"
+						} } } }`,
 						func(input map[string]interface{}) {
 							assert.Equal(t, "REPOID", input["repositoryId"])
 							assert.Equal(t, "my title", input["title"])
@@ -318,9 +318,9 @@ func Test_createRun(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{}) {
+							{ "data": { "createPullRequest": { "pullRequest": {
+								"URL": "https://github.com/OWNER/REPO/pull/12"
+							} } } }`, func(input map[string]interface{}) {
 						assert.Equal(t, "REPOID", input["repositoryId"].(string))
 						assert.Equal(t, "my title", input["title"].(string))
 						assert.Equal(t, "my body", input["body"].(string))
@@ -366,11 +366,11 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.GraphQL(`mutation PullRequestCreate\b`),
 					httpmock.GraphQLMutation(`
-						{ "data": { "createPullRequest": { "pullRequest": {
-							"id": "PullRequest#1",
-							"URL": "https://github.com/OWNER/REPO/pull/12"
-						} } } }
-						`, func(input map[string]interface{}) {
+							{ "data": { "createPullRequest": { "pullRequest": {
+								"id": "PullRequest#1",
+								"URL": "https://github.com/OWNER/REPO/pull/12"
+							} } } }
+							`, func(input map[string]interface{}) {
 						assert.Equal(t, "REPOID", input["repositoryId"].(string))
 						assert.Equal(t, "my title", input["title"].(string))
 						assert.Equal(t, "my body", input["body"].(string))
@@ -381,10 +381,10 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.GraphQL(`mutation UpdateProjectV2Items\b`),
 					httpmock.GraphQLQuery(`
-						{ "data": { "add_000": { "item": {
-							"id": "1"
-						} } } }
-						`, func(mutations string, inputs map[string]interface{}) {
+							{ "data": { "add_000": { "item": {
+								"id": "1"
+							} } } }
+							`, func(mutations string, inputs map[string]interface{}) {
 						variables, err := json.Marshal(inputs)
 						assert.NoError(t, err)
 						expectedMutations := "mutation UpdateProjectV2Items($input_000: AddProjectV2ItemByIdInput!) {add_000: addProjectV2ItemById(input: $input_000) { item { id } }}"
@@ -428,10 +428,10 @@ func Test_createRun(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{}) {
+							{ "data": { "createPullRequest": { "pullRequest": {
+								"URL": "https://github.com/OWNER/REPO/pull/12"
+							} } } }
+							`, func(input map[string]interface{}) {
 						assert.Equal(t, false, input["maintainerCanModify"].(bool))
 						assert.Equal(t, "REPOID", input["repositoryId"].(string))
 						assert.Equal(t, "my title", input["title"].(string))
@@ -475,16 +475,16 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.REST("POST", "repos/OWNER/REPO/forks"),
 					httpmock.StatusStringResponse(201, `
-						{ "node_id": "NODEID",
-						  "name": "REPO",
-						  "owner": {"login": "monalisa"}
-						}`))
+							{ "node_id": "NODEID",
+							  "name": "REPO",
+							  "owner": {"login": "monalisa"}
+							}`))
 				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{}) {
+							{ "data": { "createPullRequest": { "pullRequest": {
+								"URL": "https://github.com/OWNER/REPO/pull/12"
+							}}}}`, func(input map[string]interface{}) {
 						assert.Equal(t, "REPOID", input["repositoryId"].(string))
 						assert.Equal(t, "master", input["baseRefName"].(string))
 						assert.Equal(t, "monalisa:feature", input["headRefName"].(string))
@@ -541,9 +541,9 @@ func Test_createRun(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{}) {
+							{ "data": { "createPullRequest": { "pullRequest": {
+								"URL": "https://github.com/OWNER/REPO/pull/12"
+							} } } }`, func(input map[string]interface{}) {
 						assert.Equal(t, "REPOID", input["repositoryId"].(string))
 						assert.Equal(t, "master", input["baseRefName"].(string))
 						assert.Equal(t, "monalisa:feature", input["headRefName"].(string))
@@ -552,9 +552,9 @@ func Test_createRun(t *testing.T) {
 			cmdStubs: func(cs *run.CommandStubber) {
 				cs.Register(`git config --get-regexp \^branch\\\.feature\\\.`, 1, "") // determineTrackingBranch
 				cs.Register("git show-ref --verify", 0, heredoc.Doc(`
-		deadbeef HEAD
-		deadb00f refs/remotes/upstream/feature
-		deadbeef refs/remotes/origin/feature`)) // determineTrackingBranch
+			deadbeef HEAD
+			deadb00f refs/remotes/upstream/feature
+			deadbeef refs/remotes/origin/feature`)) // determineTrackingBranch
 			},
 			expectedOut:    "https://github.com/OWNER/REPO/pull/12\n",
 			expectedErrOut: "\nCreating pull request for monalisa:feature into master in OWNER/REPO\n\n",
@@ -573,10 +573,10 @@ func Test_createRun(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{}) {
+			{ "data": { "createPullRequest": { "pullRequest": {
+				"URL": "https://github.com/OWNER/REPO/pull/12"
+			} } } }
+			`, func(input map[string]interface{}) {
 						assert.Equal(t, "REPOID", input["repositoryId"].(string))
 						assert.Equal(t, "master", input["baseRefName"].(string))
 						assert.Equal(t, "my-feat2", input["headRefName"].(string))
@@ -584,13 +584,13 @@ func Test_createRun(t *testing.T) {
 			},
 			cmdStubs: func(cs *run.CommandStubber) {
 				cs.Register(`git config --get-regexp \^branch\\\.feature\\\.`, 0, heredoc.Doc(`
-		branch.feature.remote origin
-		branch.feature.merge refs/heads/my-feat2
-	`)) // determineTrackingBranch
+			branch.feature.remote origin
+			branch.feature.merge refs/heads/my-feat2
+		`)) // determineTrackingBranch
 				cs.Register("git show-ref --verify", 0, heredoc.Doc(`
-		deadbeef HEAD
-		deadbeef refs/remotes/origin/my-feat2
-	`)) // determineTrackingBranch
+			deadbeef HEAD
+			deadbeef refs/remotes/origin/my-feat2
+		`)) // determineTrackingBranch
 			},
 			expectedOut:    "https://github.com/OWNER/REPO/pull/12\n",
 			expectedErrOut: "\nCreating pull request for my-feat2 into master in OWNER/REPO\n\n",
@@ -609,25 +609,25 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.GraphQL(`query PullRequestTemplates\b`),
 					httpmock.StringResponse(`
-				{ "data": { "repository": { "pullRequestTemplates": [
-					{ "filename": "template1",
-					  "body": "this is a bug" },
-					{ "filename": "template2",
-					  "body": "this is a enhancement" }
-				] } } }`))
+					{ "data": { "repository": { "pullRequestTemplates": [
+						{ "filename": "template1",
+						  "body": "this is a bug" },
+						{ "filename": "template2",
+						  "body": "this is a enhancement" }
+					] } } }`))
 				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{}) {
+				{ "data": { "createPullRequest": { "pullRequest": {
+					"URL": "https://github.com/OWNER/REPO/pull/12"
+				} } } }
+				`, func(input map[string]interface{}) {
 						assert.Equal(t, "my title", input["title"].(string))
-						assert.Equal(t, "- commit 1\n- commit 0\n\nthis is a bug", input["body"].(string))
+						assert.Equal(t, "- **commit 1**\n- **commit 0**\n\nthis is a bug", input["body"].(string))
 					}))
 			},
 			cmdStubs: func(cs *run.CommandStubber) {
-				cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "1234567890,commit 0\n2345678901,commit 1")
+				cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "d3476a1,commit 0\n7a6ea13,commit 1")
 			},
 			promptStubs: func(pm *prompter.PrompterMock) {
 				pm.MarkdownEditorFunc = func(p, d string, ba bool) (string, error) {
@@ -671,39 +671,39 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.GraphQL(`query RepositoryResolveMetadataIDs\b`),
 					httpmock.StringResponse(`
-				{ "data": {
-					"u000": { "login": "MonaLisa", "id": "MONAID" },
-					"u001": { "login": "hubot", "id": "HUBOTID" },
-					"repository": {
-						"l000": { "name": "bug", "id": "BUGID" },
-						"l001": { "name": "TODO", "id": "TODOID" }
-					},
-					"organization": {
-						"t000": { "slug": "core", "id": "COREID" },
-						"t001": { "slug": "robots", "id": "ROBOTID" }
-					}
-				} }
-				`))
+					{ "data": {
+						"u000": { "login": "MonaLisa", "id": "MONAID" },
+						"u001": { "login": "hubot", "id": "HUBOTID" },
+						"repository": {
+							"l000": { "name": "bug", "id": "BUGID" },
+							"l001": { "name": "TODO", "id": "TODOID" }
+						},
+						"organization": {
+							"t000": { "slug": "core", "id": "COREID" },
+							"t001": { "slug": "robots", "id": "ROBOTID" }
+						}
+					} }
+					`))
 				reg.Register(
 					httpmock.GraphQL(`query RepositoryMilestoneList\b`),
 					httpmock.StringResponse(`
-				{ "data": { "repository": { "milestones": {
-					"nodes": [
-						{ "title": "GA", "id": "GAID" },
-						{ "title": "Big One.oh", "id": "BIGONEID" }
-					],
-					"pageInfo": { "hasNextPage": false }
-				} } } }
-				`))
+					{ "data": { "repository": { "milestones": {
+						"nodes": [
+							{ "title": "GA", "id": "GAID" },
+							{ "title": "Big One.oh", "id": "BIGONEID" }
+						],
+						"pageInfo": { "hasNextPage": false }
+					} } } }
+					`))
 				mockRetrieveProjects(t, reg)
 				reg.Register(
 					httpmock.GraphQL(`mutation PullRequestCreate\b`),
 					httpmock.GraphQLMutation(`
-				{ "data": { "createPullRequest": { "pullRequest": {
-					"id": "NEWPULLID",
-					"URL": "https://github.com/OWNER/REPO/pull/12"
-				} } } }
-			`, func(inputs map[string]interface{}) {
+					{ "data": { "createPullRequest": { "pullRequest": {
+						"id": "NEWPULLID",
+						"URL": "https://github.com/OWNER/REPO/pull/12"
+					} } } }
+				`, func(inputs map[string]interface{}) {
 						assert.Equal(t, "TITLE", inputs["title"])
 						assert.Equal(t, "BODY", inputs["body"])
 						if v, ok := inputs["assigneeIds"]; ok {
@@ -716,10 +716,10 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.GraphQL(`mutation PullRequestCreateMetadata\b`),
 					httpmock.GraphQLMutation(`
-				{ "data": { "updatePullRequest": {
-					"clientMutationId": ""
-				} } }
-			`, func(inputs map[string]interface{}) {
+					{ "data": { "updatePullRequest": {
+						"clientMutationId": ""
+					} } }
+				`, func(inputs map[string]interface{}) {
 						assert.Equal(t, "NEWPULLID", inputs["pullRequestId"])
 						assert.Equal(t, []interface{}{"MONAID"}, inputs["assigneeIds"])
 						assert.Equal(t, []interface{}{"BUGID", "TODOID"}, inputs["labelIds"])
@@ -729,10 +729,10 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.GraphQL(`mutation PullRequestCreateRequestReviews\b`),
 					httpmock.GraphQLMutation(`
-				{ "data": { "requestReviews": {
-					"clientMutationId": ""
-				} } }
-			`, func(inputs map[string]interface{}) {
+					{ "data": { "requestReviews": {
+						"clientMutationId": ""
+					} } }
+				`, func(inputs map[string]interface{}) {
 						assert.Equal(t, "NEWPULLID", inputs["pullRequestId"])
 						assert.Equal(t, []interface{}{"HUBOTID", "MONAID"}, inputs["userIds"])
 						assert.Equal(t, []interface{}{"COREID", "ROBOTID"}, inputs["teamIds"])
@@ -807,7 +807,6 @@ func Test_createRun(t *testing.T) {
 				cs.Register(`git show-ref --verify -- HEAD refs/remotes/origin/feature`, 0, "")
 				cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "")
 				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) {
@@ -834,25 +833,25 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.GraphQL(`query PullRequestTemplates\b`),
 					httpmock.StringResponse(`
-			{ "data": { "repository": { "pullRequestTemplates": [
-				{ "filename": "template1",
-				  "body": "this is a bug" },
-				{ "filename": "template2",
-				  "body": "this is a enhancement" }
-			] } } }`),
+				{ "data": { "repository": { "pullRequestTemplates": [
+					{ "filename": "template1",
+					  "body": "this is a bug" },
+					{ "filename": "template2",
+					  "body": "this is a enhancement" }
+				] } } }`),
 				)
 				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{}) {
+			{ "data": { "createPullRequest": { "pullRequest": {
+				"URL": "https://github.com/OWNER/REPO/pull/12"
+			} } } }
+			`, func(input map[string]interface{}) {
 						assert.Equal(t, true, input["draft"].(bool))
 					}))
 			},
 			cmdStubs: func(cs *run.CommandStubber) {
-				cs.Register(`git -c log.ShowSignature=false log --pretty=format:%H,%s --cherry origin/master...feature`, 0, "")
+				cs.Register(`git -c log.ShowSignature=false log --pretty=format:%H,%s,%b --cherry origin/master...feature`, 0, "")
 				cs.Register(`git rev-parse --show-toplevel`, 0, "")
 			},
 			promptStubs: func(pm *prompter.PrompterMock) {
@@ -884,28 +883,28 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.GraphQL(`query RepositoryResolveMetadataIDs\b`),
 					httpmock.StringResponse(`
-		{ "data": {
-			"u000": { "login": "jillValentine", "id": "JILLID" },
-			"repository": {},
-			"organization": {}
-		} }
-		`))
+			{ "data": {
+				"u000": { "login": "jillValentine", "id": "JILLID" },
+				"repository": {},
+				"organization": {}
+			} }
+			`))
 				reg.Register(
 					httpmock.GraphQL(`mutation PullRequestCreateRequestReviews\b`),
 					httpmock.GraphQLMutation(`
-		{ "data": { "requestReviews": {
-			"clientMutationId": ""
-		} } }
-	`, func(inputs map[string]interface{}) {
+			{ "data": { "requestReviews": {
+				"clientMutationId": ""
+			} } }
+		`, func(inputs map[string]interface{}) {
 						assert.Equal(t, []interface{}{"JILLID"}, inputs["userIds"])
 					}))
 				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{}) {
+			{ "data": { "createPullRequest": { "pullRequest": {
+				"URL": "https://github.com/OWNER/REPO/pull/12"
+			} } } }
+			`, func(input map[string]interface{}) {
 						assert.Equal(t, "recovered title", input["title"].(string))
 						assert.Equal(t, "recovered body", input["body"].(string))
 					}))
@@ -989,10 +988,10 @@ func Test_createRun(t *testing.T) {
 				reg.Register(
 					httpmock.GraphQL(`mutation PullRequestCreate\b`),
 					httpmock.StringResponse(`
-						{ "data": { "createPullRequest": { "pullRequest": {
-							"URL": "https://github.com/OWNER/REPO/pull/12"
-						} } } }
-					`))
+							{ "data": { "createPullRequest": { "pullRequest": {
+								"URL": "https://github.com/OWNER/REPO/pull/12"
+							} } } }
+						`))
 			},
 			expectedOut: "https://github.com/OWNER/REPO/pull/12\n",
 		},
@@ -1006,15 +1005,46 @@ func Test_createRun(t *testing.T) {
 			},
 			cmdStubs: func(cs *run.CommandStubber) {
 				cs.Register(
-					"git -c log.ShowSignature=false log --pretty=format:%H,%s --cherry origin/master...feature",
+					"git -c log.ShowSignature=false log --pretty=format:%H,%s,%b --cherry origin/master...feature",
 					0,
 					"56b6f8bb7c9e3a30093cd17e48934ce354148e80,second commit of pr\n"+
-						"343jdfe47c9e3a30093cd17e48934ce354148e80,first commit of pr",
+						"3a9b48085046d156c5acce8f3b3a0532cd706a4a,first commit of pr,first commit description\n",
+				)
+			},
+			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",
+		},
+		{
+			name: "fillverbose flag provided",
+			tty:  true,
+			setup: func(opts *CreateOptions, t *testing.T) func() {
+				opts.FillVerbose = true
+				opts.HeadBranch = "feature"
+				return func() {}
+			},
+			cmdStubs: func(cs *run.CommandStubber) {
 				cs.Register(
-					"git -c log.ShowSignature=false show -s --pretty=format:%b 343jdfe47c9e3a30093cd17e48934ce354148e80",
+					"git -c log.ShowSignature=false log --pretty=format:%H,%s,%b --cherry origin/master...feature",
 					0,
-					"first commit description",
+					"56b6f8bb7c9e3a30093cd17e48934ce354148e80,second commit of pr,second commit description\n"+
+						"3a9b48085046d156c5acce8f3b3a0532cd706a4a,first commit of pr,first commit with super long description, with super long description, with super long description, with super long description.\n",
 				)
 			},
 			httpStubs: func(reg *httpmock.Registry, t *testing.T) {
@@ -1028,8 +1058,8 @@ func Test_createRun(t *testing.T) {
 						}
 						`,
 						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")
+							assert.Equal(t, "feature", input["title"], "pr title should be branch name")
+							assert.Equal(t, "- **first commit of pr**\n  first commit with super long description, with super long description, with super long description, with super long description.\n\n- **second commit of pr**\n  second commit description", input["body"], "pr body should be commits msg+body")
 						},
 					),
 				)