Merge pull request #276 from joshuavial/feat/crew-list-all
feat: add --all to gt crew list
This commit is contained in:
@@ -19,6 +19,7 @@ var (
|
||||
crewAccount string
|
||||
crewAgentOverride string
|
||||
crewAll bool
|
||||
crewListAll bool
|
||||
crewDryRun bool
|
||||
)
|
||||
|
||||
@@ -77,7 +78,8 @@ Shows git branch, session state, and git status for each workspace.
|
||||
|
||||
Examples:
|
||||
gt crew list # List in current rig
|
||||
gt crew list --rig greenplace # List in specific rig
|
||||
gt crew list --rig greenplace # List in specific rig
|
||||
gt crew list --all # List in all rigs
|
||||
gt crew list --json # JSON output`,
|
||||
RunE: runCrewList,
|
||||
}
|
||||
@@ -323,6 +325,7 @@ func init() {
|
||||
crewAddCmd.Flags().BoolVar(&crewBranch, "branch", false, "Create a feature branch (crew/<name>)")
|
||||
|
||||
crewListCmd.Flags().StringVar(&crewRig, "rig", "", "Filter by rig name")
|
||||
crewListCmd.Flags().BoolVar(&crewListAll, "all", false, "List crew workspaces in all rigs")
|
||||
crewListCmd.Flags().BoolVar(&crewJSON, "json", false, "Output as JSON")
|
||||
|
||||
crewAtCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use")
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/crew"
|
||||
"github.com/steveyegge/gastown/internal/git"
|
||||
"github.com/steveyegge/gastown/internal/rig"
|
||||
"github.com/steveyegge/gastown/internal/style"
|
||||
"github.com/steveyegge/gastown/internal/tmux"
|
||||
)
|
||||
@@ -22,43 +24,63 @@ type CrewListItem struct {
|
||||
}
|
||||
|
||||
func runCrewList(cmd *cobra.Command, args []string) error {
|
||||
crewMgr, r, err := getCrewManager(crewRig)
|
||||
if err != nil {
|
||||
return err
|
||||
if crewListAll && crewRig != "" {
|
||||
return fmt.Errorf("cannot use --all with --rig")
|
||||
}
|
||||
|
||||
workers, err := crewMgr.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing crew workers: %w", err)
|
||||
}
|
||||
|
||||
if len(workers) == 0 {
|
||||
fmt.Println("No crew workspaces found.")
|
||||
return nil
|
||||
var rigs []*rig.Rig
|
||||
if crewListAll {
|
||||
allRigs, _, err := getAllRigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rigs = allRigs
|
||||
} else {
|
||||
_, r, err := getCrewManager(crewRig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rigs = []*rig.Rig{r}
|
||||
}
|
||||
|
||||
// Check session and git status for each worker
|
||||
t := tmux.NewTmux()
|
||||
var items []CrewListItem
|
||||
|
||||
for _, w := range workers {
|
||||
sessionID := crewSessionName(r.Name, w.Name)
|
||||
hasSession, _ := t.HasSession(sessionID)
|
||||
for _, r := range rigs {
|
||||
crewGit := git.NewGit(r.Path)
|
||||
crewMgr := crew.NewManager(r, crewGit)
|
||||
|
||||
crewGit := git.NewGit(w.ClonePath)
|
||||
gitClean := true
|
||||
if status, err := crewGit.Status(); err == nil {
|
||||
gitClean = status.Clean
|
||||
workers, err := crewMgr.List()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: failed to list crew workers in %s: %v\n", r.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
items = append(items, CrewListItem{
|
||||
Name: w.Name,
|
||||
Rig: r.Name,
|
||||
Branch: w.Branch,
|
||||
Path: w.ClonePath,
|
||||
HasSession: hasSession,
|
||||
GitClean: gitClean,
|
||||
})
|
||||
for _, w := range workers {
|
||||
sessionID := crewSessionName(r.Name, w.Name)
|
||||
hasSession, _ := t.HasSession(sessionID)
|
||||
|
||||
workerGit := git.NewGit(w.ClonePath)
|
||||
gitClean := true
|
||||
if status, err := workerGit.Status(); err == nil {
|
||||
gitClean = status.Clean
|
||||
}
|
||||
|
||||
items = append(items, CrewListItem{
|
||||
Name: w.Name,
|
||||
Rig: r.Name,
|
||||
Branch: w.Branch,
|
||||
Path: w.ClonePath,
|
||||
HasSession: hasSession,
|
||||
GitClean: gitClean,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
fmt.Println("No crew workspaces found.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if crewJSON {
|
||||
|
||||
127
internal/cmd/crew_list_test.go
Normal file
127
internal/cmd/crew_list_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
)
|
||||
|
||||
func setupTestTownForCrewList(t *testing.T, rigs map[string][]string) string {
|
||||
t.Helper()
|
||||
|
||||
townRoot := t.TempDir()
|
||||
mayorDir := filepath.Join(townRoot, "mayor")
|
||||
if err := os.MkdirAll(mayorDir, 0755); err != nil {
|
||||
t.Fatalf("mkdir mayor: %v", err)
|
||||
}
|
||||
|
||||
townConfig := &config.TownConfig{
|
||||
Type: "town",
|
||||
Version: config.CurrentTownVersion,
|
||||
Name: "test-town",
|
||||
PublicName: "Test Town",
|
||||
CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
if err := config.SaveTownConfig(filepath.Join(mayorDir, "town.json"), townConfig); err != nil {
|
||||
t.Fatalf("save town.json: %v", err)
|
||||
}
|
||||
|
||||
rigsConfig := &config.RigsConfig{
|
||||
Version: config.CurrentRigsVersion,
|
||||
Rigs: make(map[string]config.RigEntry),
|
||||
}
|
||||
|
||||
for rigName, crewNames := range rigs {
|
||||
rigsConfig.Rigs[rigName] = config.RigEntry{
|
||||
GitURL: "https://example.com/" + rigName + ".git",
|
||||
AddedAt: time.Now(),
|
||||
}
|
||||
|
||||
rigPath := filepath.Join(townRoot, rigName)
|
||||
crewDir := filepath.Join(rigPath, "crew")
|
||||
if err := os.MkdirAll(crewDir, 0755); err != nil {
|
||||
t.Fatalf("mkdir crew dir: %v", err)
|
||||
}
|
||||
for _, crewName := range crewNames {
|
||||
if err := os.MkdirAll(filepath.Join(crewDir, crewName), 0755); err != nil {
|
||||
t.Fatalf("mkdir crew worker: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := config.SaveRigsConfig(filepath.Join(mayorDir, "rigs.json"), rigsConfig); err != nil {
|
||||
t.Fatalf("save rigs.json: %v", err)
|
||||
}
|
||||
|
||||
return townRoot
|
||||
}
|
||||
|
||||
func TestRunCrewList_AllWithRigErrors(t *testing.T) {
|
||||
townRoot := setupTestTownForCrewList(t, map[string][]string{"rig-a": {"alice"}})
|
||||
|
||||
originalWd, _ := os.Getwd()
|
||||
defer os.Chdir(originalWd)
|
||||
if err := os.Chdir(townRoot); err != nil {
|
||||
t.Fatalf("chdir: %v", err)
|
||||
}
|
||||
|
||||
crewListAll = true
|
||||
crewRig = "rig-a"
|
||||
defer func() {
|
||||
crewListAll = false
|
||||
crewRig = ""
|
||||
}()
|
||||
|
||||
err := runCrewList(&cobra.Command{}, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for --all with --rig, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCrewList_AllAggregatesJSON(t *testing.T) {
|
||||
townRoot := setupTestTownForCrewList(t, map[string][]string{
|
||||
"rig-a": {"alice"},
|
||||
"rig-b": {"bob"},
|
||||
})
|
||||
|
||||
originalWd, _ := os.Getwd()
|
||||
defer os.Chdir(originalWd)
|
||||
if err := os.Chdir(townRoot); err != nil {
|
||||
t.Fatalf("chdir: %v", err)
|
||||
}
|
||||
|
||||
crewListAll = true
|
||||
crewJSON = true
|
||||
crewRig = ""
|
||||
defer func() {
|
||||
crewListAll = false
|
||||
crewJSON = false
|
||||
}()
|
||||
|
||||
output := captureStdout(t, func() {
|
||||
if err := runCrewList(&cobra.Command{}, nil); err != nil {
|
||||
t.Fatalf("runCrewList failed: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
var items []CrewListItem
|
||||
if err := json.Unmarshal([]byte(output), &items); err != nil {
|
||||
t.Fatalf("unmarshal output: %v", err)
|
||||
}
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("expected 2 crew workers, got %d", len(items))
|
||||
}
|
||||
|
||||
rigs := map[string]bool{}
|
||||
for _, item := range items {
|
||||
rigs[item.Rig] = true
|
||||
}
|
||||
if !rigs["rig-a"] || !rigs["rig-b"] {
|
||||
t.Fatalf("expected crew from rig-a and rig-b, got: %#v", rigs)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user