How to Use git grep to Search a Repository Fast the Safe Way
Search a repository fast with git grep: line numbers, case, whole words, counts, function context, searching old revisions and limiting to file types.

Recursive grep in a project wades through build output, vendored packages and .git itself. git grep asks Git for the file list instead, so it searches only what belongs to the project and does it with multiple threads. It is the first tool to reach for in any repository, and it fits next to grep with exclusions. Examples use Git 2.47.
Basic searches
git grep -n 'TODO'
git grep -nw 'cache' -- '*.py'
git grep -ci 'deprecated'src/cache.py:41: # TODO: expire entries by size
src/config.py:12:# TODO: read from environment
-n prints line numbers, -w restricts to whole-word matches so cache does not find cached, and -c prints a count per file. A pathspec after -- limits the file set. By default the pattern is a basic regular expression; -E switches to extended and -P to Perl syntax if Git was built with PCRE. Use -F to search a literal string that contains special characters.
Combine patterns
git grep -n -e 'retry' --and -e 'timeout'
git grep -n --all-match -e 'open(' -e 'close('
git grep -n -e 'TODO' --and --not -e 'FIXME'The boolean options work per line with --and, --or (the default) and --not, using -e to introduce each pattern. --all-match requires every pattern to appear somewhere in a file, not necessarily on the same line, which finds files that use both calls.
Context and old versions
git grep -p 'MAX_RETRIES' -- src/
git grep -W 'def get' src/cache.py
git grep 'MAX_RETRIES' v2.3.0
git grep -n 'MAX_RETRIES' $(git rev-list -n 5 HEAD)-p prints the name of the function that contains each match, and -W prints the whole function body. Giving a revision searches the committed files at that point, without checking them out, with each result prefixed by the revision name. Passing several revisions answers "when did this string appear?" but for that question git log -S is more direct; see blame and log.
Untracked and ignored files
git grep --untracked -n 'API_KEY'
git grep --no-index -n 'API_KEY' /etc/myapp--untracked includes new files that are not yet added. --no-index turns git grep into a fast grep for any directory, even outside a repository, still respecting ignore rules unless you add --no-exclude-standard. This is useful for a scan before a commit, such as a rough check for secrets; a dedicated tool is better, as in gitleaks and trufflehog.
Where this does not apply
Ignored files are skipped, including a generated directory you might want to search; use plain grep -r or ripgrep for that. It searches text, so binary matches show only "Binary file matches" unless you add -a. For regular expression details across tools, see regex in git. Set grep.lineNumber true to make -n the default.
What git grep searches and why it is quick
The command git grep searches the files Git tracks in the working tree, so it skips build output, dependency folders and anything ignored, without a list of exclusions. It also reads from the index and from stored objects efficiently, and it uses several threads, so on a large repository it often finishes before a recursive grep has walked the directories. The cost is that it knows only tracked files by default. A new file that has not been added is invisible, which surprises people right after creating something. Add --untracked to include untracked files that are not ignored, and --no-index to search a directory outside any repository with the same interface.
The default pattern syntax is basic regular expressions, as in plain grep. Use -E for extended, -P for Perl compatible where Git was built with the library, and -F for a fixed string, which is the fastest and avoids any quoting issue when the search text holds special characters.
Showing structure with the output
Several options turn matches into something easier to read. -n adds line numbers, -c counts matches per file, -l lists only file names, and --heading with --break groups results by file with blank lines between, in a layout that looks like the output of a code search tool. -p shows the function or section that contains each match, using language rules, so you see the name of the function and not just a line. -W shows the whole function around the match. These help most when you search for a call and want to know which functions use it. Limit the search to a path with a pathspec after a double dash, for example -- 'src/*.py', and exclude with a magic pathspec such as ':!vendor'. Quote pathspecs so the shell does not expand them first.
Context options match plain grep. -A, -B and -C print lines after, before and around each match, and the same flags are useful for reading how a configuration key is used.
Searching the past
Give git grep a revision and it searches that snapshot: git grep "TODO" v1.0 searches the tag without checking it out. Several revisions work at once, and the output prefixes each line with the revision name. To find which commits contain a string across history, loop over git rev-list with grep, or use git log -S instead, which is built for that question. Searching every commit with grep is slow on a large history and wasteful when pickaxe answers it in a single pass. Use grep on a revision when you want to see the code as it was, and log with -S or -G when you want to know when something changed.
One security use deserves a mention. Running git grep over past commits for key-like strings finds credentials committed and later removed, which still live in history. A dedicated scanner does this more thoroughly, and a hit means the secret must be rotated, since deleting the line does not remove it from clones that already exist.
Putting it into a daily routine
A few habits make the command pay off. Search for a function name before renaming it, to see every caller. Search for a configuration key before removing it, since an unseen use in a script or template is a typical cause of an outage after a cleanup. Search for deprecated calls across a repository and count them with -c to track the migration. Save useful searches as aliases and put them in a shared script for the team so everyone checks the same patterns before a release, such as debugging statements and hard-coded addresses.
Understand the speed trade-off. On a repository of moderate size the difference from a recursive grep is small. On a very large one, git grep is much faster because it consults the index and skips untracked content. A search tool built for code, such as ripgrep, is faster still on the working tree and also respects ignore files, and it is often the better interactive choice when you do not need Git's access to old revisions.
See git-grep and the searching chapter of Pro Git.


