Merge pull request #5363 from zmanda/fix-gh-5258-backup-exits-with-wrong-code-on-ctrl-c

bugfix: fatal errors do not keep underlying error
This commit is contained in:
Michael Eischer
2025-09-24 22:04:38 +02:00
committed by GitHub
8 changed files with 63 additions and 13 deletions

View File

@@ -7,25 +7,48 @@ import (
// fatalError is an error that should be printed to the user, then the program
// should exit with an error code.
type fatalError string
type fatalError struct {
msg string
err error // Underlying error
}
func (e fatalError) Error() string {
return string(e)
func (e *fatalError) Error() string {
return e.msg
}
func (e *fatalError) Unwrap() error {
return e.err
}
// IsFatal returns true if err is a fatal message that should be printed to the
// user. Then, the program should exit.
func IsFatal(err error) bool {
var fatal fatalError
var fatal *fatalError
return errors.As(err, &fatal)
}
// Fatal returns an error that is marked fatal.
func Fatal(s string) error {
return Wrap(fatalError(s), "Fatal")
return Wrap(&fatalError{msg: s}, "Fatal")
}
// Fatalf returns an error that is marked fatal.
// Fatalf returns an error that is marked fatal, preserving an underlying error if passed.
func Fatalf(s string, data ...interface{}) error {
return Wrap(fatalError(fmt.Sprintf(s, data...)), "Fatal")
// Use the last error found.
var underlyingErr error
for i := len(data) - 1; i >= 0; i-- {
if err, ok := data[i].(error); ok {
underlyingErr = err
break
}
}
msg := fmt.Sprintf(s, data...)
fatal := &fatalError{
msg: msg,
err: underlyingErr,
}
return Wrap(fatal, "Fatal")
}

View File

@@ -20,3 +20,23 @@ func TestFatal(t *testing.T) {
}
}
}
func TestFatalErrorWrapping(t *testing.T) {
underlying := errors.New("underlying error")
fatal := errors.Fatalf("fatal error: %v", underlying)
// Test that the fatal error message is preserved
if fatal.Error() != "Fatal: fatal error: underlying error" {
t.Errorf("unexpected error message: %v", fatal.Error())
}
// Test that we can unwrap to get the underlying error
if !errors.Is(fatal, underlying) {
t.Error("fatal error should wrap the underlying error")
}
// Test that the error is marked as fatal
if !errors.IsFatal(fatal) {
t.Error("error should be marked as fatal")
}
}

View File

@@ -567,7 +567,7 @@ func (plan *PrunePlan) Execute(ctx context.Context, printer progress.Printer) er
_, err := Repack(ctx, repo, repo, plan.repackPacks, plan.keepBlobs, bar, printer.P)
bar.Done()
if err != nil {
return errors.Fatal(err.Error())
return errors.Fatalf("%s", err)
}
// Also remove repacked packs