Merge pull request #3023 from cli/cancel-error-status
Issue/pr create: exit with nonzero status code when "Cancel" was chosen
This commit is contained in:
commit
e96d974331
11 changed files with 66 additions and 24 deletions
|
|
@ -13,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
surveyCore "github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/build"
|
||||
"github.com/cli/cli/internal/config"
|
||||
|
|
@ -32,7 +33,21 @@ import (
|
|||
|
||||
var updaterEnabled = ""
|
||||
|
||||
type exitCode int
|
||||
|
||||
const (
|
||||
exitOK exitCode = 0
|
||||
exitError exitCode = 1
|
||||
exitCancel exitCode = 2
|
||||
exitAuth exitCode = 4
|
||||
)
|
||||
|
||||
func main() {
|
||||
code := mainRun()
|
||||
os.Exit(int(code))
|
||||
}
|
||||
|
||||
func mainRun() exitCode {
|
||||
buildDate := build.Date
|
||||
buildVersion := build.Version
|
||||
|
||||
|
|
@ -78,7 +93,7 @@ func main() {
|
|||
cfg, err := cmdFactory.Config()
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "failed to read configuration: %s\n", err)
|
||||
os.Exit(2)
|
||||
return exitError
|
||||
}
|
||||
|
||||
if prompt, _ := cfg.Get("", "prompt"); prompt == "disabled" {
|
||||
|
|
@ -102,7 +117,7 @@ func main() {
|
|||
expandedArgs, isShell, err = expand.ExpandAlias(cfg, os.Args, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "failed to process aliases: %s\n", err)
|
||||
os.Exit(2)
|
||||
return exitError
|
||||
}
|
||||
|
||||
if hasDebug {
|
||||
|
|
@ -113,7 +128,7 @@ func main() {
|
|||
exe, err := safeexec.LookPath(expandedArgs[0])
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "failed to run external command: %s", err)
|
||||
os.Exit(3)
|
||||
return exitError
|
||||
}
|
||||
|
||||
externalCmd := exec.Command(exe, expandedArgs[1:]...)
|
||||
|
|
@ -125,14 +140,14 @@ func main() {
|
|||
err = preparedCmd.Run()
|
||||
if err != nil {
|
||||
if ee, ok := err.(*exec.ExitError); ok {
|
||||
os.Exit(ee.ExitCode())
|
||||
return exitCode(ee.ExitCode())
|
||||
}
|
||||
|
||||
fmt.Fprintf(stderr, "failed to run external command: %s", err)
|
||||
os.Exit(3)
|
||||
return exitError
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
return exitOK
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,23 +157,33 @@ func main() {
|
|||
fmt.Fprintln(stderr, cs.Bold("Welcome to GitHub CLI!"))
|
||||
fmt.Fprintln(stderr)
|
||||
fmt.Fprintln(stderr, "To authenticate, please run `gh auth login`.")
|
||||
os.Exit(4)
|
||||
return exitAuth
|
||||
}
|
||||
|
||||
rootCmd.SetArgs(expandedArgs)
|
||||
|
||||
if cmd, err := rootCmd.ExecuteC(); err != nil {
|
||||
if err == cmdutil.SilentError {
|
||||
return exitError
|
||||
} else if cmdutil.IsUserCancellation(err) {
|
||||
if errors.Is(err, terminal.InterruptErr) {
|
||||
// ensure the next shell prompt will start on its own line
|
||||
fmt.Fprint(stderr, "\n")
|
||||
}
|
||||
return exitCancel
|
||||
}
|
||||
|
||||
printError(stderr, err, cmd, hasDebug)
|
||||
|
||||
var httpErr api.HTTPError
|
||||
if errors.As(err, &httpErr) && httpErr.StatusCode == 401 {
|
||||
fmt.Println("hint: try authenticating with `gh auth login`")
|
||||
fmt.Fprintln(stderr, "hint: try authenticating with `gh auth login`")
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
return exitError
|
||||
}
|
||||
if root.HasFailed() {
|
||||
os.Exit(1)
|
||||
return exitError
|
||||
}
|
||||
|
||||
newRelease := <-updateMessageChan
|
||||
|
|
@ -169,7 +194,7 @@ func main() {
|
|||
}
|
||||
if isHomebrew && isRecentRelease(newRelease.PublishedAt) {
|
||||
// do not notify Homebrew users before the version bump had a chance to get merged into homebrew-core
|
||||
return
|
||||
return exitOK
|
||||
}
|
||||
fmt.Fprintf(stderr, "\n\n%s %s → %s\n",
|
||||
ansi.Color("A new release of gh is available:", "yellow"),
|
||||
|
|
@ -181,13 +206,11 @@ func main() {
|
|||
fmt.Fprintf(stderr, "%s\n\n",
|
||||
ansi.Color(newRelease.URL, "yellow"))
|
||||
}
|
||||
|
||||
return exitOK
|
||||
}
|
||||
|
||||
func printError(out io.Writer, err error, cmd *cobra.Command, debug bool) {
|
||||
if err == cmdutil.SilentError {
|
||||
return
|
||||
}
|
||||
|
||||
var dnsError *net.DNSError
|
||||
if errors.As(err, &dnsError) {
|
||||
fmt.Fprintf(out, "error connecting to %s\n", dnsError.Name)
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ func editRun(opts *EditOptions) error {
|
|||
case "Submit":
|
||||
stop = true
|
||||
case "Cancel":
|
||||
return cmdutil.SilentError
|
||||
return cmdutil.CancelError
|
||||
}
|
||||
|
||||
if stop {
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ func Test_editRun(t *testing.T) {
|
|||
as.StubOne("unix.md")
|
||||
as.StubOne("Cancel")
|
||||
},
|
||||
wantErr: "SilentError",
|
||||
wantErr: "CancelError",
|
||||
gist: &shared.Gist{
|
||||
ID: "1234",
|
||||
Files: map[string]*shared.GistFile{
|
||||
|
|
|
|||
|
|
@ -260,6 +260,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
|
||||
if action == prShared.CancelAction {
|
||||
fmt.Fprintln(opts.IO.ErrOut, "Discarding.")
|
||||
err = cmdutil.CancelError
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -311,7 +311,8 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
|
||||
if action == shared.CancelAction {
|
||||
fmt.Fprintln(opts.IO.ErrOut, "Discarding.")
|
||||
return nil
|
||||
err = cmdutil.CancelError
|
||||
return
|
||||
}
|
||||
|
||||
err = handlePush(*opts, *ctx)
|
||||
|
|
@ -553,7 +554,7 @@ func NewCreateContext(opts *CreateOptions) (*CreateContext, error) {
|
|||
} else if pushOptions[selectedOption] == "Skip pushing the branch" {
|
||||
isPushEnabled = false
|
||||
} else if pushOptions[selectedOption] == "Cancel" {
|
||||
return nil, cmdutil.SilentError
|
||||
return nil, cmdutil.CancelError
|
||||
} else {
|
||||
// "Create a fork of ..."
|
||||
if baseRepo.IsPrivate {
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ func mergeRun(opts *MergeOptions) error {
|
|||
}
|
||||
if action == shared.CancelAction {
|
||||
fmt.Fprintln(opts.IO.ErrOut, "Cancelled.")
|
||||
return cmdutil.SilentError
|
||||
return cmdutil.CancelError
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -895,7 +895,7 @@ func TestPRMerge_interactiveCancelled(t *testing.T) {
|
|||
as.StubOne("Cancel") // Confirm submit survey
|
||||
|
||||
output, err := runCommand(http, "blueberries", true, "")
|
||||
if !errors.Is(err, cmdutil.SilentError) {
|
||||
if !errors.Is(err, cmdutil.CancelError) {
|
||||
t.Fatalf("got error %v", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
)
|
||||
|
||||
|
|
@ -18,6 +19,11 @@ func PreserveInput(io *iostreams.IOStreams, state *IssueMetadataState, createErr
|
|||
return
|
||||
}
|
||||
|
||||
if cmdutil.IsUserCancellation(*createErr) {
|
||||
// these errors are user-initiated cancellations
|
||||
return
|
||||
}
|
||||
|
||||
out := io.ErrOut
|
||||
|
||||
// this extra newline guards against appending to the end of a survey line
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ func createRun(opts *CreateOptions) error {
|
|||
case "Save as draft":
|
||||
opts.Draft = true
|
||||
case "Cancel":
|
||||
return cmdutil.SilentError
|
||||
return cmdutil.CancelError
|
||||
default:
|
||||
return fmt.Errorf("invalid action: %v", opts.SubmitAction)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ func deleteRun(opts *DeleteOptions) error {
|
|||
}
|
||||
|
||||
if !confirmed {
|
||||
return cmdutil.SilentError
|
||||
return cmdutil.CancelError
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package cmdutil
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
)
|
||||
|
||||
// FlagError is the kind of error raised in flag processing
|
||||
type FlagError struct {
|
||||
|
|
@ -17,3 +21,10 @@ func (fe FlagError) Unwrap() error {
|
|||
|
||||
// SilentError is an error that triggers exit code 1 without any error messaging
|
||||
var SilentError = errors.New("SilentError")
|
||||
|
||||
// CancelError signals user-initiated cancellation
|
||||
var CancelError = errors.New("CancelError")
|
||||
|
||||
func IsUserCancellation(err error) bool {
|
||||
return errors.Is(err, CancelError) || errors.Is(err, terminal.InterruptErr)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue