package action import ( "context" "fmt" "os" "os/exec" "path/filepath" "github.com/rockliang/poimen/workflows/internal/lock" ) // CloneRepoInput is input to CloneRepoActivity. type CloneRepoInput struct { RemoteURL string TargetRepoPath string } // CloneRepoActivity clones a repo if it doesn't exist, or fetches if it does. func CloneRepoActivity(ctx context.Context, in CloneRepoInput) error { // Check if repo already exists gitDir := filepath.Join(in.TargetRepoPath, ".git") if _, err := os.Stat(gitDir); err == nil { // Repo exists, fetch latest cmd := exec.CommandContext(ctx, "git", "-C", in.TargetRepoPath, "fetch", "origin") if err := cmd.Run(); err != nil { return fmt.Errorf("git fetch failed: %w", err) } return nil } // Repo doesn't exist, clone it cmd := exec.CommandContext(ctx, "git", "clone", in.RemoteURL, in.TargetRepoPath) if err := cmd.Run(); err != nil { return fmt.Errorf("git clone failed: %w", err) } return nil } // GitWorktreeAddInput is input to GitWorktreeAddActivity. type GitWorktreeAddInput struct { RepoPath string TaskID string } // GitWorktreeAddActivity creates a new git worktree for a task. func GitWorktreeAddActivity(ctx context.Context, in GitWorktreeAddInput) (string, error) { // Acquire lock to synchronize worktree creation lockPath := filepath.Join(in.RepoPath, "orchestrator.lock") if err := lock.Acquire(lockPath); err != nil { return "", fmt.Errorf("failed to acquire lock: %w", err) } defer lock.Release(lockPath) // Create worktrees directory if it doesn't exist worktreesDir := filepath.Join(in.RepoPath, "worktrees") if err := os.MkdirAll(worktreesDir, 0755); err != nil { return "", fmt.Errorf("failed to create worktrees dir: %w", err) } worktreePath := filepath.Join(worktreesDir, in.TaskID) branch := "task/" + in.TaskID // Create worktree cmd := exec.CommandContext(ctx, "git", "-C", in.RepoPath, "worktree", "add", "-b", branch, worktreePath, "origin/main") if err := cmd.Run(); err != nil { return "", fmt.Errorf("git worktree add failed: %w", err) } return worktreePath, nil } // GitCommitInput is input to GitCommitActivity. type GitCommitInput struct { WorktreePath string Message string } // GitCommitActivity commits changes in a worktree. func GitCommitActivity(ctx context.Context, in GitCommitInput) error { // Stage all changes cmd := exec.CommandContext(ctx, "git", "-C", in.WorktreePath, "add", "-A") if err := cmd.Run(); err != nil { return fmt.Errorf("git add failed: %w", err) } // Commit cmd = exec.CommandContext(ctx, "git", "-C", in.WorktreePath, "commit", "-m", in.Message) if err := cmd.Run(); err != nil { return fmt.Errorf("git commit failed: %w", err) } return nil } // GitPushInput is input to GitPushActivity. type GitPushInput struct { RepoPath string } // GitPushActivity pushes changes to origin. func GitPushActivity(ctx context.Context, in GitPushInput) error { // Acquire lock to synchronize push lockPath := filepath.Join(in.RepoPath, "orchestrator.lock") if err := lock.Acquire(lockPath); err != nil { return fmt.Errorf("failed to acquire lock: %w", err) } defer lock.Release(lockPath) cmd := exec.CommandContext(ctx, "git", "-C", in.RepoPath, "push", "origin", "main") if err := cmd.Run(); err != nil { return fmt.Errorf("git push failed: %w", err) } return nil } // GitSquashMergeInput is input to GitSquashMergeActivity. type GitSquashMergeInput struct { RepoPath string Branches []string Message string } // GitSquashMergeActivity performs a squash merge of multiple branches into main. func GitSquashMergeActivity(ctx context.Context, in GitSquashMergeInput) error { // Acquire lock to synchronize merge lockPath := filepath.Join(in.RepoPath, "orchestrator.lock") if err := lock.Acquire(lockPath); err != nil { return fmt.Errorf("failed to acquire lock: %w", err) } defer lock.Release(lockPath) // Fetch origin main cmd := exec.CommandContext(ctx, "git", "-C", in.RepoPath, "fetch", "origin", "main") if err := cmd.Run(); err != nil { return fmt.Errorf("git fetch failed: %w", err) } // Checkout main and pull with ff-only cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "checkout", "main") if err := cmd.Run(); err != nil { return fmt.Errorf("git checkout main failed: %w", err) } cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "pull", "--ff-only", "origin", "main") if err := cmd.Run(); err != nil { return fmt.Errorf("git pull failed: %w", err) } // Squash merge each branch for _, branch := range in.Branches { cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "merge", "--squash", branch) if err := cmd.Run(); err != nil { return fmt.Errorf("git merge --squash %s failed: %w", branch, err) } } // Commit squashed changes cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "commit", "-m", in.Message) if err := cmd.Run(); err != nil { return fmt.Errorf("git commit failed: %w", err) } // Push to origin cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "push", "origin", "main") if err := cmd.Run(); err != nil { return fmt.Errorf("git push failed: %w", err) } // Clean up worktrees and branches for _, branch := range in.Branches { taskID := branch[len("task/"):] worktreePath := filepath.Join(in.RepoPath, "worktrees", taskID) // Remove worktree cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "worktree", "remove", worktreePath, "--force") if err := cmd.Run(); err != nil { // Log error but continue cleanup fmt.Printf("warning: failed to remove worktree %s: %v\n", worktreePath, err) } // Delete branch cmd = exec.CommandContext(ctx, "git", "-C", in.RepoPath, "branch", "-D", branch) if err := cmd.Run(); err != nil { // Log error but continue cleanup fmt.Printf("warning: failed to delete branch %s: %v\n", branch, err) } } return nil }