diff --git a/pkg/cmd/repo/delete/delete.go b/pkg/cmd/repo/delete/delete.go index 8af75b203..04803c7a1 100644 --- a/pkg/cmd/repo/delete/delete.go +++ b/pkg/cmd/repo/delete/delete.go @@ -1,10 +1,18 @@ package delete import ( + "errors" + "fmt" "net/http" + "strings" + "github.com/cli/cli/v2/api" + "github.com/cli/cli/v2/internal/ghinstance" + "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmdutil" + "github.com/cli/cli/v2/pkg/prompt" + "github.com/AlecAivazis/survey/v2" "github.com/cli/cli/v2/pkg/iostreams" "github.com/spf13/cobra" ) @@ -27,7 +35,8 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co Short: "Delete a repository", Long: `Delete a GitHub repository. - Ensure that you have authorized the \"delete_repo\" scope: gh auth refresh -h github.com -s delete_repo"`, +Deletion requires authorization with the "delete_repo" scope. +To authorize, run "gh auth refresh -h github.com -s delete_repo"`, Args: cmdutil.ExactArgs(1, "cannot delete: repository argument required"), RunE: func(cmd *cobra.Command, args []string) error { opts.RepoArg = args[0] @@ -38,11 +47,68 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co }, } - cmd.Flags().BoolVar(&opts.Confirmed, "yes", false, "Confirm deletion without prompting") + cmd.Flags().BoolVar(&opts.Confirmed, "yes", false, "confirm deletion without prompting") return cmd } func deleteRun(opts *DeleteOptions) error { + httpClient, err := opts.HttpClient() + if err != nil { + return err + } + apiClient := api.NewClientFromHTTP(httpClient) + + deleteURL := opts.RepoArg + var toDelete ghrepo.Interface + + if !strings.Contains(deleteURL, "/") { + currentUser, err := api.CurrentLoginName(apiClient, ghinstance.Default()) + if err != nil { + return err + } + deleteURL = currentUser + "/" + deleteURL + } + toDelete, err = ghrepo.FromFullName(deleteURL) + if err != nil { + return fmt.Errorf("argument error: %w", err) + } + + fullName := ghrepo.FullName(toDelete) + + doPrompt := opts.IO.CanPrompt() + if !opts.Confirmed && !doPrompt { + return errors.New("could not prompt: confirmation with prompt or --yes flag required") + } + + if !opts.Confirmed && doPrompt { + var valid string + err := prompt.SurveyAskOne( + &survey.Input{Message: fmt.Sprintf("Type %s to confirm deletion:", fullName)}, + &valid, + survey.WithValidator( + func(val interface{}) error { + if str := val.(string); str != fullName { + return fmt.Errorf("You entered %s", str) + } + return nil + })) + if err != nil { + return fmt.Errorf("could not prompt: %w", err) + } + } + + err = deleteRepo(httpClient, toDelete) + if err != nil { + return fmt.Errorf("API call failed: %w", err) + } + + if opts.IO.IsStdoutTTY() { + cs := opts.IO.ColorScheme() + fmt.Fprintf(opts.IO.Out, + "%s Deleted repository %s\n", + cs.SuccessIcon(), + fullName) + } return nil } diff --git a/pkg/cmd/repo/delete/http.go b/pkg/cmd/repo/delete/http.go index 4e04b5c52..2875c54bc 100644 --- a/pkg/cmd/repo/delete/http.go +++ b/pkg/cmd/repo/delete/http.go @@ -1,9 +1,39 @@ package delete import ( + "fmt" "net/http" + + "github.com/cli/cli/v2/api" + "github.com/cli/cli/v2/internal/ghinstance" + "github.com/cli/cli/v2/internal/ghrepo" ) -func deleteRepo(client *http.Client) error { +func deleteRepo(client *http.Client, repo ghrepo.Interface) error { + url := fmt.Sprintf("%srepos/%s", + ghinstance.RESTPrefix(repo.RepoHost()), + ghrepo.FullName(repo)) + request, err := http.NewRequest("DELETE", url, nil) + request.Header.Set("Accept", "application/vnd.github.v3+json") + if err != nil { + return err + } + + resp, err := client.Do(request) + if err != nil { + return err + } + defer resp.Body.Close() + + err = api.HandleHTTPError(resp) + if resp.StatusCode == 403 { + return fmt.Errorf(`%w + +Deletion requires authorization with the "delete_repo" scope. To authorize, run "gh auth refresh -s delete_repo"`, err) + } else if resp.StatusCode > 204 { + return err + } + + return nil } diff --git a/pkg/cmd/repo/repo.go b/pkg/cmd/repo/repo.go index 252c322e9..a061f186c 100644 --- a/pkg/cmd/repo/repo.go +++ b/pkg/cmd/repo/repo.go @@ -5,6 +5,7 @@ import ( repoCloneCmd "github.com/cli/cli/v2/pkg/cmd/repo/clone" repoCreateCmd "github.com/cli/cli/v2/pkg/cmd/repo/create" creditsCmd "github.com/cli/cli/v2/pkg/cmd/repo/credits" + repoDeleteCmd "github.com/cli/cli/v2/pkg/cmd/repo/delete" repoForkCmd "github.com/cli/cli/v2/pkg/cmd/repo/fork" gardenCmd "github.com/cli/cli/v2/pkg/cmd/repo/garden" repoListCmd "github.com/cli/cli/v2/pkg/cmd/repo/list" @@ -42,6 +43,7 @@ func NewCmdRepo(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(repoSyncCmd.NewCmdSync(f, nil)) cmd.AddCommand(creditsCmd.NewCmdRepoCredits(f, nil)) cmd.AddCommand(gardenCmd.NewCmdGarden(f, nil)) + cmd.AddCommand(repoDeleteCmd.NewCmdDelete(f, nil)) return cmd }