From Glob Patterns to .gitignore: Building a Smart Generator#

Started a new project last week. After git init, I needed to add .gitignore. Opened GitHub’s gitignore template repo, copied, pasted, manually merged… after several iterations, still had unwanted files slipping through. Decided to build my own generator, consolidating common templates.

Understanding .gitignore Matching Rules#

.gitignore looks simple, but the matching rules are quite sophisticated. Core is glob patterns, but Git extends them:

Basic Syntax#

# Comments start with #
node_modules/      # Matches node_modules folder anywhere
*.log              # Matches all .log files
!important.log     # ! prefix means negation - don't ignore this file
/dist/             # Leading / means only match root directory
**/temp            # ** matches multiple directory levels

Common Pitfalls#

1. Directory vs File

dist               # Matches dist file or directory
dist/              # Only matches dist directory

If you have a dist folder, both work the same. But if there’s a file named dist, dist/ won’t match it.

2. Wildcard Scope

*.log              # Matches .log files in all directories
/*.log             # Only matches .log files in root

3. Negation Priority

*.log
!important.log     # important.log won't be ignored
!important/*.log   # .log files in important/ won't be ignored

Negation rules must come after ignore rules to take effect.

Template System Design#

I researched .gitignore rules for major tech stacks. Some are universal, others are stack-specific:

Universal Rules#

const commonRules = [
  '.env',              // Environment variables
  '.DS_Store',         // macOS system files
  '*.log',             // Log files
]

Node.js Specific#

const nodeRules = [
  'node_modules/',     // Dependencies
  'dist/',             // Build output
  'build/',
  '.next/',            // Next.js build directory
  'coverage/',         // Test coverage
  '.nyc_output/',      // NYC output
]

Python Specific#

const pythonRules = [
  '__pycache__/',      // Bytecode cache
  '*.py[cod]',         // Compiled files
  '*.so',              // Shared objects
  'venv/',             // Virtual environment
  '.pytest_cache/',    // pytest cache
  '.mypy_cache/',      // mypy cache
]

Java Specific#

const javaRules = [
  '*.class',           // Bytecode
  '*.jar',             // JAR files
  'target/',           // Maven build directory
  '.gradle/',          // Gradle cache
  '.idea/',            // IntelliJ IDEA config
  '*.iml',             // IDEA module files
]

Deduplication and Merging#

Users might select both Node.js and React templates, which have overlapping rules. Direct concatenation produces:

node_modules/
dist/
*.log
node_modules/    # Duplicate
dist/            # Duplicate
*.log            # Duplicate

Using Set for deduplication:

function generateGitignore(selectedTemplates: string[]): string {
  const rules = new Set<string>()
  
  selectedTemplates.forEach(key => {
    templates[key]?.forEach(rule => rules.add(rule))
  })
  
  return Array.from(rules).join('\n')
}

But this scrambles the order. Better approach - group by template:

function generateGitignore(selectedTemplates: string[]): string {
  const sections: string[] = []
  
  selectedTemplates.forEach(key => {
    const rules = templates[key] || []
    if (rules.length > 0) {
      sections.push(`# ${key}`)
      sections.push(...rules)
      sections.push('')  // Blank line separator
    }
  })
  
  return sections.join('\n')
}

Real-time Custom Rule Merging#

Users might need project-specific rules:

# Project specific
config/local.json
temp/
*.bak

Implementing real-time merge:

const [selectedTemplates, setSelectedTemplates] = useState<string[]>([])
const [customRules, setCustomRules] = useState('')

const output = useMemo(() => {
  const rules = new Set<string>()
  
  // Template rules
  selectedTemplates.forEach(key => {
    templates[key]?.forEach(rule => rules.add(rule))
  })
  
  // Custom rules
  customRules.split('\n')
    .map(line => line.trim())
    .filter(Boolean)
    .forEach(rule => rules.add(rule))
  
  return Array.from(rules).join('\n')
}, [selectedTemplates, customRules])

Using useMemo to cache results, avoiding recalculation on every render.

Frontend Interaction Design#

Template Selector#

Button group showing all templates, click to toggle:

function TemplateSelector({ templates, selected, onToggle }) {
  return (
    <div className="grid grid-cols-2 md:grid-cols-4 gap-2">
      {templates.map(key => (
        <button
          key={key}
          onClick={() => onToggle(key)}
          className={selected.includes(key) ? 'active' : ''}
        >
          {formatLabel(key)}
        </button>
      ))}
    </div>
  )
}

Real-time Preview#

Show results immediately after user interaction, no “Generate” button needed:

// output is computed property, automatically responds to changes
<pre>{output}</pre>

One-click Download#

Generate and download .gitignore file:

function handleDownload() {
  const blob = new Blob([output], { type: 'text/plain' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = '.gitignore'  // Filename starts with dot
  a.click()
  URL.revokeObjectURL(url)   // Release memory
}

Note URL.revokeObjectURL - not releasing causes memory leaks.

Edge Cases#

1. Empty Template#

User selects nothing, enters no custom rules:

if (output.length === 0) {
  return <EmptyState />
}

2. Rule Validation#

Users might enter invalid rules:

node_modules/ dist/  # Multiple rules on one line, invalid

Simple validation:

function validateRule(rule: string): boolean {
  // Empty lines or comments are valid
  if (rule.trim() === '' || rule.trim().startsWith('#')) {
    return true
  }
  
  // Simple check: no space-separated patterns
  if (rule.split(/\s+/).length > 1) {
    return false
  }
  
  return true
}

3. Special Characters#

Rules may contain special characters, but .gitignore doesn’t need escaping - save directly.

Final Result#

Based on these ideas, built this tool: .gitignore Generator

Key features:

  • 10+ tech stack templates (Node, Python, Java, Go, Rust, Swift, etc.)
  • Multi-template merge with auto-deduplication
  • Custom rule input
  • Real-time preview
  • One-click .gitignore download

Core code under 200 lines, but covers common scenarios. Hope this helps.


Related tools: Docker Compose Generator | Nginx Config Generator