Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions pkg/generators/git_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,40 @@ func (g *GitGenerator) Write(overwrite ...bool) error {
}

existingLines := make(map[string]struct{})
commentedNormalized := make(map[string]struct{})
var unmanagedLines []string
lines := strings.Split(string(content), "\n")
for i, line := range lines {
existingLines[line] = struct{}{}

// Track normalized commented versions of Windsor entries
trimmed := strings.TrimLeft(line, " \t")
if strings.HasPrefix(trimmed, "#") {
norm := normalizeGitignoreComment(trimmed)
if norm != "" {
commentedNormalized[norm] = struct{}{}
}
}

if i == len(lines)-1 && line == "" {
continue
}
unmanagedLines = append(unmanagedLines, line)
}

for _, line := range gitIgnoreLines {
if _, exists := existingLines[line]; !exists {
if line == "# managed by windsor cli" {
if line == "# managed by windsor cli" {
if _, exists := existingLines[line]; !exists {
unmanagedLines = append(unmanagedLines, "")
unmanagedLines = append(unmanagedLines, line)
}
continue
}

if _, exists := existingLines[line]; !exists {
if _, commentedExists := commentedNormalized[line]; !commentedExists {
unmanagedLines = append(unmanagedLines, line)
}
unmanagedLines = append(unmanagedLines, line)
}
}

Expand All @@ -109,6 +127,23 @@ func (g *GitGenerator) Write(overwrite ...bool) error {
return nil
}

// =============================================================================
// Helper Functions
// =============================================================================

// normalizeGitignoreComment normalizes a commented .gitignore line to its uncommented form.
// It removes all leading #, whitespace, and trailing whitespace.
func normalizeGitignoreComment(line string) string {
trimmed := strings.TrimLeft(line, " \t")
if !strings.HasPrefix(trimmed, "#") {
return ""
}
// Remove all leading # and whitespace after #
noHash := strings.TrimLeft(trimmed, "#")
noHash = strings.TrimLeft(noHash, " \t")
return strings.TrimSpace(noHash)
}

// =============================================================================
// Interface Compliance
// =============================================================================
Expand Down
78 changes: 78 additions & 0 deletions pkg/generators/git_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io/fs"
"os"
"path/filepath"
"strings"
"testing"
)

Expand Down Expand Up @@ -235,4 +236,81 @@ func TestGitGenerator_Write(t *testing.T) {
t.Errorf("expected error %s, got %s", expectedError, err.Error())
}
})

t.Run("HandlesCommentedOutLines", func(t *testing.T) {
// Given a GitGenerator with mocks
generator, mocks := setup(t)

// And GetProjectRoot is mocked to return a specific path
mocks.Shell.GetProjectRootFunc = func() (string, error) {
return filepath.Join("mock", "project", "root"), nil
}

// And ReadFile is mocked to return content with various commented out Windsor entries
commentedContent := "existing content\n# .aws/\n # .aws/\n# .aws/\n## .aws/\n#\t.aws/\n# .aws/ \n#contexts/**/.terraform/\n# contexts/**/.terraform/ "
commentedContent = strings.ReplaceAll(commentedContent, "#\t.aws/", "#\t.aws/")
mocks.Shims.ReadFile = func(path string) ([]byte, error) {
expectedPath := filepath.Join("mock", "project", "root", ".gitignore")
if path == expectedPath {
return []byte(commentedContent), nil
}
return nil, fmt.Errorf("unexpected file read: %s", path)
}

// And WriteFile is mocked to verify the content
var writtenContent []byte
mocks.Shims.WriteFile = func(path string, content []byte, _ fs.FileMode) error {
writtenContent = content
return nil
}

// When Write is called
err := generator.Write()

// Then no error should occur
if err != nil {
t.Errorf("expected no error, got %v", err)
}

// And the content should preserve all commented lines and not add uncommented duplicates
actualContent := string(writtenContent)
commentVariants := []string{
"# .aws/",
" # .aws/",
"# .aws/",
"## .aws/",
"#\t.aws/",
"# .aws/ ",
"#contexts/**/.terraform/",
"# contexts/**/.terraform/ ",
}
commentVariants[4] = "#\t.aws/"
for i, variant := range commentVariants {
if i == 4 {
variant = "#\t.aws/"
}
if !strings.Contains(actualContent, variant) {
t.Errorf("expected content to preserve commented variant: %q", variant)
}
}

// Check that uncommented versions are NOT added when any commented version exists
lines := strings.Split(actualContent, "\n")
hasUncommentedAws := false
hasUncommentedTerraform := false
for _, line := range lines {
if strings.TrimSpace(line) == ".aws/" {
hasUncommentedAws = true
}
if strings.TrimSpace(line) == "contexts/**/.terraform/" {
hasUncommentedTerraform = true
}
}
if hasUncommentedAws {
t.Errorf("expected content to not add uncommented .aws/ when any commented version exists")
}
if hasUncommentedTerraform {
t.Errorf("expected content to not add uncommented contexts/**/.terraform/ when any commented version exists")
}
})
}
Loading