Merge branch 'commits-in-template' into 'main'

Fill mr description from commits in editor

Closes #1070

See merge request https://gitlab.com/gitlab-org/cli/-/merge_requests/1277

Merged-by: Gary Holtz <gholtz@gitlab.com>
Approved-by: Gary Holtz <gholtz@gitlab.com>
Reviewed-by: Benedek Thaler <benedek.thaler@wincent.co>
Reviewed-by: Davis Bickford <dbickford@gitlab.com>
Co-authored-by: Benedek Thaler <thaler@thaler.hu>
This commit is contained in:
Gary Holtz 2023-07-11 05:07:37 +00:00
commit 8aa562b422
2 changed files with 138 additions and 18 deletions

View File

@ -387,7 +387,11 @@ func createRun(opts *CreateOpts) error {
return fmt.Errorf("error getting templates: %w", err)
}
templateNames = append(templateNames, "Open a blank merge request")
const mrWithCommitsTemplate = "Open a merge request with commit messages"
const mrEmptyTemplate = "Open a blank merge request"
templateNames = append(templateNames, mrWithCommitsTemplate)
templateNames = append(templateNames, mrEmptyTemplate)
selectQs := []*survey.Question{
{
@ -402,8 +406,21 @@ func createRun(opts *CreateOpts) error {
if err := prompt.Ask(selectQs, &templateResponse); err != nil {
return fmt.Errorf("could not prompt: %w", err)
}
if templateResponse.Index != len(templateNames) {
templateName = templateNames[templateResponse.Index]
templateName = templateNames[templateResponse.Index]
if templateName == mrWithCommitsTemplate {
// templateContents should be filled from commit messages
commits, err := git.Commits(opts.TargetTrackingBranch, opts.SourceBranch)
if err != nil {
return fmt.Errorf("failed to get commits: %w", err)
}
templateContents, err = mrBody(commits, true)
if err != nil {
return err
}
} else if templateName == mrEmptyTemplate {
// blank merge request was choosen, leave templateContents empty
} else {
templateContents, err = cmdutils.LoadGitLabTemplate(cmdutils.MergeRequestTemplate, templateName)
if err != nil {
return fmt.Errorf("failed to get template contents: %w", err)
@ -617,6 +634,27 @@ func createRun(opts *CreateOpts) error {
return errors.New("expected to cancel, preview in browser, or submit")
}
func mrBody(commits []*git.Commit, fillCommitBody bool) (string, error) {
var body strings.Builder
re := regexp.MustCompile(`\r?\n\n`)
for i := len(commits) - 1; i >= 0; i-- {
// adds 2 spaces for markdown line wrapping
fmt.Fprintf(&body, "- %s \n", commits[i].Title)
if fillCommitBody {
commitBody, err := git.CommitBody(commits[i].Sha)
if err != nil {
return "", fmt.Errorf("failed to get commit message for %s: %w", commits[i].Sha, err)
}
commitBody = re.ReplaceAllString(commitBody, " \n")
fmt.Fprintf(&body, "%s\n", commitBody)
}
}
return body.String(), nil
}
func mrBodyAndTitle(opts *CreateOpts) error {
// TODO: detect forks
commits, err := git.Commits(opts.TargetTrackingBranch, opts.SourceBranch)
@ -640,22 +678,11 @@ func mrBodyAndTitle(opts *CreateOpts) error {
}
if opts.Description == "" {
var body strings.Builder
for i := len(commits) - 1; i >= 0; i-- {
// adds 2 spaces for markdown line wrapping
fmt.Fprintf(&body, "- %s \n", commits[i].Title)
if opts.FillCommitBody {
commitBody, err := git.CommitBody(commits[i].Sha)
if err != nil {
return err
}
re := regexp.MustCompile(`\r?\n\n`)
commitBody = re.ReplaceAllString(commitBody, " \n")
fmt.Fprintf(&body, "%s\n", commitBody)
}
description, err := mrBody(commits, opts.FillCommitBody)
if err != nil {
return err
}
opts.Description = body.String()
opts.Description = description
}
}
return nil

View File

@ -235,6 +235,99 @@ func TestNewCmdCreate_RelatedIssue(t *testing.T) {
assert.Contains(t, output.String(), "https://gitlab.com/OWNER/REPO/-/merge_requests/12")
}
func TestNewCmdCreate_TemplateFromCommitMessages(t *testing.T) {
fakeHTTP := httpmock.New()
defer fakeHTTP.Verify(t)
fakeHTTP.RegisterResponder(http.MethodPost, "/projects/OWNER/REPO/merge_requests",
func(req *http.Request) (*http.Response, error) {
rb, _ := io.ReadAll(req.Body)
assert.Contains(t, string(rb), "- commit msg 1 \\n\\n")
assert.Contains(t, string(rb), "- commit msg 2 \\ncommit body")
resp, _ := httpmock.NewStringResponse(http.StatusCreated, `
{
"id": 1,
"iid": 12,
"project_id": 3,
"title": "...",
"description": "...",
"state": "opened",
"target_branch": "master",
"source_branch": "feat-new-mr",
"web_url": "https://gitlab.com/OWNER/REPO/-/merge_requests/12"
}
`)(req)
return resp, nil
},
)
fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO",
httpmock.NewStringResponse(http.StatusOK, `
{
"id": 1,
"description": null,
"default_branch": "master",
"web_url": "http://gitlab.com/OWNER/REPO",
"name": "OWNER",
"path": "REPO",
"merge_requests_enabled": true,
"path_with_namespace": "OWNER/REPO"
}
`),
)
ask, teardown := prompt.InitAskStubber()
defer teardown()
ask.Stub([]*prompt.QuestionStub{
{
Name: "index",
Value: 0,
},
})
ask.Stub([]*prompt.QuestionStub{
{
Name: "Description",
Default: true,
},
})
cs, csTeardown := test.InitCmdStubber()
defer csTeardown()
cs.Stub("HEAD branch: main\n") // git remote show <name>
cs.Stub("/") // git rev-parse --show-toplevel
// git -c log.ShowSignature=false log --pretty=format:%H,%s --cherry upstream/main...feat-new-mr
cs.Stub(heredoc.Doc(`
deadb00f,commit msg 2
deadbeef,commit msg 1
`))
// git -c log.ShowSignature=false show -s --pretty=format:%b deadbeef
cs.Stub("")
// git -c log.ShowSignature=false show -s --pretty=format:%b deadb00f
cs.Stub("commit body")
cliStr := []string{
"--source-branch", "feat-new-mr",
"--title", "mr-title",
"--yes",
}
cli := strings.Join(cliStr, " ")
t.Log(cli)
output, err := runCommand(fakeHTTP, "feat-new-mr", true, cli)
if err != nil {
if errors.Is(err, cmdutils.SilentError) {
t.Errorf("Unexpected error: %q", output.Stderr())
}
t.Error(err)
return
}
}
func TestNewCmdCreate_RelatedIssueWithTitleAndDescription(t *testing.T) {
fakeHTTP := httpmock.New()
defer fakeHTTP.Verify(t)