feat(rig): Add --branch flag for custom default branch
Add --branch flag to `gt rig add` to specify a custom default branch instead of auto-detecting from remote. This supports repositories that use non-standard default branches like `develop` or `release`. Changes: - Add --branch flag to `gt rig add` command - Store default_branch in rig config.json - Propagate default branch to refinery, witness, daemon, and all commands - Rename ensureMainBranch to ensureDefaultBranch for clarity - Add Rig.DefaultBranch() method for consistent access - Update crew/manager.go and swarm/manager.go to use rig config Based on PR #49 by @kustrun - rebased and extended with additional fixes. Co-authored-by: kustrun <kustrun@users.noreply.github.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
31df1bb2c1
commit
eea4435269
@@ -52,8 +52,8 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("getting crew worker: %w", err)
|
||||
}
|
||||
|
||||
// Ensure crew workspace is on main branch (persistent roles should not use feature branches)
|
||||
ensureMainBranch(worker.ClonePath, fmt.Sprintf("Crew workspace %s/%s", r.Name, name))
|
||||
// Ensure crew workspace is on default branch (persistent roles should not use feature branches)
|
||||
ensureDefaultBranch(worker.ClonePath, fmt.Sprintf("Crew workspace %s/%s", r.Name, name), r.Path)
|
||||
|
||||
// If --no-tmux, just print the path
|
||||
if crewNoTmux {
|
||||
|
||||
@@ -205,10 +205,11 @@ func attachToTmuxSession(sessionID string) error {
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// ensureMainBranch checks if a git directory is on main branch.
|
||||
// ensureDefaultBranch checks if a git directory is on the default branch.
|
||||
// If not, warns the user and offers to switch.
|
||||
// Returns true if on main (or switched to main), false if user declined.
|
||||
func ensureMainBranch(dir, roleName string) bool { //nolint:unparam // bool return kept for future callers to check
|
||||
// Returns true if on default branch (or switched to it), false if user declined.
|
||||
// The rigPath parameter is used to look up the configured default branch.
|
||||
func ensureDefaultBranch(dir, roleName, rigPath string) bool { //nolint:unparam // bool return kept for future callers to check
|
||||
g := git.NewGit(dir)
|
||||
|
||||
branch, err := g.CurrentBranch()
|
||||
@@ -217,31 +218,38 @@ func ensureMainBranch(dir, roleName string) bool { //nolint:unparam // bool retu
|
||||
return true
|
||||
}
|
||||
|
||||
if branch == "main" || branch == "master" {
|
||||
// Get configured default branch for this rig
|
||||
defaultBranch := "main" // fallback
|
||||
if rigCfg, err := rig.LoadRigConfig(rigPath); err == nil && rigCfg.DefaultBranch != "" {
|
||||
defaultBranch = rigCfg.DefaultBranch
|
||||
}
|
||||
|
||||
if branch == defaultBranch || branch == "master" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Warn about wrong branch
|
||||
fmt.Printf("\n%s %s is on branch '%s', not main\n",
|
||||
fmt.Printf("\n%s %s is on branch '%s', not %s\n",
|
||||
style.Warning.Render("⚠"),
|
||||
roleName,
|
||||
branch)
|
||||
fmt.Println(" Persistent roles should work on main to avoid orphaned work.")
|
||||
branch,
|
||||
defaultBranch)
|
||||
fmt.Printf(" Persistent roles should work on %s to avoid orphaned work.\n", defaultBranch)
|
||||
fmt.Println()
|
||||
|
||||
// Auto-switch to main
|
||||
fmt.Printf(" Switching to main...\n")
|
||||
if err := g.Checkout("main"); err != nil {
|
||||
fmt.Printf(" %s Could not switch to main: %v\n", style.Error.Render("✗"), err)
|
||||
fmt.Println(" Please manually run: git checkout main && git pull")
|
||||
// Auto-switch to default branch
|
||||
fmt.Printf(" Switching to %s...\n", defaultBranch)
|
||||
if err := g.Checkout(defaultBranch); err != nil {
|
||||
fmt.Printf(" %s Could not switch to %s: %v\n", style.Error.Render("✗"), defaultBranch, err)
|
||||
fmt.Printf(" Please manually run: git checkout %s && git pull\n", defaultBranch)
|
||||
return false
|
||||
}
|
||||
|
||||
// Pull latest
|
||||
if err := g.Pull("origin", "main"); err != nil {
|
||||
if err := g.Pull("origin", defaultBranch); err != nil {
|
||||
fmt.Printf(" %s Pull failed (continuing anyway): %v\n", style.Warning.Render("⚠"), err)
|
||||
} else {
|
||||
fmt.Printf(" %s Switched to main and pulled latest\n", style.Success.Render("✓"))
|
||||
fmt.Printf(" %s Switched to %s and pulled latest\n", style.Success.Render("✓"), defaultBranch)
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/steveyegge/gastown/internal/events"
|
||||
"github.com/steveyegge/gastown/internal/git"
|
||||
"github.com/steveyegge/gastown/internal/mail"
|
||||
"github.com/steveyegge/gastown/internal/rig"
|
||||
"github.com/steveyegge/gastown/internal/style"
|
||||
"github.com/steveyegge/gastown/internal/workspace"
|
||||
)
|
||||
@@ -147,11 +149,17 @@ func runDone(cmd *cobra.Command, args []string) error {
|
||||
agentBeadID = getAgentBeadID(ctx)
|
||||
}
|
||||
|
||||
// For COMPLETED, we need an issue ID and branch must not be main
|
||||
// Get configured default branch for this rig
|
||||
defaultBranch := "main" // fallback
|
||||
if rigCfg, err := rig.LoadRigConfig(filepath.Join(townRoot, rigName)); err == nil && rigCfg.DefaultBranch != "" {
|
||||
defaultBranch = rigCfg.DefaultBranch
|
||||
}
|
||||
|
||||
// For COMPLETED, we need an issue ID and branch must not be the default branch
|
||||
var mrID string
|
||||
if exitType == ExitCompleted {
|
||||
if branch == "main" || branch == "master" {
|
||||
return fmt.Errorf("cannot submit main/master branch to merge queue")
|
||||
if branch == defaultBranch || branch == "master" {
|
||||
return fmt.Errorf("cannot submit %s/master branch to merge queue", defaultBranch)
|
||||
}
|
||||
|
||||
// Check for unpushed commits - branch must be pushed before MR creation
|
||||
@@ -164,9 +172,6 @@ func runDone(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("branch has %d unpushed commit(s); run 'git push -u origin %s' first", unpushedCount, branch)
|
||||
}
|
||||
|
||||
// Detect the repo's default branch (main vs master)
|
||||
defaultBranch := g.RemoteDefaultBranch()
|
||||
|
||||
// Check that branch has commits ahead of default branch (prevents submitting stale branches)
|
||||
aheadCount, err := g.CommitsAhead(defaultBranch, branch)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/git"
|
||||
"github.com/steveyegge/gastown/internal/rig"
|
||||
"github.com/steveyegge/gastown/internal/style"
|
||||
"github.com/steveyegge/gastown/internal/workspace"
|
||||
)
|
||||
@@ -78,8 +80,14 @@ func runMqSubmit(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if branch == "main" || branch == "master" {
|
||||
return fmt.Errorf("cannot submit main/master branch to merge queue")
|
||||
// Get configured default branch for this rig
|
||||
defaultBranch := "main" // fallback
|
||||
if rigCfg, err := rig.LoadRigConfig(filepath.Join(townRoot, rigName)); err == nil && rigCfg.DefaultBranch != "" {
|
||||
defaultBranch = rigCfg.DefaultBranch
|
||||
}
|
||||
|
||||
if branch == defaultBranch || branch == "master" {
|
||||
return fmt.Errorf("cannot submit %s/master branch to merge queue", defaultBranch)
|
||||
}
|
||||
|
||||
// CRITICAL: Verify branch is pushed before creating MR bead
|
||||
@@ -111,7 +119,7 @@ func runMqSubmit(cmd *cobra.Command, args []string) error {
|
||||
bd := beads.New(cwd)
|
||||
|
||||
// Determine target branch
|
||||
target := "main"
|
||||
target := defaultBranch
|
||||
if mqSubmitEpic != "" {
|
||||
// Explicit --epic flag takes precedence
|
||||
target = "integration/" + mqSubmitEpic
|
||||
@@ -119,7 +127,7 @@ func runMqSubmit(cmd *cobra.Command, args []string) error {
|
||||
// Auto-detect: check if source issue has a parent epic with an integration branch
|
||||
autoTarget, err := detectIntegrationBranch(bd, g, issueID)
|
||||
if err != nil {
|
||||
// Non-fatal: log and continue with main as target
|
||||
// Non-fatal: log and continue with default branch as target
|
||||
fmt.Printf(" %s\n", style.Dim.Render(fmt.Sprintf("(note: %v)", err)))
|
||||
} else if autoTarget != "" {
|
||||
target = autoTarget
|
||||
|
||||
@@ -252,6 +252,7 @@ Examples:
|
||||
var (
|
||||
rigAddPrefix string
|
||||
rigAddLocalRepo string
|
||||
rigAddBranch string
|
||||
rigResetHandoff bool
|
||||
rigResetMail bool
|
||||
rigResetStale bool
|
||||
@@ -281,6 +282,7 @@ func init() {
|
||||
|
||||
rigAddCmd.Flags().StringVar(&rigAddPrefix, "prefix", "", "Beads issue prefix (default: derived from name)")
|
||||
rigAddCmd.Flags().StringVar(&rigAddLocalRepo, "local-repo", "", "Local repo path to share git objects (optional)")
|
||||
rigAddCmd.Flags().StringVar(&rigAddBranch, "branch", "", "Default branch name (default: auto-detected from remote)")
|
||||
|
||||
rigResetCmd.Flags().BoolVar(&rigResetHandoff, "handoff", false, "Clear handoff content")
|
||||
rigResetCmd.Flags().BoolVar(&rigResetMail, "mail", false, "Clear stale mail messages")
|
||||
@@ -340,10 +342,11 @@ func runRigAdd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// Add the rig
|
||||
newRig, err := mgr.AddRig(rig.AddRigOptions{
|
||||
Name: name,
|
||||
GitURL: gitURL,
|
||||
BeadsPrefix: rigAddPrefix,
|
||||
LocalRepo: rigAddLocalRepo,
|
||||
Name: name,
|
||||
GitURL: gitURL,
|
||||
BeadsPrefix: rigAddPrefix,
|
||||
LocalRepo: rigAddLocalRepo,
|
||||
DefaultBranch: rigAddBranch,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding rig: %w", err)
|
||||
|
||||
@@ -750,8 +750,8 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Crew workspace %s/%s exists\n", rigName, name)
|
||||
}
|
||||
|
||||
// Ensure crew workspace is on main branch
|
||||
ensureMainBranch(worker.ClonePath, fmt.Sprintf("Crew workspace %s/%s", rigName, name))
|
||||
// Ensure crew workspace is on default branch
|
||||
ensureDefaultBranch(worker.ClonePath, fmt.Sprintf("Crew workspace %s/%s", rigName, name), r.Path)
|
||||
|
||||
// Resolve account for Claude config
|
||||
accountsPath := constants.MayorAccountsPath(townRoot)
|
||||
@@ -933,8 +933,8 @@ func startCrewMember(rigName, crewName, townRoot string) error {
|
||||
return fmt.Errorf("getting crew worker: %w", err)
|
||||
}
|
||||
|
||||
// Ensure crew workspace is on main branch
|
||||
ensureMainBranch(worker.ClonePath, fmt.Sprintf("Crew workspace %s/%s", rigName, crewName))
|
||||
// Ensure crew workspace is on default branch
|
||||
ensureDefaultBranch(worker.ClonePath, fmt.Sprintf("Crew workspace %s/%s", rigName, crewName), r.Path)
|
||||
|
||||
// Create tmux session
|
||||
t := tmux.NewTmux()
|
||||
|
||||
Reference in New Issue
Block a user