How to grep (search) committed code in the Git history – Dev

The best answers to the question “How to grep (search) committed code in the Git history” in the category Dev.

QUESTION:

I have deleted a file or some code in a file sometime in the past. Can I grep in the content (not in the commit messages)?

A very poor solution is to grep the log:

git log -p | grep <pattern>

However, this doesn’t return the commit hash straight away. I played around with git grep to no avail.

ANSWER:

You should use the pickaxe (-S) option of git log.

To search for Foo:

git log -SFoo -- path_containing_change
git log -SFoo --since=2009.1.1 --until=2010.1.1 -- path_containing_change

See Git history – find lost line by keyword for more.


As Jakub Narębski commented:

  • this looks for differences that introduce or remove an instance of <string>.
    It usually means “revisions where you added or removed line with ‘Foo'”.

  • the --pickaxe-regex option allows you to use extended POSIX regex instead of searching for a string.
    Example (from git log): git log -S"frotz\(nitfol" --pickaxe-regex


As Rob commented, this search is case-sensitive – he opened a follow-up question on how to search case-insensitive.

ANSWER:

To search for commit content (i.e., actual lines of source, as opposed to commit messages and the like), you need to do:

git grep <regexp> $(git rev-list --all)

git rev-list --all | xargs git grep <expression> will work if you run into an “Argument list too long” error.

If you want to limit the search to some subtree (for instance, “lib/util”), you will need to pass that to the rev-list subcommand and grep as well:

git grep <regexp> $(git rev-list --all -- lib/util) -- lib/util

This will grep through all your commit text for regexp.

The reason for passing the path in both commands is because rev-list will return the revisions list where all the changes to lib/util happened, but also you need to pass to grep so that it will only search in lib/util.

Just imagine the following scenario: grep might find the same <regexp> on other files which are contained in the same revision returned by rev-list (even if there was no change to that file on that revision).

Here are some other useful ways of searching your source:

Search working tree for text matching regular expression regexp:

git grep <regexp>

Search working tree for lines of text matching regular expression regexp1 or regexp2:

git grep -e <regexp1> [--or] -e <regexp2>

Search working tree for lines of text matching regular expression regexp1 and regexp2, reporting file paths only:

git grep -l -e <regexp1> --and -e <regexp2>

Search working tree for files that have lines of text matching regular expression regexp1 and lines of text matching regular expression regexp2:

git grep -l --all-match -e <regexp1> -e <regexp2>

Search working tree for changed lines of text matching pattern:

git diff --unified=0 | grep <pattern>

Search all revisions for text matching regular expression regexp:

git grep <regexp> $(git rev-list --all)

Search all revisions between rev1 and rev2 for text matching regular expression regexp:

git grep <regexp> $(git rev-list <rev1>..<rev2>)

ANSWER:

If you want to browse code changes (see what actually has been changed with the given word in the whole history) go for patch mode – I found a very useful combination of doing:

git log -p
# Hit "https://stackoverflow.com/" for search mode.
# Type in the word you are searching.
# If the first search is not relevant, hit 'n' for next (like in Vim ;) )

ANSWER:

My favorite way to do it is with git log‘s -G option (added in version 1.7.4).

-G<regex>
       Look for differences whose added or removed line matches the given <regex>.

There is a subtle difference between the way the -G and -S options determine if a commit matches:

  • The -S option essentially counts the number of times your search matches in a file before and after a commit. The commit is shown in the log if the before and after counts are different. This will not, for example, show commits where a line matching your search was moved.
  • With the -G option, the commit is shown in the log if your search matches any line that was added, removed, or changed.

Take this commit as an example:

diff --git a/test b/test
index dddc242..60a8ba6 100644
--- a/test
+++ b/test
@@ -1 +1 @@
-hello hello
+hello goodbye hello

Because the number of times “hello” appears in the file is the same before and after this commit, it will not match using -Shello. However, since there was a change to a line matching hello, the commit will be shown using -Ghello.