package worker import ( "context" "fmt" "os/exec" "strings" ) // baseBranch — ветка, от которой отходит feature-работа dev (свежайший main). const baseBranch = "main" // ensureBranch пересоздаёт feature-ветку для задачи в repoDir с нуля, // беря за базу свежайший origin/. Сначала сбрасывает worktree // к чистому состоянию (reset --hard + clean -fd) — затирая наработки прошлых // прогонов, иначе checkout -B упирается в «local changes would be overwritten». func (w *Worker) ensureBranch(ctx context.Context, repoDir, branch string) error { if _, err := w.git(ctx, repoDir, "fetch", "origin"); err != nil { return err } // возвращаем worktree к чистому состоянию текущей ветки, затем чистим untracked if _, err := w.git(ctx, repoDir, "reset", "--hard"); err != nil { return err } if _, err := w.git(ctx, repoDir, "clean", "-fd"); err != nil { return err } // пересоздаём ветку от свежайшего origin/ if _, err := w.git(ctx, repoDir, "checkout", "-B", branch, "origin/"+baseBranch); err != nil { return err } return nil } // branchDiff возвращает diff всей feature-ветки относительно базы: // это все изменения коммитов ветки (base...branch), а не только последний. func (w *Worker) branchDiff(ctx context.Context, repoDir, branch string) (string, error) { return w.git(ctx, repoDir, "diff", "origin/"+baseBranch+"..."+branch) } // pushBranch пушит feature-ветку в origin (remote 'origin' создан при клоне). // Токен передаётся через http.extraHeader, если задан. func (w *Worker) pushBranch(ctx context.Context, repoDir, branch string) error { args := []string{"push", "origin", "HEAD:" + branch} if w.GitToken != "" { // У `git push` НЕТ опции --config (в отличие от clone). Конфиг задаётся // глобальной опцией `-c =`, которая идёт ДО подкоманды. args = append([]string{"-c", "http.extraHeader=Authorization: Bearer " + w.GitToken}, args...) } _, err := w.git(ctx, repoDir, args...) return err } // git выполняет git-команду в каталоге dir. Ошибки оборачиваются с выводом. func (w *Worker) git(ctx context.Context, dir string, args ...string) (string, error) { cmd := exec.CommandContext(ctx, "git", args...) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("git %v: %s", args, strings.TrimSpace(string(out))) } return string(out), nil } // featureBranchName — имя ветки задачи: feat/. func featureBranchName(taskTag string) string { tag := strings.TrimSpace(taskTag) if tag == "" { // защита от старых задач с пустым task_tag: не даём сломать git (feat/ невалидна) return "feat/main" // коллизия с базовой "main" не страшна: ветка feature-своя есть под этим именем } return "feat/" + tag }