summaryrefslogtreecommitdiff
path: root/internal/api/stories.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api/stories.go')
-rw-r--r--internal/api/stories.go65
1 files changed, 61 insertions, 4 deletions
diff --git a/internal/api/stories.go b/internal/api/stories.go
index 459d0db..640bb0e 100644
--- a/internal/api/stories.go
+++ b/internal/api/stories.go
@@ -1,6 +1,7 @@
package api
import (
+ "database/sql"
"encoding/json"
"fmt"
"net/http"
@@ -9,22 +10,35 @@ import (
"time"
"github.com/google/uuid"
+ "github.com/thepeterstone/claudomator/internal/deployment"
"github.com/thepeterstone/claudomator/internal/task"
)
-// createStoryBranch creates a new git branch in localPath and pushes it to origin.
+// createStoryBranch creates a new git branch in localPath from origin/master (or main)
+// and pushes it to origin. Idempotent: treats "already exists" as success.
func createStoryBranch(localPath, branchName string) error {
- out, err := exec.Command("git", "-C", localPath, "checkout", "-b", branchName).CombinedOutput()
+ // Fetch latest from origin so origin/master is up to date.
+ if out, err := exec.Command("git", "-C", localPath, "fetch", "origin").CombinedOutput(); err != nil {
+ return fmt.Errorf("git fetch: %w (output: %s)", err, string(out))
+ }
+ // Try to create branch from origin/master; fall back to origin/main.
+ base := "origin/master"
+ if out, err := exec.Command("git", "-C", localPath, "rev-parse", "--verify", "origin/master").CombinedOutput(); err != nil {
+ if strings.Contains(string(out), "fatal") || err != nil {
+ base = "origin/main"
+ }
+ }
+ out, err := exec.Command("git", "-C", localPath, "checkout", "-b", branchName, base).CombinedOutput()
if err != nil {
if !strings.Contains(string(out), "already exists") {
return fmt.Errorf("git checkout -b: %w (output: %s)", err, string(out))
}
- // Branch exists; switch to it.
+ // Branch exists; switch to it — idempotent.
if out2, err2 := exec.Command("git", "-C", localPath, "checkout", branchName).CombinedOutput(); err2 != nil {
return fmt.Errorf("git checkout: %w (output: %s)", err2, string(out2))
}
}
- if out, err := exec.Command("git", "-C", localPath, "push", "-u", "origin", branchName).CombinedOutput(); err != nil {
+ if out, err := exec.Command("git", "-C", localPath, "push", "origin", branchName).CombinedOutput(); err != nil {
return fmt.Errorf("git push: %w (output: %s)", err, string(out))
}
return nil
@@ -306,3 +320,46 @@ func (s *Server) handleApproveStory(w http.ResponseWriter, r *http.Request) {
"task_ids": taskIDs,
})
}
+
+// handleStoryDeploymentStatus aggregates the deployment status across all tasks in a story.
+// GET /api/stories/{id}/deployment-status
+func (s *Server) handleStoryDeploymentStatus(w http.ResponseWriter, r *http.Request) {
+ id := r.PathValue("id")
+
+ story, err := s.store.GetStory(id)
+ if err != nil {
+ writeJSON(w, http.StatusNotFound, map[string]string{"error": "story not found"})
+ return
+ }
+
+ tasks, err := s.store.ListTasksByStory(id)
+ if err != nil {
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+
+ // Collect all commits from the latest execution of each task.
+ var allCommits []task.GitCommit
+ for _, t := range tasks {
+ exec, err := s.store.GetLatestExecution(t.ID)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ continue
+ }
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+ allCommits = append(allCommits, exec.Commits...)
+ }
+
+ // Determine project remote URL for the deployment check.
+ projectRemoteURL := ""
+ if story.ProjectID != "" {
+ if proj, err := s.store.GetProject(story.ProjectID); err == nil {
+ projectRemoteURL = proj.RemoteURL
+ }
+ }
+
+ status := deployment.Check(allCommits, projectRemoteURL)
+ writeJSON(w, http.StatusOK, status)
+}