Search committed code throughout Git history

Search historical snapshots with git grep, find changes with git log -S or -G, and keep searches manageable in large or shallow repositories.

Best practices
Illustration of Git branches and version history

Use git grep when you want to find matching content in historical snapshots. Use git log -S or -G when you want to find the commits that changed it. They answer different questions: a snapshot can contain a string for hundreds of commits even though only a few commits introduced or removed it.

Search snapshots

For a small history, this searches every commit reachable from local refs:

git grep -n -F -e 'api_key' $(git rev-list --all)

The output identifies the commit, file, and matching line. -F treats the query as a literal string; omit it when you need the default regular-expression matching.

The command substitution can exceed the operating system's argument-size limit in a large repository. A shell loop avoids building one huge argument list:

git rev-list --all | while IFS= read -r commit; do
  git grep -n -F -e 'api_key' "$commit" --
  status=$?
  if [ "$status" -gt 1 ]; then
    exit "$status"
  fi
done

A grep status of 1 means no matches for that snapshot; higher statuses indicate errors. Full-history scans can still be expensive and repeat the same content many times.

Narrow the search when possible:

git grep -n -F -e 'password' $(git rev-list main) -- '*.js'
git grep -n -F -e 'TODO' v1.0.0 --

Use the loop form for a large selected history as well. Replace the tag with one that exists locally.

Find the commits that changed a string

git log --all -S'encryptPassword' --oneline
git log --all -G'db\.(connect|query)' --oneline
git log --all -S'api_key' -p

-S selects changes in the number of occurrences of the string. Moving or replacing text while keeping that count unchanged may not match. -G selects patches with added or removed lines matching the regular expression, so it can catch changes that -S does not.

These are useful for locating a removed function or a configuration change. For a configuration file, git log -p -- config.yml keeps the surrounding diff context intact. For potential credential exposure, a literal search such as AKIA is only a clue: it cannot establish that all secrets have been found or whether a match is active.

FAQ

What does --all include?

Commits reachable from local refs and HEAD, including fetched remote-tracking refs. It does not fetch missing history, automatically include every reflog-only object, or recover pruned commits. A shallow clone limits the available history.

How do I make the search faster?

Limit branches, paths, or the revision list, for example with git rev-list --since='2026-01-01' main. Remember that narrowing the time range can omit older snapshots that matter to the investigation. Use -S or -G when change points, rather than every occurrence, are the goal.

How do I read a matching historical file?

Run git show COMMIT:path/to/file using the commit and path from the result.

Can GitHub code search replace this?

Its indexed code search is not a scan of every historical commit. For a history investigation, fetch the relevant history and use the local commands above.

See git-grep, git-log, git-rev-list, and GitHub code search limitations.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting