A Go library for programmatically managing .gitignore and .dockignore files with intelligent conflict detection and automatic resolution.
- π Intelligent conflict detection - Finds redundant, unreachable, and ineffective rules
- π§ Automatic conflict resolution - Smart reordering and cleanup of ignore files
- π Repository abstraction - Works with files, memory, or custom storage backends
- β Comprehensive validation - Prevents invalid patterns and configurations
- π― Type-safe rule creation - File, directory, extension, and glob patterns
go get github.com/MoonMoon1919/gignorepackage main
import (
"fmt"
"github.com/MoonMoon1919/gignore"
)
func main() {
// Create a new ignore file service
repo := gignore.NewFileRepository(gignore.RenderOptions{})
service := gignore.NewService(repo)
// Add rules to .gitignore
service.AddExtensionRule(".gitignore", "log", gignore.INCLUDE)
service.AddDirectoryRule(".gitignore", "build", gignore.RECURSIVE, gignore.INCLUDE)
service.AddFileRule(".gitignore", "config.json", gignore.INCLUDE)
// Create exceptions
service.AddFileRule(".gitignore", "build/important.txt", gignore.EXCLUDE)
// Automatically fix any conflicts
fixes, err := service.AutoFix(".gitignore", 10)
if err != nil {
panic(err)
}
for _, fix := range fixes {
fmt.Println("Applied:", fix)
}
}File Rules - Exact file paths
service.AddFileRule(path, "src/main.go", gignore.INCLUDE)
// Generates: src/main.goExtension Rules - File extensions
service.AddExtensionRule(path, "log", gignore.INCLUDE)
// Generates: *.logDirectory Rules - Directory patterns with different modes
// Just the directory
service.AddDirectoryRule(path, "build", gignore.DIRECTORY, gignore.INCLUDE)
// Generates: build/
// Everything in directory recursively
service.AddDirectoryRule(path, "build", gignore.RECURSIVE, gignore.INCLUDE)
// Generates: build/**
// Direct children only
service.AddDirectoryRule(path, "build", gignore.CHILDREN, gignore.INCLUDE)
// Generates: build/*
// Directory anywhere in tree
service.AddDirectoryRule(path, "temp", gignore.ANYWHERE, gignore.INCLUDE)
// Generates: **/temp
// Root-level only (leading slash)
service.AddDirectoryRule(path, "node_modules", gignore.ROOT_ONLY, gignore.INCLUDE)
// Generates: /node_modulesGlob Rules - Complex patterns
service.AddGlobRule(path, "temp*.log", gignore.INCLUDE)
// Generates: temp*.loggignore.INCLUDE- Ignore this pattern (no!prefix)gignore.EXCLUDE- Don't ignore this pattern (!prefix for exceptions)
The library automatically detects four types of conflicts:
Semantic Conflicts - Same pattern with opposite actions
config.json
!config.json # β Conflicting actionsRedundant Rules - Duplicate patterns
*.log
*.log # β RedundantUnreachable Rules - Broader patterns make specific ones meaningless
build/**
build/ # β Unreachable (build/** already covers this)Ineffective Rules - Exception rules with no prior exclusion
!build/important.txt # β Ineffective (nothing to override)
build/**// Load existing .gitignore
var ignoreFile gignore.IgnoreFile
err := repo.Load(".gitignore", &ignoreFile)
// Find conflicts
conflicts := ignoreFile.FindConflicts()
for _, conflict := range conflicts {
fmt.Printf("Conflict: %s between '%s' and '%s'\n",
conflict.ConflictType,
conflict.Left.Render(),
conflict.Right.Render())
}// Move a rule before or after another rule
result, err := service.MoveRule(".gitignore", "!important.txt", "build/**", gignore.AFTER)// Parse .gitignore content
content := `*.log
build/
!build/important.txt`
var ignoreFile gignore.IgnoreFile
err := gignore.Parse(content, &ignoreFile)The library uses explicit error types for better error handling:
err := service.AddFileRule(".gitignore", "config.json", gignore.INCLUDE)
if err != nil {
switch {
case errors.Is(err, gignore.SemanticConflictError):
// Handle semantic conflict
case errors.Is(err, gignore.RedundantRuleError):
// Handle redundant rule
case errors.Is(err, gignore.UnreachableRuleError):
// Handle unreachable rule
}
}The library includes comprehensive test coverage. The repository interface is simple to fake.
// Create fake repository for testing
type FakeRepository struct {
files map[string]string
}
func (f *FakeRepository) Load(path string, ignoreFile *IgnoreFile) error {
content, ok := f.files[path]
if !ok {
return fileReadError
}
return LoadFile(strings.NewReader(content), ignoreFile)
}
func (f *FakeRepository) Save(path string, ignoreFile *IgnoreFile) error {
content := Render(ignoreFile, RenderOptions{})
f.files[path] = content
return nil
}
func NewFakeRepository() FakeRepository {
return FakeRepository{
files: make(map[string]string),
}
}
service := gignore.NewService(repo)
// Test your ignore file logicSee CONTRIBUTING
This project is licensed under the MIT License - see the LICENSE file for details.
If you encounter any issues or have questions, please open an issue on GitHub.