You've already forked agentic-coding-workflow
feat(infra/git): Runner interface + Credential injection + GitError
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user