package main import ( "fmt" "os/exec" "time" "github.com/orchard9/rdev/internal/sdlc" "github.com/spf13/cobra" ) var branchCmd = &cobra.Command{ Use: "branch", Short: "Manage feature branches", } var branchCreateCmd = &cobra.Command{ Use: "create ", Short: "Create a feature branch", Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { root := mustResolveRoot() slug := args[0] cfg, err := sdlc.LoadConfig(root) if err != nil { return err } // Create the branch manifest manifest, err := sdlc.CreateBranch(root, slug, cfg) if err != nil { return err } // Create the git branch branchName := manifest.Name gitCmd := exec.Command("git", "checkout", "-b", branchName) gitCmd.Dir = root if out, err := gitCmd.CombinedOutput(); err != nil { return fmt.Errorf("git checkout -b %s: %s: %w", branchName, string(out), err) } // Record the action in state state, err := sdlc.LoadState(root) if err != nil { return err } state.RecordAction("branch_created", slug, "cli") if err := state.Save(root); err != nil { return err } if jsonOutput { printJSON(manifest) return nil } fmt.Printf("Created branch: %s (from %s)\n", branchName, manifest.BaseBranch) return nil }, } var branchStatusCmd = &cobra.Command{ Use: "status ", Short: "Show branch status and merge checklist", Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { root := mustResolveRoot() slug := args[0] f, err := sdlc.LoadFeature(root, slug) if err != nil { return err } if f.Branch == "" { if jsonOutput { printJSON(map[string]string{"error": "no branch associated with feature"}) return nil } fmt.Printf("Feature %s has no branch.\n", slug) return nil } manifest, err := sdlc.LoadBranch(root, f.Branch) if err != nil { return err } checklist, err := sdlc.MergeChecklist(root, slug) if err != nil { return err } if jsonOutput { printJSON(map[string]any{ "branch": manifest, "checklist": checklist, "ready": len(checklist) == 0, }) return nil } fmt.Printf("Branch: %s\n", manifest.Name) fmt.Printf("Feature: %s\n", manifest.Feature) fmt.Printf("Base: %s\n", manifest.BaseBranch) fmt.Printf("Created: %s\n", manifest.CreatedAt.Format(time.RFC3339)) if manifest.LastSyncAt != nil { fmt.Printf("Last sync: %s\n", manifest.LastSyncAt.Format(time.RFC3339)) } fmt.Println() if len(checklist) == 0 { fmt.Println("Merge status: READY") } else { fmt.Println("Merge status: NOT READY") fmt.Println("Unmet gates:") for _, gate := range checklist { fmt.Printf(" - %s\n", gate) } } return nil }, } var branchSyncCmd = &cobra.Command{ Use: "sync ", Short: "Sync feature branch with base branch", Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { root := mustResolveRoot() slug := args[0] f, err := sdlc.LoadFeature(root, slug) if err != nil { return err } if f.Branch == "" { return fmt.Errorf("feature %s has no branch", slug) } manifest, err := sdlc.LoadBranch(root, f.Branch) if err != nil { return err } // Fetch and rebase fetchCmd := exec.Command("git", "fetch", "origin") fetchCmd.Dir = root if out, err := fetchCmd.CombinedOutput(); err != nil { return fmt.Errorf("git fetch: %s: %w", string(out), err) } rebaseCmd := exec.Command("git", "rebase", "origin/"+manifest.BaseBranch) rebaseCmd.Dir = root if out, err := rebaseCmd.CombinedOutput(); err != nil { return fmt.Errorf("git rebase origin/%s: %s: %w", manifest.BaseBranch, string(out), err) } // Update last sync time now := time.Now().UTC() manifest.LastSyncAt = &now if err := sdlc.SaveBranch(root, manifest); err != nil { return err } if jsonOutput { printJSON(map[string]string{ "feature": slug, "branch": manifest.Name, "synced": "true", "synced_at": now.Format(time.RFC3339), }) return nil } fmt.Printf("Synced %s with origin/%s\n", manifest.Name, manifest.BaseBranch) return nil }, } func init() { branchCmd.AddCommand( branchCreateCmd, branchStatusCmd, branchSyncCmd, ) rootCmd.AddCommand(branchCmd) }