c36aa18c67
Nuevas skills para crear TUIs, inicializar frontends React y módulos Go. Incluye binario parallel-executor y utilidades de soporte. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
// Package shell contiene operaciones I/O del parallel executor.
|
|
// Todas las funciones retornan Result[T] y tienen side effects (git, filesystem).
|
|
package shell
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/lucasdataproyects/devfactory/core"
|
|
dfshell "github.com/lucasdataproyects/devfactory/shell"
|
|
|
|
pcore "github.com/lucasdataproyects/parallel-executor/core"
|
|
)
|
|
|
|
// CreateWorktree crea un git worktree para una issue.
|
|
// Crea branch desde master y configura el directorio de trabajo.
|
|
func CreateWorktree(spec pcore.WorktreeSpec, repoRoot string) core.Result[string] {
|
|
// Verificar que no existe ya
|
|
if dfshell.DirExists(spec.WorkDir) {
|
|
return core.Ok(spec.WorkDir)
|
|
}
|
|
|
|
// Crear directorio padre si no existe
|
|
parentDir := spec.WorkDir[:strings.LastIndex(spec.WorkDir, "/")]
|
|
mkResult := dfshell.MkdirAll(parentDir)
|
|
if mkResult.IsErr() {
|
|
return core.Err[string](mkResult.Error())
|
|
}
|
|
|
|
// Actualizar master primero
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
updateResult := dfshell.RunWithContext(ctx, "git", "-C", repoRoot, "fetch", "origin", "master")
|
|
if updateResult.IsErr() {
|
|
// No fatal — puede no tener remote
|
|
}
|
|
|
|
// Crear worktree con nueva branch desde master
|
|
result := dfshell.Run("git", "-C", repoRoot,
|
|
"worktree", "add",
|
|
"-b", spec.BranchName,
|
|
spec.WorkDir,
|
|
"master",
|
|
)
|
|
if result.IsErr() {
|
|
// Branch puede existir — intentar sin -b
|
|
result = dfshell.Run("git", "-C", repoRoot,
|
|
"worktree", "add",
|
|
spec.WorkDir,
|
|
spec.BranchName,
|
|
)
|
|
if result.IsErr() {
|
|
return core.Err[string](fmt.Errorf("failed to create worktree for issue #%04d: %w",
|
|
spec.Issue.Number, result.Error()))
|
|
}
|
|
}
|
|
|
|
return core.Ok(spec.WorkDir)
|
|
}
|
|
|
|
// RemoveWorktree elimina un worktree y su branch.
|
|
func RemoveWorktree(spec pcore.WorktreeSpec, repoRoot string) core.Result[struct{}] {
|
|
if !dfshell.DirExists(spec.WorkDir) {
|
|
return core.Ok(struct{}{})
|
|
}
|
|
|
|
// Remover worktree
|
|
result := dfshell.Run("git", "-C", repoRoot,
|
|
"worktree", "remove", "--force", spec.WorkDir,
|
|
)
|
|
if result.IsErr() {
|
|
// Fallback: eliminar directorio manualmente
|
|
os.RemoveAll(spec.WorkDir)
|
|
// Prune worktrees huérfanos
|
|
dfshell.Run("git", "-C", repoRoot, "worktree", "prune")
|
|
}
|
|
|
|
return core.Ok(struct{}{})
|
|
}
|
|
|
|
// CleanupAllWorktrees limpia todos los worktrees del directorio worktrees/.
|
|
func CleanupAllWorktrees(repoRoot string) core.Result[int] {
|
|
worktreesDir := repoRoot + "/worktrees"
|
|
if !dfshell.DirExists(worktreesDir) {
|
|
return core.Ok(0)
|
|
}
|
|
|
|
entries := dfshell.ListDir(worktreesDir)
|
|
if entries.IsErr() {
|
|
return core.Ok(0)
|
|
}
|
|
|
|
count := 0
|
|
for _, entry := range entries.Unwrap() {
|
|
fullPath := worktreesDir + "/" + entry
|
|
dfshell.Run("git", "-C", repoRoot, "worktree", "remove", "--force", fullPath)
|
|
count++
|
|
}
|
|
|
|
// Prune
|
|
dfshell.Run("git", "-C", repoRoot, "worktree", "prune")
|
|
|
|
// Eliminar directorio vacío
|
|
os.Remove(worktreesDir)
|
|
|
|
return core.Ok(count)
|
|
}
|
|
|
|
// ListWorktrees devuelve los worktrees activos.
|
|
func ListWorktrees(repoRoot string) core.Result[[]string] {
|
|
result := dfshell.Run("git", "-C", repoRoot, "worktree", "list", "--porcelain")
|
|
if result.IsErr() {
|
|
return core.Err[[]string](result.Error())
|
|
}
|
|
|
|
output := result.Unwrap().Output()
|
|
lines := strings.Split(output, "\n")
|
|
|
|
var paths []string
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, "worktree ") {
|
|
path := strings.TrimPrefix(line, "worktree ")
|
|
if path != repoRoot {
|
|
paths = append(paths, path)
|
|
}
|
|
}
|
|
}
|
|
|
|
return core.Ok(paths)
|
|
}
|
|
|