errors.go
package main
import (
"errors"
"fmt"
"strings"
)
// Error classes for the plumbing layer.
// The serve layer maps these to HTTP statuses with errors.Is:
// errInvalid means the request was malformed (400),
// errNotFound means the asked-for thing doesn't exist (404),
// and anything else is internal (500).
var (
// errInvalid marks input rejected by a validator: a malformed repo name, ref, or tree path.
errInvalid = errors.New("invalid")
// errNotFound marks a lookup that came back empty: an unknown repo, an unresolvable ref, or a path absent from a tree.
errNotFound = errors.New("not found")
)
// gitError reports a git invocation that failed.
// Read operations translate the failures that mean "doesn't exist" into errNotFound;
// a gitError that escapes to a caller is an internal problem (missing binary, repo corruption, parse surprise).
type gitError struct {
args []string // the git argv, excluding the leading "git" itself
exitCode int // the process's exit code; -1 if it never ran
stderr string // trimmed stderr capture, for diagnostics
cause error // the underlying exec error, if any
}
func (e *gitError) Error() string {
msg := fmt.Sprintf("git %s: exit %d", strings.Join(e.args, " "), e.exitCode)
if e.stderr != "" {
msg += ": " + e.stderr
}
if e.cause != nil {
msg += ": " + e.cause.Error()
}
return msg
}
func (e *gitError) Unwrap() error { return e.cause }
// notFoundOnExit converts a git failure into errNotFound when the process ran and exited nonzero:
// the operations that use this are ones where any clean nonzero exit means "no such ref/path".
// Failures to run at all (or errors that aren't gitError) pass through unchanged, staying internal.
func notFoundOnExit(err error, what string) error {
var ge *gitError
if errors.As(err, &ge) && ge.exitCode > 0 {
return fmt.Errorf("%w: %s", errNotFound, what)
}
return err
}
errors.go · 1.9 KiB · 240bb1d0c2