fix: ensure gitignore patterns on role creation
Add EnsureGitignorePatterns to rig package that ensures .gitignore has required Gas Town patterns (.runtime/, .claude/, .beads/, .logs/). Called from crew and polecat managers when creating new workers. This prevents runtime-gitignore warnings from gt doctor. The function: - Creates .gitignore if it doesn't exist - Appends missing patterns to existing files - Recognizes pattern variants (.runtime vs .runtime/) - Adds "# Gas Town" header when appending Includes comprehensive tests for all scenarios.
This commit is contained in:
committed by
Steve Yegge
parent
05ea767149
commit
2aadb0165b
@@ -249,3 +249,193 @@ func TestCopyFilePreserveMode_NonexistentSource(t *testing.T) {
|
||||
t.Error("copyFilePreserveMode() with nonexistent source should return error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureGitignorePatterns_CreatesNewFile(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
err := EnsureGitignorePatterns(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("EnsureGitignorePatterns() error = %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(tmpDir, ".gitignore"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read .gitignore: %v", err)
|
||||
}
|
||||
|
||||
// Check all required patterns are present
|
||||
patterns := []string{".runtime/", ".claude/", ".beads/", ".logs/"}
|
||||
for _, pattern := range patterns {
|
||||
if !containsLine(string(content), pattern) {
|
||||
t.Errorf(".gitignore missing pattern %q", pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureGitignorePatterns_AppendsToExisting(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create existing .gitignore with some content
|
||||
existing := "node_modules/\n*.log\n"
|
||||
if err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte(existing), 0644); err != nil {
|
||||
t.Fatalf("Failed to create .gitignore: %v", err)
|
||||
}
|
||||
|
||||
err := EnsureGitignorePatterns(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("EnsureGitignorePatterns() error = %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(tmpDir, ".gitignore"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read .gitignore: %v", err)
|
||||
}
|
||||
|
||||
// Should preserve existing content
|
||||
if !containsLine(string(content), "node_modules/") {
|
||||
t.Error("Existing pattern node_modules/ was removed")
|
||||
}
|
||||
|
||||
// Should add header
|
||||
if !containsLine(string(content), "# Gas Town (added by gt)") {
|
||||
t.Error("Missing Gas Town header comment")
|
||||
}
|
||||
|
||||
// Should add required patterns
|
||||
patterns := []string{".runtime/", ".claude/", ".beads/", ".logs/"}
|
||||
for _, pattern := range patterns {
|
||||
if !containsLine(string(content), pattern) {
|
||||
t.Errorf(".gitignore missing pattern %q", pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureGitignorePatterns_SkipsExistingPatterns(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create existing .gitignore with some Gas Town patterns already
|
||||
existing := ".runtime/\n.claude/\n"
|
||||
if err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte(existing), 0644); err != nil {
|
||||
t.Fatalf("Failed to create .gitignore: %v", err)
|
||||
}
|
||||
|
||||
err := EnsureGitignorePatterns(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("EnsureGitignorePatterns() error = %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(tmpDir, ".gitignore"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read .gitignore: %v", err)
|
||||
}
|
||||
|
||||
// Should not duplicate existing patterns
|
||||
count := countOccurrences(string(content), ".runtime/")
|
||||
if count != 1 {
|
||||
t.Errorf(".runtime/ appears %d times, expected 1", count)
|
||||
}
|
||||
|
||||
// Should add missing patterns
|
||||
if !containsLine(string(content), ".beads/") {
|
||||
t.Error(".gitignore missing pattern .beads/")
|
||||
}
|
||||
if !containsLine(string(content), ".logs/") {
|
||||
t.Error(".gitignore missing pattern .logs/")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureGitignorePatterns_RecognizesVariants(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create existing .gitignore with variant patterns (without trailing slash)
|
||||
existing := ".runtime\n/.claude\n"
|
||||
if err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte(existing), 0644); err != nil {
|
||||
t.Fatalf("Failed to create .gitignore: %v", err)
|
||||
}
|
||||
|
||||
err := EnsureGitignorePatterns(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("EnsureGitignorePatterns() error = %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(tmpDir, ".gitignore"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read .gitignore: %v", err)
|
||||
}
|
||||
|
||||
// Should recognize variants and not add duplicates
|
||||
// .runtime (no slash) should count as .runtime/
|
||||
if containsLine(string(content), ".runtime/") && containsLine(string(content), ".runtime") {
|
||||
// Only one should be present unless they're the same line
|
||||
runtimeCount := countOccurrences(string(content), ".runtime")
|
||||
if runtimeCount > 1 {
|
||||
t.Errorf(".runtime appears %d times (variant detection failed)", runtimeCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureGitignorePatterns_AllPatternsPresent(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create existing .gitignore with all required patterns
|
||||
existing := ".runtime/\n.claude/\n.beads/\n.logs/\n"
|
||||
if err := os.WriteFile(filepath.Join(tmpDir, ".gitignore"), []byte(existing), 0644); err != nil {
|
||||
t.Fatalf("Failed to create .gitignore: %v", err)
|
||||
}
|
||||
|
||||
err := EnsureGitignorePatterns(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("EnsureGitignorePatterns() error = %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(tmpDir, ".gitignore"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read .gitignore: %v", err)
|
||||
}
|
||||
|
||||
// File should be unchanged (no header added)
|
||||
if containsLine(string(content), "# Gas Town") {
|
||||
t.Error("Should not add header when all patterns already present")
|
||||
}
|
||||
|
||||
// Content should match original
|
||||
if string(content) != existing {
|
||||
t.Errorf("File was modified when it shouldn't be.\nGot: %q\nWant: %q", string(content), existing)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func containsLine(content, pattern string) bool {
|
||||
for _, line := range splitLines(content) {
|
||||
if line == pattern {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func countOccurrences(content, pattern string) int {
|
||||
count := 0
|
||||
for _, line := range splitLines(content) {
|
||||
if line == pattern {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func splitLines(content string) []string {
|
||||
var lines []string
|
||||
start := 0
|
||||
for i, c := range content {
|
||||
if c == '\n' {
|
||||
lines = append(lines, content[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
if start < len(content) {
|
||||
lines = append(lines, content[start:])
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user