.gitignore Patterns
.gitignore uses a small but expressive pattern language — similar to shell globs, with a handful of Git-specific additions. Knowing the rules saves hours of head-scratching when patterns do not match what you expected.
The cheat sheet
Pattern | Matches |
|---|---|
secret.txt | A file named secret.txt anywhere |
/secret.txt | secret.txt at the repo root only |
*.log | Any file ending in .log |
build/ | Any directory named build |
/build/ | A build/ directory only at the root |
logs/** | Everything inside logs/ recursively |
**/temp | Any temp directory anywhere |
docs/**/*.html | HTML files at any depth under docs/ |
!keep.log | Re-include keep.log even if *.log matches |
comment | Comment line — ignored |
Basic patterns
Plain names
# Match a specific file anywhere in the repo TODO.txt # Match a folder anywhere node_modules/ # Match by extension *.log *.tmp
Anchored to repo root
Leading /
# Match TODO.txt only at the repo root /TODO.txt # Match the top-level build/ folder only /build/
Without a leading slash, the pattern matches at any depth. With a leading slash, it is anchored to the directory the .gitignore lives in.
Directories only
Trailing /
# Match directories only (files named 'logs' would NOT match) logs/ # Match both files and directories called 'cache' cache
Wildcards
*— any sequence of characters except/. (*.logdoes NOT cross directories.)?— exactly one character (except/).[abc]— one character from the seta,b, orc.[a-z]— one character in the range a–z.
Examples
*.log # any .log file in any directory debug?.log # debug1.log, debugA.log, etc. debug[1-9].log # debug1.log through debug9.log log[!.txt] # NOT log.txt (negated character class)
Globstar — match across directories
**
# Anything inside any 'temp' folder, at any depth **/temp/ # Any .log file anywhere **/*.log # Anything inside docs, at any depth docs/** # All HTML files at any depth under docs/ docs/**/*.html
Negation (re-including files)
!
# Ignore everything in tmp/ tmp/* # …except this one file !tmp/keep.txt # Ignore all .log files *.log # …except the access log !access.log
Comments and blank lines
# This is a comment # Blank lines are also fine — they're ignored # Section heading *.log # Escape leading # if you literally have a file named '#' \#hash-named-file
Escaping special characters
# A file literally named '!foo' \!foo # A file literally containing a space (or just quote it in your shell) some\ file.txt
Real-world examples decoded
A common Node.js .gitignore explained
# Dependencies — matches node_modules at any depth node_modules/ # Per-environment build dirs at the root only /build/ /dist/ # Coverage reports — exact folder, any location coverage/ # Logs — any file ending in .log *.log # But keep this one specific log we DO want !important.log # Editor backup files anywhere *~ *.swp .DS_Store # Local env files — both .env and .env.something .env .env.* # But keep example files !.env.example
Debugging your patterns
Why is (or isn't) this file ignored?
git check-ignore -v path/to/some/file.log # .gitignore:3:*.log path/to/some/file.log # ^file ^line ^rule ^matched
That output is the single best debugging tool for ignore patterns. It tells you exactly which file and which line of which .gitignore is matching.
Order of evaluation
Git reads
.gitignorefiles from the directory of the file in question, up to the repo root. Closer files win.Inside each file, rules are evaluated top to bottom. The LAST matching rule wins.
.git/info/exclude(private) and the global excludesFile also apply, with reasonable precedence.Command-line ignores (
git add --force) override.gitignorefor that operation.
What about Windows paths and forward slashes?
Always use forward slashes (/) in .gitignore, even on Windows. Git normalises paths internally.
git check-ignore -v <path> before you change the file. It will save you many minutes of trial-and-error.