Gitignore Negation Patterns with ! Explained
Master gitignore negation with the ! prefix. Learn to ignore a directory but keep specific files, handle nested exceptions, and avoid common pitfalls.
Detailed Explanation
Gitignore negation (the ! prefix) lets you re-include files that were previously excluded by a broader pattern. It is one of the most powerful — and most confusing — features of .gitignore.
Basic syntax:
# Ignore everything in build/
build/*
# But keep the .gitkeep file
!build/.gitkeep
The ! prefix reverses a previous ignore rule for a specific file or pattern.
The critical gotcha — parent directories:
Negation cannot re-include a file if its parent directory is ignored. This is the number one source of confusion:
# THIS DOES NOT WORK:
build/
!build/important.txt
When git ignores build/ (with the trailing slash), it never looks inside that directory. The negation for important.txt is never evaluated. The fix is to use a wildcard:
# THIS WORKS:
build/*
!build/important.txt
Using build/* ignores the contents of build/ but still allows git to traverse the directory and discover the negated file.
Nested directory negation:
To keep a file deep in an ignored directory, you must negate every level:
# Ignore all of vendor except a specific package
vendor/*
!vendor/critical-package/
vendor/critical-package/*
!vendor/critical-package/src/
Each directory level must be explicitly allowed for git to traverse into it.
Common use cases:
- Keep empty directories — Git does not track empty directories. Use a
.gitkeepconvention:
logs/*
!logs/.gitkeep
- Share specific IDE settings:
.vscode/*
!.vscode/settings.json
!.vscode/extensions.json
- Ignore all except specific file types:
*
!*.py
!*/
Note: you must also negate !*/ to allow git to enter subdirectories and find matching files.
Order matters: Rules are processed top to bottom. Later rules override earlier ones. A negation must come AFTER the broader ignore rule it is meant to override.
Debugging: Use git check-ignore -v <path> to see exactly which .gitignore rule (and from which file) is affecting a specific path.
Use Case
A developer needs to ignore an entire vendor directory except for a locally patched package that must be tracked until the upstream fix is released.