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.
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.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan