Home / Alt manpages / git-grep(1)

  • git-grep(1)
  • User command
  • linux

Search Tracked Code Precisely with git grep

You will search a Git repository's tracked files, limit the result to the paths you mean, and use exit statuses safely in scripts. This guide targets Git 2.43.0, the version installed on this machine from the git-man package. Allow about ten minutes for the first run, plus time to adapt the pathspecs to your repository.

The examples read repository data. They do not edit files, stage changes, rewrite history or require elevated privileges. Replace /path/to/repository and the sample patterns with values for your project.

1. Confirm the version and repository

Run the version check, then change into the working tree you want to search:

$ git --version
git version 2.43.0
$ cd /path/to/repository
$ git rev-parse --show-toplevel
/path/to/repository

git grep normally searches tracked files in the current working tree. It needs a Git repository unless you explicitly select --no-index. The command searches recursively by default, so a search from a subdirectory covers that directory and below.

Checkpoint: if git rev-parse reports that this is not a repository, stop and check the path. Do not add sudo; permissions and repository location are separate issues.

Give the pattern first, followed by -- and an optional pathspec. This example finds the literal text timeout in tracked shell and Python files:

$ git grep -n -F 'timeout' -- '*.sh' '*.py'
scripts/backup.sh:18:timeout=30
tools/check.py:42:    timeout = 10

-n adds line numbers. -F makes the pattern a fixed string, so characters such as . and * are not regular-expression operators. Git uses basic regular expressions by default. Choose -E for extended expressions, or -P for Perl-compatible expressions when this Git build has PCRE2 support.

The pathspecs after -- are filters, not shell file expansion. Quoting them lets Git interpret the wildcard. A leading directory also works:

$ git grep -n -F 'TODO' -- src/

From a subdirectory, output paths are normally relative to where you ran the command. Add --full-name when a script or log needs paths relative to the project top:

$ git grep --full-name -n -F 'TODO' -- src/

3. Make the result easier to review

Use case-insensitive matching for text whose capitalisation varies, and word matching when a short term must not match inside a longer identifier:

$ git grep -n -i -w 'cache' -- src/
$ git grep -n -C 2 -F 'deprecated' -- src/

The first command adds lines containing cache as a word, such as cache.clear(), but not cacheable. The second adds two leading and two trailing lines around each group of matches. Use -A for trailing context or -B for leading context.

For a quick inventory, show file names or counts instead of every matching line:

$ git grep -l -F 'deprecated' -- src/
src/old_api.c
src/compat.c
$ git grep -c -F 'deprecated' -- src/
src/old_api.c:2
src/compat.c:1

When another program consumes the file names, -z uses a NUL delimiter and prints names verbatim. That matters when a repository contains unusual path characters. Do not parse highlighted terminal output in a script; turn off match highlighting when output is being captured.

4. Combine patterns without guessing

Multiple expressions are combined with OR by default. Use -e for every expression when you are composing a Boolean query:

$ git grep -n -e '#define' --and \( -e 'MAX_PATH' -e 'PATH_MAX' \) -- '*.c' '*.h'

This finds lines containing #define and either of the two constant names. Parentheses group the OR expression. In the shell they must be escaped as shown, or quoted, so the shell does not interpret them.

Use --not to exclude an expression and --all-match when separate expressions must all occur somewhere in the same file. The latter is easy to misread: it selects files with a line matching each expression; it does not require all expressions to appear on one line.

5. Search a different Git view

The default view is the working tree's tracked files. Choose another view deliberately:

$ git grep --cached -n -F 'password' -- .
$ git grep HEAD~1 -n -F 'timeout' -- src/
$ git grep --untracked -n -F 'config' -- .

--cached searches blobs in the index, which is useful for checking what is staged rather than what is currently on disk. A tree such as HEAD~1 searches that committed snapshot. --untracked adds untracked files to the normal tracked-file search.

Ignored files remain excluded from the untracked search. --no-exclude-standard includes them, which can expose build output, dependency trees or local secrets. Treat that option as a deliberate security boundary and inspect the pathspec before printing or sharing its output.

6. Handle patterns and statuses in scripts

A pattern beginning with a hyphen can be mistaken for an option. Use -e, especially when the pattern comes from user input:

pattern='-n'
if git grep --quiet -e "$pattern" -- src/; then
    printf '%s\n' 'pattern found'
else
    status=$?
    if [ "$status" -eq 1 ]; then
        printf '%s\n' 'pattern not found'
    else
        printf 'git grep failed with status %s\n' "$status" &2
        exit "$status"
    fi
fi

--quiet suppresses matching lines and returns status 0 when a match exists. Status 1 means no match; another non-zero status indicates an error or invalid invocation. Capture $? immediately, as the next command replaces it.

For repeated patterns, put one pattern per line in a file and pass it with -f. Keep that file controlled and reviewable. If a pattern can contain a NUL byte, the file form is the documented way to provide it, although the selected regular-expression backend may reject such a pattern.

7. Search outside a repository only when you mean to

--no-index searches the current directory without using Git's tracked-file view. It is similar to recursive grep, but still accepts Git pathspecs:

$ cd /path/to/plain-directory
$ git grep --no-index -n -F 'BEGIN PRIVATE KEY' -- config/

This command is read-only, but the pattern is security-sensitive. Avoid sending its output to a shared log, and do not paste matching secret material into tickets or chat. --no-index cannot be combined with --cached or --untracked. If you only wanted the repository search, return to the repository and remove this option.

Done means

  • You confirmed the Git version and searched the intended repository.
  • You chose fixed-string or regular-expression matching deliberately.
  • You used -- and quoted pathspecs to keep the search scope clear.
  • You selected --cached, a tree, --untracked or --no-index only when that view was intended.
  • Scripts use -e, --quiet and explicit handling for no-match versus error statuses.