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.

Pattern

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:

  1. Keep empty directories — Git does not track empty directories. Use a .gitkeep convention:
logs/*
!logs/.gitkeep
  1. Share specific IDE settings:
.vscode/*
!.vscode/settings.json
!.vscode/extensions.json
  1. 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.

Try It — .gitignore Generator

Open full tool