Files
agentic-coding-workflow/internal/infra/git/errors.go
T

92 lines
2.0 KiB
Go

package git
import (
"errors"
"fmt"
)
// GitError 是 git 子进程返回非零或超时时的统一错误形式。Stderr 已截断 ≤2000 字符;
// 上层(workspace.Service)会根据 stderr 内容映射到具体业务错误码。
type GitError struct {
Args []string
ExitCode int
Stderr string
Cause error
}
func (e *GitError) Error() string {
return fmt.Sprintf("git %v: exit %d: %s", e.Args, e.ExitCode, e.Stderr)
}
func (e *GitError) Unwrap() error { return e.Cause }
// IsAuthError 通过 stderr 关键字识别 401/403 类型的远端拒绝(PAT 失效、SSH key
// 不被信任等)。grep 的关键字源自常见 git 远端的英文响应;模糊但可用。
func IsAuthError(err error) bool {
var ge *GitError
if !errors.As(err, &ge) {
return false
}
for _, k := range []string{
"Authentication failed",
"could not read Username",
"Permission denied (publickey)",
"403 Forbidden",
"401 Unauthorized",
"fatal: Authentication",
} {
if containsCase(ge.Stderr, k) {
return true
}
}
return false
}
// IsConflict 用于识别 "branch already exists" 等 worktree add 冲突场景。
func IsConflict(err error) bool {
var ge *GitError
if !errors.As(err, &ge) {
return false
}
for _, k := range []string{
"already exists",
"is already checked out",
"already used by worktree",
} {
if containsCase(ge.Stderr, k) {
return true
}
}
return false
}
// containsCase 大小写不敏感子串匹配;避免引入 strings.EqualFold 之类逐字符开销。
func containsCase(s, sub string) bool {
if len(sub) == 0 || len(s) < len(sub) {
return false
}
S, Sub := lower(s), lower(sub)
return indexOf(S, Sub) >= 0
}
func lower(s string) string {
b := make([]byte, len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if c >= 'A' && c <= 'Z' {
c += 'a' - 'A'
}
b[i] = c
}
return string(b)
}
func indexOf(s, sub string) int {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return i
}
}
return -1
}