Add gt worktree list command (gt-xheo6.2)
Implements `gt worktree list` to show all cross-rig worktrees owned by the current crew member. Scans all rigs and shows git status summary for each worktree found. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,11 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/steveyegge/gastown/internal/config"
|
||||||
"github.com/steveyegge/gastown/internal/constants"
|
"github.com/steveyegge/gastown/internal/constants"
|
||||||
"github.com/steveyegge/gastown/internal/git"
|
"github.com/steveyegge/gastown/internal/git"
|
||||||
"github.com/steveyegge/gastown/internal/style"
|
"github.com/steveyegge/gastown/internal/style"
|
||||||
|
"github.com/steveyegge/gastown/internal/workspace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Worktree command flags
|
// Worktree command flags
|
||||||
@@ -44,8 +46,26 @@ Examples:
|
|||||||
RunE: runWorktree,
|
RunE: runWorktree,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var worktreeListCmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List all cross-rig worktrees owned by current crew member",
|
||||||
|
Long: `List all git worktrees created for cross-rig work.
|
||||||
|
|
||||||
|
This command scans all rigs in the workspace and finds worktrees
|
||||||
|
that belong to the current crew member. Each worktree is shown with
|
||||||
|
its git status summary.
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
Cross-rig worktrees for gastown/crew/joe:
|
||||||
|
|
||||||
|
beads ~/gt/beads/crew/gastown-joe/ (clean)
|
||||||
|
mayor ~/gt/mayor/crew/gastown-joe/ (2 uncommitted)`,
|
||||||
|
RunE: runWorktreeList,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
worktreeCmd.Flags().BoolVar(&worktreeNoCD, "no-cd", false, "Just print path (don't print cd command)")
|
worktreeCmd.Flags().BoolVar(&worktreeNoCD, "no-cd", false, "Just print path (don't print cd command)")
|
||||||
|
worktreeCmd.AddCommand(worktreeListCmd)
|
||||||
|
|
||||||
rootCmd.AddCommand(worktreeCmd)
|
rootCmd.AddCommand(worktreeCmd)
|
||||||
}
|
}
|
||||||
@@ -154,3 +174,89 @@ func setGitConfig(worktreePath, key, value string) error {
|
|||||||
cmd := exec.Command("git", "-C", worktreePath, "config", key, value)
|
cmd := exec.Command("git", "-C", worktreePath, "config", key, value)
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runWorktreeList(cmd *cobra.Command, args []string) error {
|
||||||
|
// Detect current crew identity from cwd
|
||||||
|
detected, err := detectCrewFromCwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("must be in a crew workspace to use this command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceRig := detected.rigName
|
||||||
|
crewName := detected.crewName
|
||||||
|
worktreeName := fmt.Sprintf("%s-%s", sourceRig, crewName)
|
||||||
|
|
||||||
|
// Find town root
|
||||||
|
townRoot, err := workspace.FindFromCwdOrError()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load rigs config to list all rigs
|
||||||
|
rigsConfigPath := constants.MayorRigsPath(townRoot)
|
||||||
|
rigsConfig, err := config.LoadRigsConfig(rigsConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading rigs config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Cross-rig worktrees for %s/crew/%s:\n\n", sourceRig, crewName)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for rigName := range rigsConfig.Rigs {
|
||||||
|
// Skip our own rig - worktrees are for cross-rig work
|
||||||
|
if rigName == sourceRig {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rig path is simply townRoot/<rigName>
|
||||||
|
rigPath := filepath.Join(townRoot, rigName)
|
||||||
|
|
||||||
|
// Check if worktree exists: <rig>/crew/<source-rig>-<name>/
|
||||||
|
worktreePath := filepath.Join(constants.RigCrewPath(rigPath), worktreeName)
|
||||||
|
|
||||||
|
if _, err := os.Stat(worktreePath); os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Worktree exists - get git status
|
||||||
|
statusSummary := getGitStatusSummary(worktreePath)
|
||||||
|
|
||||||
|
// Format the path for display (use ~ for home directory)
|
||||||
|
displayPath := worktreePath
|
||||||
|
if home, err := os.UserHomeDir(); err == nil {
|
||||||
|
if rel, err := filepath.Rel(home, worktreePath); err == nil && !filepath.IsAbs(rel) {
|
||||||
|
displayPath = "~/" + rel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" %-10s %s (%s)\n", rigName, displayPath, statusSummary)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
fmt.Printf(" (none)\n")
|
||||||
|
fmt.Printf("\nCreate a worktree with: gt worktree <rig>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGitStatusSummary returns a brief status summary for a git directory.
|
||||||
|
func getGitStatusSummary(dir string) string {
|
||||||
|
g := git.NewGit(dir)
|
||||||
|
|
||||||
|
// Check for uncommitted changes
|
||||||
|
status, err := g.Status()
|
||||||
|
if err != nil {
|
||||||
|
return "error"
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.Clean {
|
||||||
|
return "clean"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count uncommitted files (modified, added, deleted, untracked)
|
||||||
|
uncommitted := len(status.Modified) + len(status.Added) + len(status.Deleted) + len(status.Untracked)
|
||||||
|
|
||||||
|
return fmt.Sprintf("%d uncommitted", uncommitted)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user