From 21e6d959827926c63d23bc4f8eb5ad4e927410e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 14 Dec 2021 17:49:43 +0100 Subject: [PATCH 1/4] Fix `repo create` printing created URL in no-TTY mode --- pkg/cmd/repo/create/create.go | 4 ++ pkg/cmd/repo/create/create_test.go | 63 +++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/repo/create/create.go b/pkg/cmd/repo/create/create.go index c8b79bc21..980b6afd6 100644 --- a/pkg/cmd/repo/create/create.go +++ b/pkg/cmd/repo/create/create.go @@ -356,6 +356,8 @@ func createFromScratch(opts *CreateOptions) error { "%s Created repository %s on GitHub\n", cs.SuccessIconWithColor(cs.Green), ghrepo.FullName(repo)) + } else { + fmt.Fprintln(opts.IO.Out, repo.URL) } if opts.Interactive { @@ -503,6 +505,8 @@ func createFromLocal(opts *CreateOptions) error { "%s Created repository %s on GitHub\n", cs.SuccessIconWithColor(cs.Green), ghrepo.FullName(repo)) + } else { + fmt.Fprintln(stdout, repo.URL) } protocol, err := cfg.Get(repo.RepoHost(), "git_protocol") diff --git a/pkg/cmd/repo/create/create_test.go b/pkg/cmd/repo/create/create_test.go index 162bf7741..b7dfc896e 100644 --- a/pkg/cmd/repo/create/create_test.go +++ b/pkg/cmd/repo/create/create_test.go @@ -323,6 +323,66 @@ func Test_createRun(t *testing.T) { }, wantStdout: "āœ“ Created repository OWNER/REPO on GitHub\nāœ“ Added remote https://github.com/OWNER/REPO.git\nāœ“ Pushed commits to https://github.com/OWNER/REPO.git\n", }, + { + name: "noninteractive create from scratch", + opts: &CreateOptions{ + Interactive: false, + Name: "REPO", + Visibility: "PRIVATE", + }, + tty: false, + 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" + } + } + } + }`)) + }, + wantStdout: "https://github.com/OWNER/REPO\n", + }, + { + name: "noninteractive create from source", + opts: &CreateOptions{ + Interactive: false, + Source: ".", + Name: "REPO", + Visibility: "PRIVATE", + }, + tty: false, + 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: "https://github.com/OWNER/REPO\n", + }, } for _, tt := range tests { q, teardown := prompt.InitAskStubber() @@ -348,7 +408,7 @@ func Test_createRun(t *testing.T) { tt.execStubs(cs) } - io, _, stdout, _ := iostreams.Test() + io, _, stdout, stderr := iostreams.Test() io.SetStdinTTY(tt.tty) io.SetStdoutTTY(tt.tty) tt.opts.IO = io @@ -363,6 +423,7 @@ func Test_createRun(t *testing.T) { } assert.NoError(t, err) assert.Equal(t, tt.wantStdout, stdout.String()) + assert.Equal(t, "", stderr.String()) }) } } From 3e40780074ec9ed8865894910983778af8889bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 14 Dec 2021 18:05:42 +0100 Subject: [PATCH 2/4] :nail_care: repo create cleanups --- pkg/cmd/repo/create/create.go | 61 ++++++++++++++--------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/pkg/cmd/repo/create/create.go b/pkg/cmd/repo/create/create.go index 980b6afd6..300718a36 100644 --- a/pkg/cmd/repo/create/create.go +++ b/pkg/cmd/repo/create/create.go @@ -45,10 +45,6 @@ type CreateOptions struct { DisableIssues bool DisableWiki bool Interactive bool - - ConfirmSubmit bool - EnableIssues bool - EnableWiki bool } func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command { @@ -58,6 +54,9 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co Config: f.Config, } + var enableIssues bool + var enableWiki bool + cmd := &cobra.Command{ Use: "create []", Short: "Create a new repository", @@ -135,10 +134,10 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co } if cmd.Flags().Changed("enable-issues") { - opts.DisableIssues = !opts.EnableIssues + opts.DisableIssues = !enableIssues } if cmd.Flags().Changed("enable-wiki") { - opts.DisableWiki = !opts.EnableWiki + opts.DisableWiki = !enableWiki } if opts.Template != "" && (opts.Homepage != "" || opts.Team != "" || opts.DisableIssues || opts.DisableWiki) { return cmdutil.FlagErrorf("the `--template` option is not supported with `--homepage`, `--team`, `--disable-issues`, or `--disable-wiki`") @@ -168,9 +167,9 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co cmd.Flags().BoolVar(&opts.DisableWiki, "disable-wiki", false, "Disable wiki in the new repository") // deprecated flags - cmd.Flags().BoolVarP(&opts.ConfirmSubmit, "confirm", "y", false, "Skip the confirmation prompt") - cmd.Flags().BoolVar(&opts.EnableIssues, "enable-issues", true, "Enable issues in the new repository") - cmd.Flags().BoolVar(&opts.EnableWiki, "enable-wiki", true, "Enable wiki in the new repository") + cmd.Flags().BoolP("confirm", "y", false, "Skip the confirmation prompt") + cmd.Flags().BoolVar(&enableIssues, "enable-issues", true, "Enable issues in the new repository") + cmd.Flags().BoolVar(&enableWiki, "enable-wiki", true, "Enable wiki in the new repository") _ = cmd.Flags().MarkDeprecated("confirm", "Pass any argument to skip confirmation prompt") _ = cmd.Flags().MarkDeprecated("enable-issues", "Disable issues with `--disable-issues`") @@ -224,31 +223,27 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co } func createRun(opts *CreateOptions) error { - var fromScratch bool - modeOptions := []string{ - "Create a new repository on GitHub from scratch", - "Push an existing local repository to GitHub"} + fromScratch := opts.Source == "" if opts.Interactive { - var createMode string - createModePrompt := &survey.Select{ + var selectedMode string + modeOptions := []string{ + "Create a new repository on GitHub from scratch", + "Push an existing local repository to GitHub", + } + if err := prompt.SurveyAskOne(&survey.Select{ Message: "What would you like to do?", Options: modeOptions, - } - err := prompt.SurveyAskOne(createModePrompt, &createMode) - if err != nil { + }, &selectedMode); err != nil { return err } - fromScratch = createMode == modeOptions[0] - } else { - fromScratch = opts.Source == "" + fromScratch = selectedMode == modeOptions[0] } if fromScratch { return createFromScratch(opts) - } else { - return createFromLocal(opts) } + return createFromLocal(opts) } // create new repo on remote host @@ -831,31 +826,23 @@ func interactiveSource() (string, error) { } func confirmSubmission(repoName, repoOwner, visibility string) (bool, error) { - qs := []*survey.Question{} targetRepo := normalizeRepoName(repoName) if repoOwner != "" { targetRepo = fmt.Sprintf("%s/%s", repoOwner, targetRepo) } - promptString := fmt.Sprintf(`This will create "%s" as a %s repository on GitHub. Continue?`, targetRepo, strings.ToLower(visibility)) - - confirmSubmitQuestion := &survey.Question{ + var answer struct { + ConfirmSubmit bool + } + err := prompt.SurveyAsk([]*survey.Question{{ Name: "confirmSubmit", Prompt: &survey.Confirm{ - Message: promptString, + Message: fmt.Sprintf(`This will create "%s" as a %s repository on GitHub. Continue?`, targetRepo, strings.ToLower(visibility)), Default: true, }, - } - qs = append(qs, confirmSubmitQuestion) - - answer := struct { - ConfirmSubmit bool - }{} - - err := prompt.SurveyAsk(qs, &answer) + }}, &answer) if err != nil { return false, err } - return answer.ConfirmSubmit, nil } From c90fc18b0c25e75c37282926f713657a4723ba36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 14 Dec 2021 18:18:08 +0100 Subject: [PATCH 3/4] Avoid creating repo when cancelling from interactive mode --- pkg/cmd/repo/create/create.go | 10 +++++----- pkg/cmd/repo/create/create_test.go | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/pkg/cmd/repo/create/create.go b/pkg/cmd/repo/create/create.go index 300718a36..1b840ca10 100644 --- a/pkg/cmd/repo/create/create.go +++ b/pkg/cmd/repo/create/create.go @@ -329,11 +329,6 @@ func createFromScratch(opts *CreateOptions) error { templateRepoMainBranch = repo.DefaultBranchRef.Name } - repo, err := repoCreate(httpClient, repoToCreate.RepoHost(), input) - if err != nil { - return err - } - if opts.Interactive { doCreate, err := confirmSubmission(opts.Name, repoToCreate.RepoOwner(), opts.Visibility) if err != nil { @@ -344,6 +339,11 @@ func createFromScratch(opts *CreateOptions) error { } } + repo, err := repoCreate(httpClient, repoToCreate.RepoHost(), input) + if err != nil { + return err + } + cs := opts.IO.ColorScheme() isTTY := opts.IO.IsStdoutTTY() if isTTY { diff --git a/pkg/cmd/repo/create/create_test.go b/pkg/cmd/repo/create/create_test.go index b7dfc896e..9048845fd 100644 --- a/pkg/cmd/repo/create/create_test.go +++ b/pkg/cmd/repo/create/create_test.go @@ -205,6 +205,28 @@ func Test_createRun(t *testing.T) { cs.Register(`git clone https://github.com/OWNER/REPO.git`, 0, "") }, }, + { + name: "interactive create from scratch but cancel before submit", + opts: &CreateOptions{Interactive: true}, + tty: true, + askStubs: func(as *prompt.AskStubber) { + as.StubOne("Create a new repository on GitHub from scratch") + as.Stub([]*prompt.QuestionStub{ + {Name: "repoName", Value: "REPO"}, + {Name: "repoDescription", Value: "my new repo"}, + {Name: "repoVisibility", Value: "PRIVATE"}, + }) + as.Stub([]*prompt.QuestionStub{ + {Name: "addGitIgnore", Value: false}}) + as.Stub([]*prompt.QuestionStub{ + {Name: "addLicense", Value: false}}) + as.Stub([]*prompt.QuestionStub{ + {Name: "confirmSubmit", Value: false}}) + }, + wantStdout: "", + wantErr: true, + errMsg: "CancelError", + }, { name: "interactive with existing repository public", opts: &CreateOptions{Interactive: true}, From 5cd977e3283041493a0031ec05cfae9eb82e27bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 14 Dec 2021 18:38:46 +0100 Subject: [PATCH 4/4] :nail_care: normalize prompt style for text inputs --- pkg/cmd/repo/create/create.go | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/pkg/cmd/repo/create/create.go b/pkg/cmd/repo/create/create.go index 1b840ca10..b8d8c764e 100644 --- a/pkg/cmd/repo/create/create.go +++ b/pkg/cmd/repo/create/create.go @@ -277,6 +277,10 @@ func createFromScratch(opts *CreateOptions) error { if err != nil { return err } + + if err := confirmSubmission(opts.Name, opts.Visibility); err != nil { + return err + } } if strings.Contains(opts.Name, "/") { @@ -329,16 +333,6 @@ func createFromScratch(opts *CreateOptions) error { templateRepoMainBranch = repo.DefaultBranchRef.Name } - if opts.Interactive { - doCreate, err := confirmSubmission(opts.Name, repoToCreate.RepoOwner(), opts.Visibility) - if err != nil { - return err - } - if !doCreate { - return cmdutil.CancelError - } - } - repo, err := repoCreate(httpClient, repoToCreate.RepoHost(), input) if err != nil { return err @@ -782,13 +776,13 @@ func interactiveRepoInfo(defaultName string) (string, string, string, error) { { Name: "repoName", Prompt: &survey.Input{ - Message: "Repository Name: ", + Message: "Repository name", Default: defaultName, }, }, { Name: "repoDescription", - Prompt: &survey.Input{Message: "Description: "}, + Prompt: &survey.Input{Message: "Description"}, }, { Name: "repoVisibility", @@ -815,7 +809,7 @@ func interactiveRepoInfo(defaultName string) (string, string, string, error) { func interactiveSource() (string, error) { var sourcePath string sourcePrompt := &survey.Input{ - Message: "Path to local repository: ", + Message: "Path to local repository", Default: "."} err := prompt.SurveyAskOne(sourcePrompt, &sourcePath) @@ -825,10 +819,10 @@ func interactiveSource() (string, error) { return sourcePath, nil } -func confirmSubmission(repoName, repoOwner, visibility string) (bool, error) { - targetRepo := normalizeRepoName(repoName) - if repoOwner != "" { - targetRepo = fmt.Sprintf("%s/%s", repoOwner, targetRepo) +func confirmSubmission(repoWithOwner, visibility string) error { + targetRepo := normalizeRepoName(repoWithOwner) + if idx := strings.IndexRune(repoWithOwner, '/'); idx > 0 { + targetRepo = repoWithOwner[0:idx+1] + normalizeRepoName(repoWithOwner[idx+1:]) } var answer struct { ConfirmSubmit bool @@ -841,9 +835,12 @@ func confirmSubmission(repoName, repoOwner, visibility string) (bool, error) { }, }}, &answer) if err != nil { - return false, err + return err } - return answer.ConfirmSubmit, nil + if !answer.ConfirmSubmit { + return cmdutil.CancelError + } + return nil } // normalizeRepoName takes in the repo name the user inputted and normalizes it using the same logic as GitHub (GitHub.com/new)