fix: разрешить формат owner/repo в имени репозитория (E3 был слишком строг, слэш ≠ эскейп)
All checks were successful
CI / test (push) Successful in 50s
CI / build-and-package (amd64, linux) (push) Successful in 40s
CI / build-and-package (amd64, windows) (push) Successful in 45s

This commit is contained in:
Hermes
2026-08-16 23:00:56 +05:00
parent 4044c0866f
commit 35d9a74ef1
2 changed files with 20 additions and 3 deletions

View File

@@ -370,6 +370,15 @@ func (w *Worker) clone(ctx context.Context, repo string) error {
url := buildCloneURL(base, repo) url := buildCloneURL(base, repo)
dst := filepath.Join(w.Worktree, repo) dst := filepath.Join(w.Worktree, repo)
// Для формата "owner/repo" git clone не создаёт промежуточный каталог {worktree}/owner
// до самого репозитория — поэтому создаём родителя явно. Worktree в целом гарантирован
// app.New, но вложенные сегменты owner здесь обязательны.
if parent := filepath.Dir(dst); parent != "." && parent != w.Worktree {
if err := os.MkdirAll(parent, 0o755); err != nil {
return fmt.Errorf("%w: создать %s: %v", ErrClone, parent, err)
}
}
args := []string{"clone"} args := []string{"clone"}
if w.GitToken != "" { if w.GitToken != "" {
// https-базовый URL: кладём токен внутрь URL (для приватных репозиториев). // https-базовый URL: кладём токен внутрь URL (для приватных репозиториев).
@@ -391,12 +400,20 @@ func buildCloneURL(base, repo string) string {
} }
// validateRepoName отклоняет имена с пути-эскейпом (E3). // validateRepoName отклоняет имена с пути-эскейпом (E3).
// Допускается формат "owner/repo" (один слэш) — клон ляжет в {worktree}/owner/repo,
// промежуточный каталог создаёт clone(). Путь с сегментами "." , ".." , пустыми или
// абсолютный — ошибка.
func validateRepoName(repo string) error { func validateRepoName(repo string) error {
if repo == "" { if repo == "" {
return fmt.Errorf("%w: пустое имя", ErrRepoPathHint) return fmt.Errorf("%w: пустое имя", ErrRepoPathHint)
} }
if strings.Contains(repo, "/") || strings.Contains(repo, "..") { if filepath.IsAbs(repo) {
return fmt.Errorf("%w: %q", ErrRepoPathHint, repo) return fmt.Errorf("%w: %q", ErrRepoPathHint, repo)
} }
for _, seg := range strings.Split(repo, "/") {
if seg == "" || seg == "." || seg == ".." {
return fmt.Errorf("%w: %q", ErrRepoPathHint, repo)
}
}
return nil return nil
} }

View File

@@ -416,14 +416,14 @@ func TestWorkerPromptRendered(t *testing.T) {
} }
func TestValidateRepoName(t *testing.T) { func TestValidateRepoName(t *testing.T) {
valid := []string{"calc", "proj-a", "my.repo", "node_2"} valid := []string{"calc", "proj-a", "my.repo", "node_2", "kamelion/ratatoskr-go", "a/b/c"}
for _, r := range valid { for _, r := range valid {
if err := validateRepoName(r); err != nil { if err := validateRepoName(r); err != nil {
t.Errorf("validateRepoName(%q) = %v, want nil", r, err) t.Errorf("validateRepoName(%q) = %v, want nil", r, err)
} }
} }
invalid := []string{"", "../etc", "a/b", "a/../b", ".."} invalid := []string{"", "../etc", "etc/..", "a/../b", "..", ".", "/etc", "a//b", "./x"}
for _, r := range invalid { for _, r := range invalid {
if err := validateRepoName(r); err == nil { if err := validateRepoName(r); err == nil {
t.Errorf("validateRepoName(%q) = nil, want E3", r) t.Errorf("validateRepoName(%q) = nil, want E3", r)