errors.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package git
  2. /*
  3. errors.go
  4. Error vocabulary plus the classifier that decides whether a failed transport
  5. operation should make the front-end pop the credential dialog.
  6. go-git surfaces authentication problems through several unrelated error
  7. values depending on the transport and the server, so matching on the message
  8. is unavoidable. IsAuthError is kept as a pure, table-tested function.
  9. */
  10. import (
  11. "errors"
  12. "strings"
  13. "github.com/go-git/go-git/v5/plumbing/transport"
  14. )
  15. var (
  16. //ErrNotARepo is returned when a path is not inside a git working tree.
  17. ErrNotARepo = errors.New("not a git repository")
  18. //ErrNoRemote is returned when an operation needs a remote but none is set.
  19. ErrNoRemote = errors.New("repository has no remote configured")
  20. //ErrUnbornBranch is returned when HEAD does not resolve yet (no commits).
  21. ErrUnbornBranch = errors.New("branch has no commits yet")
  22. //ErrAuthRequired is returned when the remote rejected or demanded
  23. //credentials. The AGI layer turns this into authRequired = true.
  24. ErrAuthRequired = errors.New("authentication required")
  25. //ErrUnmergedBranch is returned when deleting a local branch that still holds
  26. //commits unreachable from HEAD. The AGI layer turns this into
  27. //unmerged = true so the caller can confirm and retry with force.
  28. ErrUnmergedBranch = errors.New("branch has commits that are not merged into the current branch")
  29. )
  30. // authErrorFragments are lower-cased substrings that identify an authentication
  31. // or authorisation failure across the git hosting services ArozOS users are
  32. // likely to hit (GitHub, GitLab, Gitea, Bitbucket, plain http backends).
  33. var authErrorFragments = []string{
  34. "authentication required",
  35. "authorization failed",
  36. "authentication failed",
  37. "invalid auth method",
  38. "unauthorized",
  39. "403 forbidden",
  40. "401 unauthorized",
  41. "bad credentials",
  42. "permission denied",
  43. "could not read username",
  44. "terminal prompts disabled",
  45. "support for password authentication was removed",
  46. }
  47. // IsAuthError reports whether err represents a credential problem rather than a
  48. // genuine transport or repository failure.
  49. func IsAuthError(err error) bool {
  50. if err == nil {
  51. return false
  52. }
  53. if errors.Is(err, ErrAuthRequired) ||
  54. errors.Is(err, transport.ErrAuthenticationRequired) ||
  55. errors.Is(err, transport.ErrAuthorizationFailed) {
  56. return true
  57. }
  58. message := strings.ToLower(err.Error())
  59. for _, fragment := range authErrorFragments {
  60. if strings.Contains(message, fragment) {
  61. return true
  62. }
  63. }
  64. return false
  65. }
  66. // classifyError normalises a go-git transport error so callers upstream can
  67. // simply check errors.Is(err, ErrAuthRequired). The original message is kept as
  68. // the wrapped text because it usually names the failing host.
  69. func classifyError(err error) error {
  70. if err == nil {
  71. return nil
  72. }
  73. if IsAuthError(err) {
  74. return &authError{inner: err}
  75. }
  76. return err
  77. }
  78. // authError wraps a transport failure while reporting as ErrAuthRequired to
  79. // errors.Is, so the AGI layer can set authRequired without losing the message.
  80. type authError struct {
  81. inner error
  82. }
  83. func (e *authError) Error() string { return e.inner.Error() }
  84. func (e *authError) Unwrap() error { return e.inner }
  85. func (e *authError) Is(target error) bool { return target == ErrAuthRequired }