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 intonode_modules,dist, or.nextby 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~10looked like without checking it out.Search across all history — pickaxe-style, by piping
git rev-list.
The basics
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
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
# 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/**"
Searching other revisions
# 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
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
# 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
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
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
src/auth/login.js-41- src/auth/login.js:42: // TODO: handle expired tokens src/auth/login.js-43- return token;
Useful combinations
# 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 |
|---|---|---|---|---|
| Yes — only tracked files | Fast (multithreaded) | Searches refs, works in bare repos, identical syntax across machines | Inside Git repos, day-to-day code search |
| Yes (.gitignore-aware) | Fastest | Auto file-type filters, prettier output, recursive by default | Default editor search (VS Code, Helix use it under the hood) |
| 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 | 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=Nlets you cap or expand parallelism (default = CPU count).For huge repos, set
grep.lineNumber = truein config so-nis implicit.
Quality-of-life config
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 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.