73 lines
3.1 KiB
Go
73 lines
3.1 KiB
Go
package worker
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"os/exec"
|
||
"strings"
|
||
)
|
||
|
||
// baseBranch — ветка, от которой отходит feature-работа dev (свежайший main).
|
||
const baseBranch = "main"
|
||
|
||
// ensureBranch пересоздаёт feature-ветку для задачи в repoDir с нуля,
|
||
// беря за базу свежайший origin/<baseBranch>. Сначала сбрасывает 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/<baseBranch>
|
||
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"}
|
||
if w.GitToken != "" {
|
||
args = append(args, "--config", "http.extraHeader=Authorization: Bearer "+w.GitToken)
|
||
}
|
||
args = append(args, "origin", "HEAD:"+branch)
|
||
_, 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/<taskTag>.
|
||
func featureBranchName(taskTag string) string {
|
||
tag := strings.TrimSpace(taskTag)
|
||
if tag == "" {
|
||
// защита от старых задач с пустым task_tag: не даём сломать git (feat/ невалидна)
|
||
return "feat/main" // коллизия с базовой "main" не страшна: ветка feature-своя есть под этим именем
|
||
}
|
||
return "feat/" + tag
|
||
}
|