From 113acf92459d2e7b4f45963fb2a6101afffe7e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 21 Sep 2022 16:08:26 +0200 Subject: [PATCH] Add the option to generate a README for the created repository (#6264) --- pkg/cmd/repo/create/create.go | 268 ++++++++------------------ pkg/cmd/repo/create/create_test.go | 294 ++++++++++++++++------------- pkg/cmd/repo/create/http.go | 5 +- pkg/cmd/repo/create/http_test.go | 22 +++ 4 files changed, 262 insertions(+), 327 deletions(-) diff --git a/pkg/cmd/repo/create/create.go b/pkg/cmd/repo/create/create.go index 7857d90e872..b1eb2318eca 100644 --- a/pkg/cmd/repo/create/create.go +++ b/pkg/cmd/repo/create/create.go @@ -8,7 +8,6 @@ import ( "path/filepath" "strings" - "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/git" @@ -18,14 +17,20 @@ import ( "github.com/cli/cli/v2/pkg/cmd/repo/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/spf13/cobra" ) +type iprompter interface { + Input(string, string) (string, error) + Select(string, string, []string) (int, error) + Confirm(string, bool) (bool, error) +} + type CreateOptions struct { HttpClient func() (*http.Client, error) Config func() (config.Config, error) IO *iostreams.IOStreams + Prompter iprompter Name string Description string @@ -46,6 +51,7 @@ type CreateOptions struct { DisableWiki bool Interactive bool IncludeAllBranches bool + AddReadme bool } func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command { @@ -53,6 +59,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co IO: f.IOStreams, HttpClient: f.HttpClient, Config: f.Config, + Prompter: f.Prompter, } var enableIssues bool @@ -135,6 +142,10 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co return cmdutil.FlagErrorf(".gitignore and license templates are not added when template is provided") } + if opts.Template != "" && opts.AddReadme { + return cmdutil.FlagErrorf("the `--add-readme` option is not supported with `--template`") + } + if cmd.Flags().Changed("enable-issues") { opts.DisableIssues = !enableIssues } @@ -172,6 +183,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co cmd.Flags().BoolVar(&opts.DisableIssues, "disable-issues", false, "Disable issues in the new repository") cmd.Flags().BoolVar(&opts.DisableWiki, "disable-wiki", false, "Disable wiki in the new repository") cmd.Flags().BoolVar(&opts.IncludeAllBranches, "include-all-branches", false, "Include all branches from template repository") + cmd.Flags().BoolVar(&opts.AddReadme, "add-readme", false, "Add a README file to the new repository") // deprecated flags cmd.Flags().BoolP("confirm", "y", false, "Skip the confirmation prompt") @@ -227,19 +239,14 @@ func createRun(opts *CreateOptions) error { fromScratch := opts.Source == "" if opts.Interactive { - var selectedMode string - modeOptions := []string{ + selected, err := opts.Prompter.Select("What would you like to do?", "", []string{ "Create a new repository on GitHub from scratch", "Push an existing local repository to GitHub", - } - //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter - if err := prompt.SurveyAskOne(&survey.Select{ - Message: "What would you like to do?", - Options: modeOptions, - }, &selectedMode); err != nil { + }) + if err != nil { return err } - fromScratch = selectedMode == modeOptions[0] + fromScratch = selected == 0 } if fromScratch { @@ -264,21 +271,32 @@ func createFromScratch(opts *CreateOptions) error { host, _ := cfg.DefaultHost() if opts.Interactive { - opts.Name, opts.Description, opts.Visibility, err = interactiveRepoInfo("") + opts.Name, opts.Description, opts.Visibility, err = interactiveRepoInfo(opts.Prompter, "") + if err != nil { + return err + } + opts.AddReadme, err = opts.Prompter.Confirm("Would you like to add a README file?", false) if err != nil { return err } - opts.GitIgnoreTemplate, err = interactiveGitIgnore(httpClient, host) + opts.GitIgnoreTemplate, err = interactiveGitIgnore(httpClient, host, opts.Prompter) if err != nil { return err } - opts.LicenseTemplate, err = interactiveLicense(httpClient, host) + opts.LicenseTemplate, err = interactiveLicense(httpClient, host, opts.Prompter) if err != nil { return err } - if err := confirmSubmission(opts.Name, opts.Visibility); err != nil { + targetRepo := shared.NormalizeRepoName(opts.Name) + if idx := strings.IndexRune(targetRepo, '/'); idx > 0 { + targetRepo = targetRepo[0:idx+1] + shared.NormalizeRepoName(targetRepo[idx+1:]) + } + confirmed, err := opts.Prompter.Confirm(fmt.Sprintf(`This will create "%s" as a %s repository on GitHub. Continue?`, targetRepo, strings.ToLower(opts.Visibility)), true) + if err != nil { return err + } else if !confirmed { + return cmdutil.CancelError } } @@ -304,6 +322,7 @@ func createFromScratch(opts *CreateOptions) error { GitIgnoreTemplate: opts.GitIgnoreTemplate, LicenseTemplate: opts.LicenseTemplate, IncludeAllBranches: opts.IncludeAllBranches, + InitReadme: opts.AddReadme, } var templateRepoMainBranch string @@ -350,12 +369,8 @@ func createFromScratch(opts *CreateOptions) error { } if opts.Interactive { - cloneQuestion := &survey.Confirm{ - Message: "Clone the new repository locally?", - Default: true, - } - //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter - err = prompt.SurveyAskOne(cloneQuestion, &opts.Clone) + var err error + opts.Clone, err = opts.Prompter.Confirm("Clone the new repository locally?", true) if err != nil { return err } @@ -405,7 +420,8 @@ func createFromLocal(opts *CreateOptions) error { host, _ := cfg.DefaultHost() if opts.Interactive { - opts.Source, err = interactiveSource() + var err error + opts.Source, err = opts.Prompter.Input("Path to local repository", ".") if err != nil { return err } @@ -448,7 +464,7 @@ func createFromLocal(opts *CreateOptions) error { } if opts.Interactive { - opts.Name, opts.Description, opts.Visibility, err = interactiveRepoInfo(filepath.Base(absPath)) + opts.Name, opts.Description, opts.Visibility, err = interactiveRepoInfo(opts.Prompter, filepath.Base(absPath)) if err != nil { return err } @@ -504,27 +520,15 @@ func createFromLocal(opts *CreateOptions) error { remoteURL := ghrepo.FormatRemoteURL(repo, protocol) if opts.Interactive { - var addRemote bool - remoteQuesiton := &survey.Confirm{ - Message: `Add a remote?`, - Default: true, - } - //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter - err = prompt.SurveyAskOne(remoteQuesiton, &addRemote) + addRemote, err := opts.Prompter.Confirm("Add a remote?", true) if err != nil { return err } - if !addRemote { return nil } - pushQuestion := &survey.Input{ - Message: "What should the new remote be called?", - Default: "origin", - } - //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter - err = prompt.SurveyAskOne(pushQuestion, &baseRemote) + baseRemote, err = opts.Prompter.Input("What should the new remote be called?", "origin") if err != nil { return err } @@ -536,12 +540,8 @@ func createFromLocal(opts *CreateOptions) error { // don't prompt for push if there are no commits if opts.Interactive && committed { - pushQuestion := &survey.Confirm{ - Message: fmt.Sprintf(`Would you like to push commits from the current branch to %q?`, baseRemote), - Default: true, - } - //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter - err = prompt.SurveyAskOne(pushQuestion, &opts.Push) + var err error + opts.Push, err = opts.Prompter.Confirm(fmt.Sprintf("Would you like to push commits from the current branch to %q?", baseRemote), true) if err != nil { return err } @@ -676,177 +676,65 @@ func localInit(io *iostreams.IOStreams, remoteURL, path, checkoutBranch string) return run.PrepareCmd(gitCheckout).Run() } -func interactiveGitIgnore(client *http.Client, hostname string) (string, error) { - var addGitIgnore bool - var addGitIgnoreSurvey []*survey.Question - - addGitIgnoreQuestion := &survey.Question{ - Name: "addGitIgnore", - Prompt: &survey.Confirm{ - Message: "Would you like to add a .gitignore?", - Default: false, - }, +func interactiveGitIgnore(client *http.Client, hostname string, prompter iprompter) (string, error) { + confirmed, err := prompter.Confirm("Would you like to add a .gitignore?", false) + if err != nil { + return "", err + } else if !confirmed { + return "", nil } - addGitIgnoreSurvey = append(addGitIgnoreSurvey, addGitIgnoreQuestion) - //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter - err := prompt.SurveyAsk(addGitIgnoreSurvey, &addGitIgnore) + templates, err := listGitIgnoreTemplates(client, hostname) if err != nil { return "", err } - - var wantedIgnoreTemplate string - - if addGitIgnore { - var gitIg []*survey.Question - - gitIgnoretemplates, err := listGitIgnoreTemplates(client, hostname) - if err != nil { - return "", err - } - gitIgnoreQuestion := &survey.Question{ - Name: "chooseGitIgnore", - Prompt: &survey.Select{ - Message: "Choose a .gitignore template", - Options: gitIgnoretemplates, - }, - } - gitIg = append(gitIg, gitIgnoreQuestion) - //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter - err = prompt.SurveyAsk(gitIg, &wantedIgnoreTemplate) - if err != nil { - return "", err - } - + selected, err := prompter.Select("Choose a .gitignore template", "", templates) + if err != nil { + return "", err } - - return wantedIgnoreTemplate, nil + return templates[selected], nil } -func interactiveLicense(client *http.Client, hostname string) (string, error) { - var addLicense bool - var addLicenseSurvey []*survey.Question - var wantedLicense string - - addLicenseQuestion := &survey.Question{ - Name: "addLicense", - Prompt: &survey.Confirm{ - Message: "Would you like to add a license?", - Default: false, - }, +func interactiveLicense(client *http.Client, hostname string, prompter iprompter) (string, error) { + confirmed, err := prompter.Confirm("Would you like to add a license?", false) + if err != nil { + return "", err + } else if !confirmed { + return "", nil } - addLicenseSurvey = append(addLicenseSurvey, addLicenseQuestion) - //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter - err := prompt.SurveyAsk(addLicenseSurvey, &addLicense) + licenses, err := listLicenseTemplates(client, hostname) if err != nil { return "", err } - - licenseKey := map[string]string{} - - if addLicense { - licenseTemplates, err := listLicenseTemplates(client, hostname) - if err != nil { - return "", err - } - var licenseNames []string - for _, l := range licenseTemplates { - licenseNames = append(licenseNames, l.Name) - licenseKey[l.Name] = l.Key - } - var licenseQs []*survey.Question - - licenseQuestion := &survey.Question{ - Name: "chooseLicense", - Prompt: &survey.Select{ - Message: "Choose a license", - Options: licenseNames, - }, - } - licenseQs = append(licenseQs, licenseQuestion) - //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter - err = prompt.SurveyAsk(licenseQs, &wantedLicense) - if err != nil { - return "", err - } - return licenseKey[wantedLicense], nil + licenseNames := make([]string, 0, len(licenses)) + for _, license := range licenses { + licenseNames = append(licenseNames, license.Name) + } + selected, err := prompter.Select("Choose a license", "", licenseNames) + if err != nil { + return "", err } - return "", nil + return licenses[selected].Key, nil } // name, description, and visibility -func interactiveRepoInfo(defaultName string) (string, string, string, error) { - qs := []*survey.Question{ - { - Name: "repoName", - Prompt: &survey.Input{ - Message: "Repository name", - Default: defaultName, - }, - }, - { - Name: "repoDescription", - Prompt: &survey.Input{Message: "Description"}, - }, - { - Name: "repoVisibility", - Prompt: &survey.Select{ - Message: "Visibility", - Options: []string{"Public", "Private", "Internal"}, - }, - }} - - answer := struct { - RepoName string - RepoDescription string - RepoVisibility string - }{} - - //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter - err := prompt.SurveyAsk(qs, &answer) +func interactiveRepoInfo(prompter iprompter, defaultName string) (string, string, string, error) { + name, err := prompter.Input("Repository name", defaultName) if err != nil { return "", "", "", err } - return answer.RepoName, answer.RepoDescription, strings.ToUpper(answer.RepoVisibility), nil -} - -func interactiveSource() (string, error) { - var sourcePath string - sourcePrompt := &survey.Input{ - Message: "Path to local repository", - Default: "."} - - //nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter - err := prompt.SurveyAskOne(sourcePrompt, &sourcePath) + description, err := prompter.Input("Description", defaultName) if err != nil { - return "", err + return "", "", "", err } - return sourcePath, nil -} -func confirmSubmission(repoWithOwner, visibility string) error { - targetRepo := shared.NormalizeRepoName(repoWithOwner) - if idx := strings.IndexRune(repoWithOwner, '/'); idx > 0 { - targetRepo = repoWithOwner[0:idx+1] + shared.NormalizeRepoName(repoWithOwner[idx+1:]) - } - var answer struct { - ConfirmSubmit bool - } - //nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter - err := prompt.SurveyAsk([]*survey.Question{{ - Name: "confirmSubmit", - Prompt: &survey.Confirm{ - Message: fmt.Sprintf(`This will create "%s" as a %s repository on GitHub. Continue?`, targetRepo, strings.ToLower(visibility)), - Default: true, - }, - }}, &answer) + visibilityOptions := []string{"Public", "Private", "Internal"} + selected, err := prompter.Select("Visibility", "Public", visibilityOptions) if err != nil { - return err - } - if !answer.ConfirmSubmit { - return cmdutil.CancelError + return "", "", "", err } - return nil + + return name, description, strings.ToUpper(visibilityOptions[selected]), nil } diff --git a/pkg/cmd/repo/create/create_test.go b/pkg/cmd/repo/create/create_test.go index 75dd3a0d10c..6c7c515d3a7 100644 --- a/pkg/cmd/repo/create/create_test.go +++ b/pkg/cmd/repo/create/create_test.go @@ -2,15 +2,16 @@ package create import ( "bytes" + "fmt" "net/http" "testing" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/internal/run" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/pkg/prompt" "github.com/google/shlex" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -171,47 +172,62 @@ func TestNewCmdCreate(t *testing.T) { func Test_createRun(t *testing.T) { tests := []struct { - name string - tty bool - opts *CreateOptions - httpStubs func(*httpmock.Registry) - askStubs func(*prompt.AskStubber) - execStubs func(*run.CommandStubber) - wantStdout string - wantErr bool - errMsg string + name string + tty bool + opts *CreateOptions + httpStubs func(*httpmock.Registry) + promptStubs func(*prompter.PrompterMock) + execStubs func(*run.CommandStubber) + wantStdout string + wantErr bool + errMsg string }{ { name: "interactive create from scratch with gitignore and license", opts: &CreateOptions{Interactive: true}, tty: true, wantStdout: "āœ“ Created repository OWNER/REPO on GitHub\n", - askStubs: func(as *prompt.AskStubber) { - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne("Create a new repository on GitHub from scratch") - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "repoName", Value: "REPO"}, - {Name: "repoDescription", Value: "my new repo"}, - {Name: "repoVisibility", Value: "Private"}, - }) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "addGitIgnore", Value: true}}) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "chooseGitIgnore", Value: "Go"}}) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "addLicense", Value: true}}) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "chooseLicense", Value: "GNU Lesser General Public License v3.0"}}) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "confirmSubmit", Value: true}}) - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne(true) //clone locally? + promptStubs: func(p *prompter.PrompterMock) { + p.ConfirmFunc = func(message string, defaultValue bool) (bool, error) { + switch message { + case "Would you like to add a README file?": + return false, nil + case "Would you like to add a .gitignore?": + return true, nil + case "Would you like to add a license?": + return true, nil + case `This will create "REPO" as a private repository on GitHub. Continue?`: + return defaultValue, nil + case "Clone the new repository locally?": + return defaultValue, nil + default: + return false, fmt.Errorf("unexpected confirm prompt: %s", message) + } + } + p.InputFunc = func(message, defaultValue string) (string, error) { + switch message { + case "Repository name": + return "REPO", nil + case "Description": + return "my new repo", nil + default: + return "", fmt.Errorf("unexpected input prompt: %s", message) + } + } + p.SelectFunc = func(message, defaultValue string, options []string) (int, error) { + switch message { + case "What would you like to do?": + return prompter.IndexFor(options, "Create a new repository on GitHub from scratch") + case "Visibility": + return prompter.IndexFor(options, "Private") + case "Choose a license": + return prompter.IndexFor(options, "GNU Lesser General Public License v3.0") + case "Choose a .gitignore template": + return prompter.IndexFor(options, "Go") + default: + return 0, fmt.Errorf("unexpected select prompt: %s", message) + } + } }, httpStubs: func(reg *httpmock.Registry) { reg.Register( @@ -233,24 +249,41 @@ func Test_createRun(t *testing.T) { name: "interactive create from scratch but cancel before submit", opts: &CreateOptions{Interactive: true}, tty: true, - askStubs: func(as *prompt.AskStubber) { - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne("Create a new repository on GitHub from scratch") - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "repoName", Value: "REPO"}, - {Name: "repoDescription", Value: "my new repo"}, - {Name: "repoVisibility", Value: "Private"}, - }) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "addGitIgnore", Value: false}}) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "addLicense", Value: false}}) - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "confirmSubmit", Value: false}}) + promptStubs: func(p *prompter.PrompterMock) { + p.ConfirmFunc = func(message string, defaultValue bool) (bool, error) { + switch message { + case "Would you like to add a README file?": + return false, nil + case "Would you like to add a .gitignore?": + return false, nil + case "Would you like to add a license?": + return false, nil + case `This will create "REPO" as a private repository on GitHub. Continue?`: + return false, nil + default: + return false, fmt.Errorf("unexpected confirm prompt: %s", message) + } + } + p.InputFunc = func(message, defaultValue string) (string, error) { + switch message { + case "Repository name": + return "REPO", nil + case "Description": + return "my new repo", nil + default: + return "", fmt.Errorf("unexpected input prompt: %s", message) + } + } + p.SelectFunc = func(message, defaultValue string, options []string) (int, error) { + switch message { + case "What would you like to do?": + return prompter.IndexFor(options, "Create a new repository on GitHub from scratch") + case "Visibility": + return prompter.IndexFor(options, "Private") + default: + return 0, fmt.Errorf("unexpected select prompt: %s", message) + } + } }, wantStdout: "", wantErr: true, @@ -260,19 +293,37 @@ func Test_createRun(t *testing.T) { name: "interactive with existing repository public", opts: &CreateOptions{Interactive: true}, tty: true, - askStubs: func(as *prompt.AskStubber) { - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne("Push an existing local repository to GitHub") - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne(".") - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "repoName", Value: "REPO"}, - {Name: "repoDescription", Value: "my new repo"}, - {Name: "repoVisibility", Value: "Private"}, - }) - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne(false) + promptStubs: func(p *prompter.PrompterMock) { + p.ConfirmFunc = func(message string, defaultValue bool) (bool, error) { + switch message { + case "Add a remote?": + return false, nil + default: + return false, fmt.Errorf("unexpected confirm prompt: %s", message) + } + } + p.InputFunc = func(message, defaultValue string) (string, error) { + switch message { + case "Path to local repository": + return defaultValue, nil + case "Repository name": + return "REPO", nil + case "Description": + return "my new repo", nil + default: + return "", fmt.Errorf("unexpected input prompt: %s", message) + } + } + p.SelectFunc = func(message, defaultValue string, options []string) (int, error) { + switch message { + case "What would you like to do?": + return prompter.IndexFor(options, "Push an existing local repository to GitHub") + case "Visibility": + return prompter.IndexFor(options, "Private") + default: + return 0, fmt.Errorf("unexpected select prompt: %s", message) + } + } }, httpStubs: func(reg *httpmock.Registry) { reg.Register( @@ -298,72 +349,44 @@ func Test_createRun(t *testing.T) { wantStdout: "āœ“ Created repository OWNER/REPO on GitHub\n", }, { - name: "interactive with existing repository public add remote", + name: "interactive with existing repository public add remote and push", opts: &CreateOptions{Interactive: true}, tty: true, - askStubs: func(as *prompt.AskStubber) { - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne("Push an existing local repository to GitHub") - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne(".") - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "repoName", Value: "REPO"}, - {Name: "repoDescription", Value: "my new repo"}, - {Name: "repoVisibility", Value: "Private"}, - }) - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne(true) //ask for adding a remote - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne("origin") //ask for remote name - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne(false) //ask to push to remote - }, - httpStubs: func(reg *httpmock.Registry) { - reg.Register( - httpmock.GraphQL(`mutation RepositoryCreate\b`), - httpmock.StringResponse(` - { - "data": { - "createRepository": { - "repository": { - "id": "REPOID", - "name": "REPO", - "owner": {"login":"OWNER"}, - "url": "https://github.com/OWNER/REPO" - } - } - } - }`)) - }, - execStubs: func(cs *run.CommandStubber) { - cs.Register(`git -C . rev-parse --git-dir`, 0, ".git") - cs.Register(`git -C . rev-parse HEAD`, 0, "commithash") - cs.Register(`git -C . remote add origin https://github.com/OWNER/REPO`, 0, "") - }, - wantStdout: "āœ“ Created repository OWNER/REPO on GitHub\nāœ“ Added remote https://github.com/OWNER/REPO.git\n", - }, - { - name: "interactive with existing repository public, add remote, and push", - opts: &CreateOptions{Interactive: true}, - tty: true, - askStubs: func(as *prompt.AskStubber) { - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne("Push an existing local repository to GitHub") - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne(".") - //nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt - as.Stub([]*prompt.QuestionStub{ - {Name: "repoName", Value: "REPO"}, - {Name: "repoDescription", Value: "my new repo"}, - {Name: "repoVisibility", Value: "Private"}, - }) - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne(true) //ask for adding a remote - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne("origin") //ask for remote name - //nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt - as.StubOne(true) //ask to push to remote + promptStubs: func(p *prompter.PrompterMock) { + p.ConfirmFunc = func(message string, defaultValue bool) (bool, error) { + switch message { + case "Add a remote?": + return true, nil + case `Would you like to push commits from the current branch to "origin"?`: + return true, nil + default: + return false, fmt.Errorf("unexpected confirm prompt: %s", message) + } + } + p.InputFunc = func(message, defaultValue string) (string, error) { + switch message { + case "Path to local repository": + return defaultValue, nil + case "Repository name": + return "REPO", nil + case "Description": + return "my new repo", nil + case "What should the new remote be called?": + return defaultValue, nil + default: + return "", fmt.Errorf("unexpected input prompt: %s", message) + } + } + p.SelectFunc = func(message, defaultValue string, options []string) (int, error) { + switch message { + case "What would you like to do?": + return prompter.IndexFor(options, "Push an existing local repository to GitHub") + case "Visibility": + return prompter.IndexFor(options, "Private") + default: + return 0, fmt.Errorf("unexpected select prompt: %s", message) + } + } }, httpStubs: func(reg *httpmock.Registry) { reg.Register( @@ -452,11 +475,10 @@ func Test_createRun(t *testing.T) { }, } for _, tt := range tests { - //nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber - q, teardown := prompt.InitAskStubber() - defer teardown() - if tt.askStubs != nil { - tt.askStubs(q) + prompterMock := &prompter.PrompterMock{} + tt.opts.Prompter = prompterMock + if tt.promptStubs != nil { + tt.promptStubs(prompterMock) } reg := &httpmock.Registry{} diff --git a/pkg/cmd/repo/create/http.go b/pkg/cmd/repo/create/http.go index 6031d28d5c7..a5cc0ae28f6 100644 --- a/pkg/cmd/repo/create/http.go +++ b/pkg/cmd/repo/create/http.go @@ -24,6 +24,7 @@ type repoCreateInput struct { GitIgnoreTemplate string LicenseTemplate string IncludeAllBranches bool + InitReadme bool } // createRepositoryInputV3 is the payload for the repo create REST API @@ -38,6 +39,7 @@ type createRepositoryInputV3 struct { HasWikiEnabled bool `json:"has_wiki"` GitIgnoreTemplate string `json:"gitignore_template,omitempty"` LicenseTemplate string `json:"license_template,omitempty"` + InitReadme bool `json:"auto_init,omitempty"` } // createRepositoryInput is the payload for the repo create GraphQL mutation @@ -134,7 +136,7 @@ func repoCreate(client *http.Client, hostname string, input repoCreateInput) (*a return api.InitRepoHostname(&response.CloneTemplateRepository.Repository, hostname), nil } - if input.GitIgnoreTemplate != "" || input.LicenseTemplate != "" { + if input.GitIgnoreTemplate != "" || input.LicenseTemplate != "" || input.InitReadme { inputv3 := createRepositoryInputV3{ Name: input.Name, HomepageURL: input.HomepageURL, @@ -145,6 +147,7 @@ func repoCreate(client *http.Client, hostname string, input repoCreateInput) (*a HasWikiEnabled: input.HasWikiEnabled, GitIgnoreTemplate: input.GitIgnoreTemplate, LicenseTemplate: input.LicenseTemplate, + InitReadme: input.InitReadme, } path := "user/repos" diff --git a/pkg/cmd/repo/create/http_test.go b/pkg/cmd/repo/create/http_test.go index 8b5b2362aec..0589017389f 100644 --- a/pkg/cmd/repo/create/http_test.go +++ b/pkg/cmd/repo/create/http_test.go @@ -304,6 +304,28 @@ func Test_repoCreate(t *testing.T) { }, wantRepo: "https://github.com/snacks-inc/crisps", }, + { + name: "create with README", + hostname: "github.com", + input: repoCreateInput{ + Name: "crisps", + InitReadme: true, + }, + stubs: func(t *testing.T, r *httpmock.Registry) { + r.Register( + httpmock.REST("POST", "user/repos"), + httpmock.RESTPayload(201, `{"name":"crisps", "owner":{"login": "snacks-inc"}, "html_url":"the://URL"}`, func(payload map[string]interface{}) { + assert.Equal(t, map[string]interface{}{ + "name": "crisps", + "private": false, + "has_issues": false, + "has_wiki": false, + "auto_init": true, + }, payload) + })) + }, + wantRepo: "https://github.com/snacks-inc/crisps", + }, { name: "create with license and gitignore on Enterprise", hostname: "example.com",