Handcrafting Codes← Index

Engineering

Finding anything in a git repo: working tree, commits, and history

Grep finds a string in your working tree

Jul 02, 2026 / 3 min read / By the author

COVER IMAGE — 16:9

Grep finds a string in your working tree. That's the easy case, and it's also the least interesting one, because the thing you're hunting for is usually not in the working tree anymore. It's in a commit message from three months ago, or in a line of code that got deleted, or on a branch you can't remember the name of. Git can find all of those. Most people only ever learn the first.

Search the current code

git grep searches tracked files in your checkout, faster than plain grep because it knows what git is tracking:

git grep "font-size: 52px;"

Fine for "where is this right now." But "right now" is often not the question.

Search every commit that ever existed

This is the one people don't know, and it's the one that saves an afternoon. You changed something, you don't remember when, you don't even remember which branch. Search the entire history at once:

git rev-list --all | xargs git grep -F "font-size: 52px;"

git rev-list --all lists every commit in the repo. Piping that into git grep runs the search against each one. The output points you straight at the commits and files where the string lived:

f3022...9e12:HtmlTemplate/style.css: font-size: 52px;
e9211...8244:RR.Web/Content/style/style.css: font-size: 52px;

The -F flag treats the pattern as a fixed string instead of a regex, which is what you want when you're searching for literal code with special characters in it.

Search commit messages

When you remember describing the change but not the change itself:

git log --grep="fixes things"

This filters the log to commits whose messages match. Good for finding the commit where someone noted a decision, a ticket number, or a bug they were chasing.

Search the code changes themselves

Here's where two flags that look similar do different things, and knowing which is which matters.

The pickaxe, -S, finds commits where the number of occurrences of a string changed. In other words, commits that added or removed that exact text:

git log -S"window.alert"

This is how you answer "when did this line get introduced, or deleted?" It's looking at the count of matches before and after each commit, so it surfaces the moments the string appeared or disappeared, not every commit that merely touched a nearby line.

The other one, -G, matches commits whose diff contains a line matching a regex:

git log -G"foo.*"

-G is broader. It catches any commit whose patch text matches the pattern, including changes where the count didn't move, like a line being edited in place. So -S answers "when did this text come or go," and -G answers "when did a change matching this pattern happen at all." Reach for -S when you're tracking the life and death of a specific string, and -G when you want every diff that mentions a pattern.

The pattern worth internalizing: the working tree is one slice of time, and git keeps all the others. When something's missing or you can't remember when it changed, you're not stuck. You just have to search the history instead of the checkout.