Git Grep

git grep searches the files Git tracks, so results match what is committed rather than whatever happens to be on disk.

git grep searches tracked files for a pattern. Because it reads from the index or a commit rather than the filesystem, the results reflect what Git knows about — which matters more than it sounds when you have a build directory, an ignored .env.local, or a branch checked out somewhere else.

The basics

git grep "TODO"
git grep -n "TODO"                # with line numbers
git grep -i "error"               # case-insensitive
git grep -l "useState"            # filenames only
git grep -w "commit"              # whole words only
git grep -c "import"              # match counts per file

Searching something other than the working tree

This is where it differs usefully from the ordinary grep:

git grep "handleAuth" main             # as of the main branch
git grep "handleAuth" v2.1.0           # as of a tag
git grep "handleAuth" 8a3f1b2          # as of one commit
git grep "handleAuth" $(git rev-list --all)   # every commit ever

Those last two answer "when did this disappear", which is a question no filesystem search can answer, because it is a question about history.

Finding when a line arrived

git log -S "handleAuth" --oneline

That is the reliable form for "when was this added", and it is usually better than grepping every commit. git blame takes it the other way — which commit last touched each line. See Git Blame.

Useful combinations

git grep -l -e "console\.log" -- "*.ts"     # only TypeScript files
git grep -n "secret" -- ':!*.lock'          # excluding lockfiles
git grep -c "" -- "*.md"                    # line counts of every doc

Paths after -- are pathspecs, so they take the same wildcards Git uses everywhere else. The :! prefix excludes, which is how you skip generated files without an ignore rule.

Tracking vs the filesystem

Plain grep -r searches everything on disk, including untracked and ignored files. That is occasionally what you want — and occasionally how you end up searching a 300MB build directory, or worse, printing a line from a file that is not in the commit you are reasoning about. When you are reasoning about committed code, git grep is the more accurate answer.