GitSearching Code (git grep)

Searching Code (git grep)

git grep is grep that knows about your repository. It only searches files tracked by Git, automatically skips .gitignored paths, is much faster than walking the working tree with plain grep, and can search across branches and historical revisions. If you write code in a Git repo, this is the search tool to learn first.

Why git grep beats plain grep
  • Honours .gitignore — never digs into node_modules, dist, or .next by accident.

  • Only tracked files — won't scan random scratch files lying in your working tree.

  • Multithreaded — uses all your cores by default.

  • Works on any revision — search what HEAD~10 looked like without checking it out.

  • Search across all history — pickaxe-style, by piping git rev-list.

The basics

Bash
git grep "TODO"                   # find "TODO" in tracked files
git grep -i "todo"                # case-insensitive
git grep -n "TODO"                # show line numbers
git grep -l "TODO"                # only file names that match
git grep -L "TODO"                # only file names that DON'T match
git grep -c "TODO"                # show count per file
git grep -w "log"                 # whole-word match (no "login", "logger")

Sample git grep -n output

Text
src/auth/login.js:42:  // TODO: handle expired tokens
src/checkout.js:18:    // TODO: refund flow
src/utils.js:7:        // TODO: replace with crypto.randomUUID
Restricting to file types

Bash
# After the pattern, "--" introduces pathspecs
git grep "useState" -- "*.tsx"
git grep "TODO" -- "src/**/*.js" ":!src/legacy/**"

# Multiple paths
git grep "import React" -- src/ tests/

# Exclude a directory
git grep "console.log" -- ":!**/node_modules/**"
Pathspec magic
Prefixing a path with `:!` excludes it. Prefixing with `:(glob)` forces shell-glob matching. These are the same pathspecs Git uses everywhere — they work in `git log`, `git diff`, and `git add` too.
Searching other revisions

Bash
# Search a specific commit/branch/tag without checking it out
git grep "TODO" HEAD~5
git grep "TODO" v1.0
git grep "TODO" origin/main -- src/

# Search across multiple revisions
git grep "TODO" main develop

When searching a ref, output prefixes the ref name

Text
v1.0:src/auth/login.js:42:  // TODO: handle expired tokens
v1.0:src/checkout.js:18:    // TODO: refund flow
Searching across ALL history

Find every commit's tree that ever contained a string

Bash
# Heavy operation — searches every commit
git grep "DEPRECATED_API" $(git rev-list --all)

# Only the heads of every branch
git grep "DEPRECATED_API" $(git rev-list --all --branches)

# Limit to commits in the last month
git grep "DEPRECATED_API" $(git rev-list --all --since='1 month ago')

For finding when a string appeared or disappeared, prefer git log -S (the pickaxe) instead — see the "Searching History" page. Use this rev-list trick when you want every snapshot that ever contained the text.

Regex flavours

Bash
git grep "fixme"                 # plain substring (basic regex by default)
git grep -E "TODO|FIXME"         # extended regex (POSIX ERE)
git grep -P "(?i)todo"           # Perl-compatible regex (needs PCRE-enabled git)
git grep -F "a.b.c"              # fixed string — dots are literal
Showing context

Bash
git grep -A 2 "TODO"             # 2 lines after each match
git grep -B 2 "TODO"             # 2 lines before
git grep -C 2 "TODO"             # 2 lines around (Context)

git grep -C 1 TODO

Text
src/auth/login.js-41-
src/auth/login.js:42:  // TODO: handle expired tokens
src/auth/login.js-43-  return token;
Useful combinations

Bash
# Function-aware: show the function each match is in
git grep -p "TODO" -- "*.js"

# Show the matching lines plus the enclosing function
git grep -p -A 0 "TODO" -- "*.js"

# Count TODOs per file, sorted
git grep -c "TODO" -- "*.js" | sort -t: -k2 -n -r | head

# Find files with both "useState" and "useEffect"
git grep -l "useState" | xargs git grep -l "useEffect"

# Open all matching files in your editor
git grep -l "TODO" | xargs code
Comparison with other tools

Tool

Honours .gitignore?

Speed

Strengths

When to use

git grep

Yes — only tracked files

Fast (multithreaded)

Searches refs, works in bare repos, identical syntax across machines

Inside Git repos, day-to-day code search

ripgrep / rg

Yes (.gitignore-aware)

Fastest

Auto file-type filters, prettier output, recursive by default

Default editor search (VS Code, Helix use it under the hood)

ack

No (but skips VCS dirs)

Fast for Perl-driven needs

Rich file-type aware filters

When you need Perl regex and don't have PCRE-enabled git

plain grep

No

Slow on large trees

Universally available

Outside repos, on log files, in one-off scripts

Performance tips
  • Quote your pattern to keep the shell from expanding it: git grep "*.test." will probably fail; git grep "\*.test\." works.

  • Use -F (fixed string) when you don't need regex — it's noticeably faster.

  • --threads=N lets you cap or expand parallelism (default = CPU count).

  • For huge repos, set grep.lineNumber = true in config so -n is implicit.

Quality-of-life config

Bash
git config --global grep.lineNumber true
git config --global grep.extendedRegexp true
git config --global color.grep.match "bold red"
git config --global color.grep.filename "magenta"
git config --global color.grep.linenumber "green"
git grep is a read-only command
It never modifies files, the index, or HEAD. It's safe to run anywhere, anytime — even on a dirty working tree or a detached HEAD. The only side effect is your shell history.
Tip
Make `gg` your muscle-memory shortcut:
git config --global alias.gg "grep -n --break --heading". Now `git gg TODO` gives you grouped, readable results — close to ripgrep's default output but inside Git's search semantics.