From 4fbe00e224ea2f2c281e175be083a2acbfc4dea6 Mon Sep 17 00:00:00 2001 From: dustin Date: Thu, 22 Jan 2026 01:31:16 +0700 Subject: [PATCH] fix: respect GT_TOWN_ROOT in quick-add command (#840) The quick-add command (used by shell hook's "Add to Gas Town?" prompt) previously only checked hardcoded paths ~/gt and ~/gastown, ignoring GT_TOWN_ROOT and any other Gas Town installations. This caused rigs to be added to the wrong town when users had multiple Gas Town installations (e.g., ~/gt and ~/Documents/code/gt). Fix the town discovery order: 1. GT_TOWN_ROOT env var (explicit user preference) 2. workspace.FindFromCwd() (supports multiple installations) 3. Fall back to ~/gt and ~/gastown --- internal/cmd/rig_quick_add.go | 23 +++++- internal/cmd/rig_quick_add_test.go | 113 +++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 internal/cmd/rig_quick_add_test.go diff --git a/internal/cmd/rig_quick_add.go b/internal/cmd/rig_quick_add.go index 640caccb..52337483 100644 --- a/internal/cmd/rig_quick_add.go +++ b/internal/cmd/rig_quick_add.go @@ -165,6 +165,19 @@ func sanitizeRigName(name string) string { } func findOrCreateTown() (string, error) { + // Priority 1: GT_TOWN_ROOT env var (explicit user preference) + if townRoot := os.Getenv("GT_TOWN_ROOT"); townRoot != "" { + if isValidTown(townRoot) { + return townRoot, nil + } + } + + // Priority 2: Try to find from cwd (supports multiple town installations) + if townRoot, err := workspace.FindFromCwd(); err == nil && townRoot != "" { + return townRoot, nil + } + + // Priority 3: Fall back to well-known locations home, err := os.UserHomeDir() if err != nil { return "", err @@ -176,11 +189,17 @@ func findOrCreateTown() (string, error) { } for _, path := range candidates { - mayorDir := filepath.Join(path, "mayor") - if _, err := os.Stat(mayorDir); err == nil { + if isValidTown(path) { return path, nil } } return "", fmt.Errorf("no Gas Town found - run 'gt install ~/gt' first") } + +// isValidTown checks if a path is a valid Gas Town installation. +func isValidTown(path string) bool { + mayorDir := filepath.Join(path, "mayor") + _, err := os.Stat(mayorDir) + return err == nil +} diff --git a/internal/cmd/rig_quick_add_test.go b/internal/cmd/rig_quick_add_test.go new file mode 100644 index 00000000..7325f870 --- /dev/null +++ b/internal/cmd/rig_quick_add_test.go @@ -0,0 +1,113 @@ +package cmd + +import ( + "os" + "path/filepath" + "testing" +) + +func TestFindOrCreateTown(t *testing.T) { + // Save original env and restore after test + origTownRoot := os.Getenv("GT_TOWN_ROOT") + defer os.Setenv("GT_TOWN_ROOT", origTownRoot) + + t.Run("respects GT_TOWN_ROOT when set", func(t *testing.T) { + // Create a valid town in temp dir + tmpTown := t.TempDir() + mayorDir := filepath.Join(tmpTown, "mayor") + if err := os.MkdirAll(mayorDir, 0755); err != nil { + t.Fatalf("mkdir mayor: %v", err) + } + + os.Setenv("GT_TOWN_ROOT", tmpTown) + + result, err := findOrCreateTown() + if err != nil { + t.Fatalf("findOrCreateTown() error = %v", err) + } + if result != tmpTown { + t.Errorf("findOrCreateTown() = %q, want %q", result, tmpTown) + } + }) + + t.Run("ignores invalid GT_TOWN_ROOT", func(t *testing.T) { + // Set GT_TOWN_ROOT to a non-existent path + os.Setenv("GT_TOWN_ROOT", "/nonexistent/path/to/town") + + // Create a valid town at ~/gt for fallback + home, err := os.UserHomeDir() + if err != nil { + t.Skip("cannot get home dir") + } + + gtPath := filepath.Join(home, "gt") + mayorDir := filepath.Join(gtPath, "mayor") + + // Skip if ~/gt doesn't exist (don't want to create it in user's home) + if _, err := os.Stat(mayorDir); os.IsNotExist(err) { + t.Skip("~/gt/mayor does not exist, skipping fallback test") + } + + result, err := findOrCreateTown() + if err != nil { + t.Fatalf("findOrCreateTown() error = %v", err) + } + // Should fall back to ~/gt since GT_TOWN_ROOT is invalid + if result != gtPath { + t.Logf("findOrCreateTown() = %q (fell back to valid town)", result) + } + }) + + t.Run("GT_TOWN_ROOT takes priority over fallback", func(t *testing.T) { + // Create two valid towns + tmpTown1 := t.TempDir() + tmpTown2 := t.TempDir() + + if err := os.MkdirAll(filepath.Join(tmpTown1, "mayor"), 0755); err != nil { + t.Fatalf("mkdir mayor1: %v", err) + } + if err := os.MkdirAll(filepath.Join(tmpTown2, "mayor"), 0755); err != nil { + t.Fatalf("mkdir mayor2: %v", err) + } + + // Set GT_TOWN_ROOT to tmpTown1 + os.Setenv("GT_TOWN_ROOT", tmpTown1) + + result, err := findOrCreateTown() + if err != nil { + t.Fatalf("findOrCreateTown() error = %v", err) + } + // Should use GT_TOWN_ROOT, not any other valid town + if result != tmpTown1 { + t.Errorf("findOrCreateTown() = %q, want %q (GT_TOWN_ROOT should take priority)", result, tmpTown1) + } + }) +} + +func TestIsValidTown(t *testing.T) { + t.Run("valid town has mayor directory", func(t *testing.T) { + tmpDir := t.TempDir() + mayorDir := filepath.Join(tmpDir, "mayor") + if err := os.MkdirAll(mayorDir, 0755); err != nil { + t.Fatalf("mkdir: %v", err) + } + + if !isValidTown(tmpDir) { + t.Error("isValidTown() = false, want true") + } + }) + + t.Run("invalid town missing mayor directory", func(t *testing.T) { + tmpDir := t.TempDir() + + if isValidTown(tmpDir) { + t.Error("isValidTown() = true, want false") + } + }) + + t.Run("nonexistent path is invalid", func(t *testing.T) { + if isValidTown("/nonexistent/path") { + t.Error("isValidTown() = true, want false") + } + }) +}