Git.gitignore Patterns

.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

Text
# 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 /

Text
# 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 /

Text
# Match directories only (files named 'logs' would NOT match)
logs/

# Match both files and directories called 'cache'
cache
Wildcards
  • * — any sequence of characters except /. (*.log does NOT cross directories.)

  • ? — exactly one character (except /).

  • [abc] — one character from the set a, b, or c.

  • [a-z] — one character in the range a–z.

Examples

Text
*.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

**

Text
# 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
The difference between * and **
`*.log` matches `.log` files in the current directory only. It does NOT cross directories. `**/*.log` matches `.log` files at any depth. The double-star is what crosses folder boundaries.
Negation (re-including files)

!

Text
# Ignore everything in tmp/
tmp/*

# …except this one file
!tmp/keep.txt

# Ignore all .log files
*.log

# …except the access log
!access.log
Negation cannot revive a child of an ignored parent
If a directory is ignored, Git stops recursing into it — so `!ignored/keep.txt` does NOT work if `ignored/` is itself ignored. The trick is to ignore the *contents* (`ignored/*`) rather than the directory.
Comments and blank lines

Text
# 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

Text
# 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

Text
# 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?

Bash
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 .gitignore files 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 .gitignore for that operation.

What about Windows paths and forward slashes?

Always use forward slashes (/) in .gitignore, even on Windows. Git normalises paths internally.

Tip
When in doubt, run git check-ignore -v <path> before you change the file. It will save you many minutes of trial-and-error.