feat: Implement bare repo architecture for branch visibility (gt-gmqe)

- Add .repo.git as shared bare repo for worktrees
- Update polecat manager to use bare repo when available
- Add git.NewGitWithDir() and CloneBare() for bare repo support
- Update gt rig init to create bare repo architecture for new rigs
- Refinery and polecats now share branch visibility via shared .git

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-25 18:49:34 -08:00
parent 200b2065a6
commit 1b66b9a2f2
4 changed files with 100 additions and 36 deletions
+3 -2
View File
@@ -217,10 +217,11 @@ func runRigAdd(cmd *cobra.Command, args []string) error {
fmt.Printf("\nStructure:\n") fmt.Printf("\nStructure:\n")
fmt.Printf(" %s/\n", name) fmt.Printf(" %s/\n", name)
fmt.Printf(" ├── config.json\n") fmt.Printf(" ├── config.json\n")
fmt.Printf(" ├── .repo.git/ (shared bare repo)\n")
fmt.Printf(" ├── .beads/ (prefix: %s)\n", newRig.Config.Prefix) fmt.Printf(" ├── .beads/ (prefix: %s)\n", newRig.Config.Prefix)
fmt.Printf(" ├── plugins/ (rig-level plugins)\n") fmt.Printf(" ├── plugins/ (rig-level plugins)\n")
fmt.Printf(" ├── refinery/rig/ (canonical main)\n") fmt.Printf(" ├── mayor/rig/ (worktree: main)\n")
fmt.Printf(" ├── mayor/rig/ (mayor's clone)\n") fmt.Printf(" ├── refinery/rig/ (worktree: refinery)\n")
fmt.Printf(" ├── crew/%s/ (your workspace)\n", crewName) fmt.Printf(" ├── crew/%s/ (your workspace)\n", crewName)
fmt.Printf(" ├── witness/\n") fmt.Printf(" ├── witness/\n")
fmt.Printf(" └── polecats/\n") fmt.Printf(" └── polecats/\n")
+28 -1
View File
@@ -20,6 +20,7 @@ var (
// Git wraps git operations for a working directory. // Git wraps git operations for a working directory.
type Git struct { type Git struct {
workDir string workDir string
gitDir string // Optional: explicit git directory (for bare repos)
} }
// NewGit creates a new Git wrapper for the given directory. // NewGit creates a new Git wrapper for the given directory.
@@ -27,6 +28,13 @@ func NewGit(workDir string) *Git {
return &Git{workDir: workDir} return &Git{workDir: workDir}
} }
// NewGitWithDir creates a Git wrapper with an explicit git directory.
// This is used for bare repos where gitDir points to the .git directory
// and workDir may be empty or point to a worktree.
func NewGitWithDir(gitDir, workDir string) *Git {
return &Git{gitDir: gitDir, workDir: workDir}
}
// WorkDir returns the working directory for this Git instance. // WorkDir returns the working directory for this Git instance.
func (g *Git) WorkDir() string { func (g *Git) WorkDir() string {
return g.workDir return g.workDir
@@ -34,8 +42,15 @@ func (g *Git) WorkDir() string {
// run executes a git command and returns stdout. // run executes a git command and returns stdout.
func (g *Git) run(args ...string) (string, error) { func (g *Git) run(args ...string) (string, error) {
// If gitDir is set (bare repo), prepend --git-dir flag
if g.gitDir != "" {
args = append([]string{"--git-dir=" + g.gitDir}, args...)
}
cmd := exec.Command("git", args...) cmd := exec.Command("git", args...)
cmd.Dir = g.workDir if g.workDir != "" {
cmd.Dir = g.workDir
}
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout
@@ -84,6 +99,18 @@ func (g *Git) Clone(url, dest string) error {
return nil return nil
} }
// CloneBare clones a repository as a bare repo (no working directory).
// This is used for the shared repo architecture where all worktrees share a single git database.
func (g *Git) CloneBare(url, dest string) error {
cmd := exec.Command("git", "clone", "--bare", url, dest)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return g.wrapError(err, stderr.String(), []string{"clone", "--bare", url})
}
return nil
}
// Checkout checks out the given ref. // Checkout checks out the given ref.
func (g *Git) Checkout(ref string) error { func (g *Git) Checkout(ref string) error {
_, err := g.run("checkout", ref) _, err := g.run("checkout", ref)
+49 -25
View File
@@ -82,6 +82,25 @@ func (m *Manager) assigneeID(name string) string {
return fmt.Sprintf("%s/%s", m.rig.Name, name) return fmt.Sprintf("%s/%s", m.rig.Name, name)
} }
// repoBase returns the git directory and Git object to use for worktree operations.
// Prefers the shared bare repo (.repo.git) if it exists, otherwise falls back to mayor/rig.
// The bare repo architecture allows all worktrees (refinery, polecats) to share branch visibility.
func (m *Manager) repoBase() (*git.Git, error) {
// First check for shared bare repo (new architecture)
bareRepoPath := filepath.Join(m.rig.Path, ".repo.git")
if info, err := os.Stat(bareRepoPath); err == nil && info.IsDir() {
// Bare repo exists - use it
return git.NewGitWithDir(bareRepoPath, ""), nil
}
// Fall back to mayor/rig (legacy architecture)
mayorPath := filepath.Join(m.rig.Path, "mayor", "rig")
if _, err := os.Stat(mayorPath); os.IsNotExist(err) {
return nil, fmt.Errorf("no repo base found (neither .repo.git nor mayor/rig exists)")
}
return git.NewGit(mayorPath), nil
}
// polecatDir returns the directory for a polecat. // polecatDir returns the directory for a polecat.
func (m *Manager) polecatDir(name string) string { func (m *Manager) polecatDir(name string) string {
return filepath.Join(m.rig.Path, "polecats", name) return filepath.Join(m.rig.Path, "polecats", name)
@@ -93,8 +112,9 @@ func (m *Manager) exists(name string) bool {
return err == nil return err == nil
} }
// Add creates a new polecat as a git worktree from the mayor's clone. // Add creates a new polecat as a git worktree from the repo base.
// This is much faster than a full clone and shares objects with the mayor. // Uses the shared bare repo (.repo.git) if available, otherwise mayor/rig.
// This is much faster than a full clone and shares objects with all worktrees.
// Polecat state is derived from beads assignee field, not state.json. // Polecat state is derived from beads assignee field, not state.json.
func (m *Manager) Add(name string) (*Polecat, error) { func (m *Manager) Add(name string) (*Polecat, error) {
if m.exists(name) { if m.exists(name) {
@@ -110,17 +130,14 @@ func (m *Manager) Add(name string) (*Polecat, error) {
return nil, fmt.Errorf("creating polecats dir: %w", err) return nil, fmt.Errorf("creating polecats dir: %w", err)
} }
// Use Mayor's clone as the base for worktrees (Mayor is canonical for the rig) // Get the repo base (bare repo or mayor/rig)
mayorPath := filepath.Join(m.rig.Path, "mayor", "rig") repoGit, err := m.repoBase()
mayorGit := git.NewGit(mayorPath) if err != nil {
return nil, fmt.Errorf("finding repo base: %w", err)
// Verify Mayor's clone exists
if _, err := os.Stat(mayorPath); os.IsNotExist(err) {
return nil, fmt.Errorf("mayor clone not found at %s (run 'gt rig add' to set up rig structure)", mayorPath)
} }
// Check if branch already exists (e.g., from previous polecat that wasn't cleaned up) // Check if branch already exists (e.g., from previous polecat that wasn't cleaned up)
branchExists, err := mayorGit.BranchExists(branchName) branchExists, err := repoGit.BranchExists(branchName)
if err != nil { if err != nil {
return nil, fmt.Errorf("checking branch existence: %w", err) return nil, fmt.Errorf("checking branch existence: %w", err)
} }
@@ -128,13 +145,13 @@ func (m *Manager) Add(name string) (*Polecat, error) {
// Create worktree - reuse existing branch if it exists // Create worktree - reuse existing branch if it exists
if branchExists { if branchExists {
// Branch exists, create worktree using existing branch // Branch exists, create worktree using existing branch
if err := mayorGit.WorktreeAddExisting(polecatPath, branchName); err != nil { if err := repoGit.WorktreeAddExisting(polecatPath, branchName); err != nil {
return nil, fmt.Errorf("creating worktree with existing branch: %w", err) return nil, fmt.Errorf("creating worktree with existing branch: %w", err)
} }
} else { } else {
// Create new branch with worktree // Create new branch with worktree
// git worktree add -b polecat/<name> <path> // git worktree add -b polecat/<name> <path>
if err := mayorGit.WorktreeAdd(polecatPath, branchName); err != nil { if err := repoGit.WorktreeAdd(polecatPath, branchName); err != nil {
return nil, fmt.Errorf("creating worktree: %w", err) return nil, fmt.Errorf("creating worktree: %w", err)
} }
} }
@@ -197,12 +214,15 @@ func (m *Manager) RemoveWithOptions(name string, force, nuclear bool) error {
} }
} }
// Use Mayor's clone to remove the worktree properly // Get repo base to remove the worktree properly
mayorPath := filepath.Join(m.rig.Path, "mayor", "rig") repoGit, err := m.repoBase()
mayorGit := git.NewGit(mayorPath) if err != nil {
// Fall back to direct removal if repo base not found
return os.RemoveAll(polecatPath)
}
// Try to remove as a worktree first (use force flag for worktree removal too) // Try to remove as a worktree first (use force flag for worktree removal too)
if err := mayorGit.WorktreeRemove(polecatPath, force); err != nil { if err := repoGit.WorktreeRemove(polecatPath, force); err != nil {
// Fall back to direct removal if worktree removal fails // Fall back to direct removal if worktree removal fails
// (e.g., if this is an old-style clone, not a worktree) // (e.g., if this is an old-style clone, not a worktree)
if removeErr := os.RemoveAll(polecatPath); removeErr != nil { if removeErr := os.RemoveAll(polecatPath); removeErr != nil {
@@ -211,7 +231,7 @@ func (m *Manager) RemoveWithOptions(name string, force, nuclear bool) error {
} }
// Prune any stale worktree entries // Prune any stale worktree entries
_ = mayorGit.WorktreePrune() _ = repoGit.WorktreePrune()
// Release name back to pool if it's a pooled name // Release name back to pool if it's a pooled name
m.namePool.Release(name) m.namePool.Release(name)
@@ -257,10 +277,14 @@ func (m *Manager) Recreate(name string, force bool) (*Polecat, error) {
polecatPath := m.polecatDir(name) polecatPath := m.polecatDir(name)
branchName := fmt.Sprintf("polecat/%s", name) branchName := fmt.Sprintf("polecat/%s", name)
mayorPath := filepath.Join(m.rig.Path, "mayor", "rig")
mayorGit := git.NewGit(mayorPath)
polecatGit := git.NewGit(polecatPath) polecatGit := git.NewGit(polecatPath)
// Get the repo base (bare repo or mayor/rig)
repoGit, err := m.repoBase()
if err != nil {
return nil, fmt.Errorf("finding repo base: %w", err)
}
// Check for uncommitted work unless forced // Check for uncommitted work unless forced
if !force { if !force {
status, err := polecatGit.CheckUncommittedWork() status, err := polecatGit.CheckUncommittedWork()
@@ -270,7 +294,7 @@ func (m *Manager) Recreate(name string, force bool) (*Polecat, error) {
} }
// Remove the worktree (use force for git worktree removal) // Remove the worktree (use force for git worktree removal)
if err := mayorGit.WorktreeRemove(polecatPath, true); err != nil { if err := repoGit.WorktreeRemove(polecatPath, true); err != nil {
// Fall back to direct removal // Fall back to direct removal
if removeErr := os.RemoveAll(polecatPath); removeErr != nil { if removeErr := os.RemoveAll(polecatPath); removeErr != nil {
return nil, fmt.Errorf("removing polecat dir: %w", removeErr) return nil, fmt.Errorf("removing polecat dir: %w", removeErr)
@@ -278,14 +302,14 @@ func (m *Manager) Recreate(name string, force bool) (*Polecat, error) {
} }
// Prune stale worktree entries // Prune stale worktree entries
_ = mayorGit.WorktreePrune() _ = repoGit.WorktreePrune()
// Delete the old branch so worktree starts fresh from current HEAD // Delete the old branch so worktree starts fresh from current HEAD
// Ignore error - branch may not exist (first recreate) or may fail to delete // Ignore error - branch may not exist (first recreate) or may fail to delete
_ = mayorGit.DeleteBranch(branchName, true) _ = repoGit.DeleteBranch(branchName, true)
// Check if branch still exists (deletion may have failed or branch was protected) // Check if branch still exists (deletion may have failed or branch was protected)
branchExists, err := mayorGit.BranchExists(branchName) branchExists, err := repoGit.BranchExists(branchName)
if err != nil { if err != nil {
return nil, fmt.Errorf("checking branch existence: %w", err) return nil, fmt.Errorf("checking branch existence: %w", err)
} }
@@ -294,12 +318,12 @@ func (m *Manager) Recreate(name string, force bool) (*Polecat, error) {
if branchExists { if branchExists {
// Branch still exists, create worktree using existing branch // Branch still exists, create worktree using existing branch
// This happens if delete failed (e.g., protected branch) // This happens if delete failed (e.g., protected branch)
if err := mayorGit.WorktreeAddExisting(polecatPath, branchName); err != nil { if err := repoGit.WorktreeAddExisting(polecatPath, branchName); err != nil {
return nil, fmt.Errorf("creating worktree with existing branch: %w", err) return nil, fmt.Errorf("creating worktree with existing branch: %w", err)
} }
} else { } else {
// Branch was deleted, create fresh worktree with new branch from HEAD // Branch was deleted, create fresh worktree with new branch from HEAD
if err := mayorGit.WorktreeAdd(polecatPath, branchName); err != nil { if err := repoGit.WorktreeAdd(polecatPath, branchName); err != nil {
return nil, fmt.Errorf("creating fresh worktree: %w", err) return nil, fmt.Errorf("creating fresh worktree: %w", err)
} }
} }
+20 -8
View File
@@ -217,28 +217,40 @@ func (m *Manager) AddRig(opts AddRigOptions) (*Rig, error) {
return nil, fmt.Errorf("saving rig config: %w", err) return nil, fmt.Errorf("saving rig config: %w", err)
} }
// Clone repository for mayor (must be first - serves as base for worktrees) // Create shared bare repo as single source of truth for all worktrees.
// This architecture allows all worktrees (mayor, refinery, polecats) to share
// branch visibility without needing to push to remote.
bareRepoPath := filepath.Join(rigPath, ".repo.git")
if err := m.git.CloneBare(opts.GitURL, bareRepoPath); err != nil {
return nil, fmt.Errorf("creating bare repo: %w", err)
}
bareGit := git.NewGitWithDir(bareRepoPath, "")
// Create mayor as worktree from bare repo on main
mayorRigPath := filepath.Join(rigPath, "mayor", "rig") mayorRigPath := filepath.Join(rigPath, "mayor", "rig")
if err := os.MkdirAll(filepath.Dir(mayorRigPath), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(mayorRigPath), 0755); err != nil {
return nil, fmt.Errorf("creating mayor dir: %w", err) return nil, fmt.Errorf("creating mayor dir: %w", err)
} }
if err := m.git.Clone(opts.GitURL, mayorRigPath); err != nil { if err := bareGit.WorktreeAddExisting(mayorRigPath, "main"); err != nil {
return nil, fmt.Errorf("cloning for mayor: %w", err) return nil, fmt.Errorf("creating mayor worktree: %w", err)
} }
// Create mayor CLAUDE.md (overrides any from cloned repo) // Create mayor CLAUDE.md (overrides any from cloned repo)
if err := m.createRoleCLAUDEmd(mayorRigPath, "mayor", opts.Name, ""); err != nil { if err := m.createRoleCLAUDEmd(mayorRigPath, "mayor", opts.Name, ""); err != nil {
return nil, fmt.Errorf("creating mayor CLAUDE.md: %w", err) return nil, fmt.Errorf("creating mayor CLAUDE.md: %w", err)
} }
// Create refinery as a worktree of mayor's clone. // Create refinery as worktree from bare repo on main.
// This allows refinery to see polecat branches locally (shared .git). // Refinery stays on main to merge polecat branches into main.
// Refinery uses the "refinery" branch which tracks main. // Uses the same main branch as mayor - they share the working copy.
refineryRigPath := filepath.Join(rigPath, "refinery", "rig") refineryRigPath := filepath.Join(rigPath, "refinery", "rig")
if err := os.MkdirAll(filepath.Dir(refineryRigPath), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(refineryRigPath), 0755); err != nil {
return nil, fmt.Errorf("creating refinery dir: %w", err) return nil, fmt.Errorf("creating refinery dir: %w", err)
} }
mayorGit := git.NewGit(mayorRigPath) // Create a refinery branch from main for the worktree
if err := mayorGit.WorktreeAdd(refineryRigPath, "refinery"); err != nil { if err := bareGit.CreateBranchFrom("refinery", "main"); err != nil {
return nil, fmt.Errorf("creating refinery branch: %w", err)
}
if err := bareGit.WorktreeAddExisting(refineryRigPath, "refinery"); err != nil {
return nil, fmt.Errorf("creating refinery worktree: %w", err) return nil, fmt.Errorf("creating refinery worktree: %w", err)
} }
// Create refinery CLAUDE.md (overrides any from cloned repo) // Create refinery CLAUDE.md (overrides any from cloned repo)