Skip to content
shellhacks.dev
Regular Expressions

How to Use Regex in Git for Log and Grep the Right Way

Use regex with git grep, git log --grep, -G and -S: pick basic, extended or Perl syntax, search messages versus content, and find who changed a pattern.

6 min read
Summarize in AI
Illustration for "How to Use Regex in Git for Log and Grep the Right Way": a terminal session, a magnifier over matching lines, a git branch graph.

Git has four different ways of searching with a regular expression, and they search four different things: the files as they are, the commit messages, the changed lines and the count of a string over time. The syntax is the same family everywhere, but the flags are not. This post lays them out together, next to searching with git grep and tracing a line. Examples use Git 2.47.

Search the files

Shell
git grep -n -E 'retry(ing)?|backoff'
git grep -n -P '(?<=timeout=)\d+' -- '*.conf'
git config --global grep.patternType extended

The default is basic regex, in which | and + must be escaped as in GNU grep. -E switches the whole command to extended syntax, and -P to Perl syntax when Git is built with PCRE2, which is the case on most Linux distributions and Homebrew. Setting grep.patternType removes the need to type the flag. Add -i to ignore case and -w for whole words.

Search commit messages

Shell
git log --oneline --grep='^fix(auth)' -E
git log --oneline --grep='cache' --grep='timeout' --all-match
git log --oneline --grep='WIP' --invert-grep

--grep matches against the commit message only. Giving it twice means OR, and --all-match turns that into AND. --invert-grep shows the commits that do not match, which is handy for hiding automated commits. The anchors ^ and $ apply to each line of the message, so ^fix matches a subject line starting with "fix". Combine with --author and path filters as in log formats and filters.

Search what changed

Shell
git log -G'MAX_RETRIES\s*=' --oneline -p -- src/config.py
git log -S'MAX_RETRIES' --oneline
git log -S'retry\(.*\)' --pickaxe-regex --oneline

-G takes a regex and lists commits whose diff contains an added or removed line matching it, which catches edits to a value. -S takes a plain string and lists commits that changed how many times it appears, so it finds when something was added or deleted, but not a change that kept the count. With --pickaxe-regex, -S treats the text as a regular expression. Add -p to see the diff. These are slower than message search because Git examines the content of every change.

Diffs and other places

Shell
git diff -G'TODO' main...feature
git branch --list 'release/*'
git tag --list 'v2.*'

The last two use shell-style globs, not regular expressions, which is a source of confusion: git branch --list and git tag --list take fnmatch patterns, and a regex there silently matches nothing. git diff -G filters the files shown to those with matching changes.

Where this does not apply

Binary files and ignored files are skipped by git grep by default; add --untracked or --no-index for those. History search across a very large repository can take minutes; limit it with a path or a date range. The patterns here share the dialect differences in regex differences across tools, and ripgrep offers another engine for working trees.

Which regular expression dialect git uses where

Git's commands choose their dialects separately, which leads to surprises. git grep uses basic regular expressions by default, with -E for extended and -P for Perl-compatible when Git was built with PCRE. git log --grep follows the same convention with its own flags, --extended-regexp and --perl-regexp, and -i ignores case. The pickaxe options, -S for a string and -G for a regular expression, are different again: -S takes a literal string unless you add --pickaxe-regex, and -G takes a basic expression. The result is that the same pattern needs a different flag in three places, and a pattern that works in one command may match nothing in another. A table in your notes saves time. Git also has configuration, grep.extendedRegexp and grep.patternType, to set the default for git grep, so a colleague's repository may behave differently from yours.

Because the tool is built into Git, the PCRE option may be missing. Check with git grep -P test on the machine in question, and expect an error about the build when it was compiled without it.

Combining several conditions

Search commands accept more than one pattern, and the way they combine matters. In git grep, repeating -e joins patterns with an OR by default, and the options --and, --or, --not and parentheses build a boolean expression, applied per line. --all-match requires that each pattern matches somewhere in the file, not on the same line, which finds files that mention two things anywhere. For commit messages, --grep repeated is an OR, and --all-match makes it an AND across the message, and --invert-grep selects commits that do not match, which helps filter out noise such as merge or bot commits. Combine these with path limits and date ranges so the search covers the relevant history and runs quickly. On a large repository, a broad history search is slow, and narrowing it by path or range is the difference between a few seconds and a few minutes.

Searching what changed over time

To find when a pattern appeared or disappeared, git log -G'regex' -p lists commits whose diff has a changed line matching the pattern, and -S'text' lists commits where the number of occurrences changed. The first catches a modified line, and the second misses a line that was edited but not added or removed, so choose by the question. Add --follow with a single path to track renames. For the state at a given revision, git grep pattern revision searches that snapshot, and prefixing results with the revision name makes comparisons across several easy. Use -n for line numbers, -c for counts per file, and -W or -p to show the enclosing function. Beware that results depend on the checked-out files for working tree searches, and on which files are tracked: untracked files are skipped unless you add --untracked, and ignored files are skipped unless you also pass --no-exclude-standard.

A short routine for investigating a regression

When a behavior changed and you do not know when, combine the tools in order. Search the current tree for the code or message with git grep to find where it lives. Ask git log -S for the commits that added or removed the string. Use git log -G if the line was modified rather than added. Read the candidate commits with git show, and check whether they were part of a larger change that explains the behavior. If the history is long, narrow with --since and a path, and consider bisect when you have a reproducible test. Write down the commit, the reason and the fix when you find it, since the next person to meet the same symptom will search the history for the same words, and a clear commit message or a linked ticket saves them the same hours. Link the commit from the ticket as well, so the search starts from a known point and not from the whole history.

See git-grep and the git-log search options.

Also inGit
Taggedgitgrep
Share

Keep reading

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.