fix: разрешить формат owner/repo в имени репозитория (E3 был слишком строг, слэш ≠ эскейп)
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user