119

Is there a way to show the git-diff filtered by a given pattern.

Something like

git grepdiff pattern changed file +++ some sentence with pattern changed file 2 --- some other pattern 

Unfortunately the simplest solution is not good enough

git diff | grep pattern +++ some sentence with pattern --- some other pattern # not an option as doesn't put the filename close to the match 

I came with a workaround using awk

git diff | awk "/\+\+\+/{f = \$2}; /PATTERN/ {print f \$0} " 

But would love to find out that there is a command for this.

3

12 Answers 12

146

Not sure but isn't git diff -G <regex> flag OK?

-G < regex>

Look for differences whose added or removed line matches the given <regex>. 
Sign up to request clarification or add additional context in comments.

9 Comments

that's not exactly what I am looking for, as I want to see only the lines, that match the pattern - not the whole diff of a files, that include the change with the pattern
then I guess there's no simpler solution as yours
At least git diff -G is a better first step than the full git diff.
My version does not have the -G flag. Was it removed?
Also useful to see which files were affected by the regex: git diff -G <regex> --raw
|
22

Have you tried git diff -S<string> or git diff -G".*string.*"? Note that they are not equivalent, see the documentation about pickaxe for what -S does.

1 Comment

git diff -S<string> worked for me, insofar as returning only the files within a diff, which had changes that matched the given string. Thanks!
12

Another possibility would be to view the whole diff and search the output using the normal less commands (type / and then the pattern).

When you have less configured to show some lines before the match using --jump-target=N, this is pretty useful. Try it like this:

PAGER="/usr/bin/less --jump-target=10" git diff 

This means that the match should be shown on line 10 (shows 9 lines of context above), which may be enough to also see the file name.

You can also use e.g. --jump-target=.5 to make it position the match in the middle of the screen.

Comments

8

I use git log -p, which opens less (configurable, though), which in turn can be searched for with /. There's also git log -S <searchword>.

2 Comments

I am familiar with those - by I am not looking in the history, but in the current working changes.
Allright, then the same method (as robinst quoted) could be used. If you want the output, you could probably use a any library using libgit2 or gitgui/gitk.
5

I think your approach to "grep" diff output is the best workaround.

You may improve your awk script by using sed:

colored="(^[\[[0-9;]*[a-zA-Z])" marker="^$colored+diff" pattern="^$colored+.*(\+|\-).*PATTERN" git diff --color | sed -rn -e "/$marker/! H; /$marker/ ba; $ ba; b; :a; x; /$pattern/ p" 
  • colored: regex to match terminal colored lines
  • marker: marker to match division from differents diff hunks, lines starting with colored "diff"
  • pattern: pattern to search for, lines starting with colored "+" or "-" and containing "PATTERN"

This will print full diff hunks, with added or removed PATTERN, also maintaining useful colored output.

Note that ^[ in colored should be actual, literal ^[. You can type them in bash by pressing Ctrl + V, Ctrl + [

2 Comments

excellent!! for anyone trying to type this in somewhere other than a bash shell (eg editing .sh file in IDE) you need to replace the ^[ like colored=$'(\e\[[0-9;]*[a-zA-Z])' and on macOS you also need to brew install gnu-sed and replace sed --> gsed
this and @Leon answers are the only answers which are not workarounds. A limitation: it prints the whole file diff not just the hunks that match.
4

I have been using this with great satisfaction :)

grep -ri <MY_PATTERN> $(git diff 790e26393d --name-only) 

1 Comment

The simplest and the most elegant solution. :) For those using an IDE such as vscode, using the -n flag (that is, grep -rin) prints the results with line numbers, so Ctrl+Click (or the equivalent mechanism for other IDEs) on the result opens the editor directly to that line.
3

Here is a custom diff tool that allows grepping inside changes (but not the context):

Usage

GIT_EXTERNAL_DIFF="mydiff --grep foo" git diff

This will output those lines in your changes that contain foo (including lines where foo disappeared because of your changes). Any grep pattern can be used instead of foo.

Each output line starts with the following prefix:

filename: oldlinenum: newlinenum| 

The script can also be used without the --grep option, in which case it simply formats the full diff (i.e. providing full context) as described above.

mydiff

#!/bin/bash my_diff() { diff --old-line-format="$1"':%6dn: |-%L' \ --new-line-format="$1"': :%6dn|+%L' \ --unchanged-line-format="$1"':%6dn:%6dn| %L' \ $2 $3 } if [[ $1 == '--grep' ]] then pattern="$2" shift 2 my_diff "$1" "$2" "$5"|grep --color=never '^[^|]\+|[-+].\+'"$pattern"'.*' else my_diff "$1" "$2" "$5" fi exit 0 

Comments

3

On Windows, a simple solution is:

git diff -U0 | findstr string 

If you want grouping by filename, use this

FOR /F "usebackq delims==" %i IN (`git diff --name-only`) do git diff -U0 %~fi | findstr string 

Comments

1

This did the job for me, I hope it will help someone:

git diff | grep -P '^\+|^\-' 

Comments

0

The solutions offered didn't exactly fit my needs, this solved my issue.

( START_DIFF=abc123 END_DIFF=123dcf # loop over all the files that have changed inside the diff # you can add a `| grep '<ext>$'` to the end of `--name-only` # if you need to be more aggresive with the filtering / or # make it go faster... for file in $(git diff $START_DIFF $END_DIFF --name-only); do # loop over every line of the diff FOR that file. while IFS= read -r line; do # prepend the file name to every line echo "$file:$line" done < <(git diff $START_DIFF $END_DIFF $file) done ) | grep what-youre-looking-for 

I could not get the line numbers working, but I didn't really need them to get them to work. The prepended filename was enough for me.


My exact issue:

Find all the files that added either a from __future__ import .. or a -*- coding: utf-8 -*- out of 70+ files.

( START_DIFF=branch-a END_DIFF=HEAD for file in $(git diff $START_DIFF $END_DIFF --name-only); do while IFS= read -r line; do echo "$file:$line" done < <(git diff $START_DIFF $END_DIFF $file) done ) | grep ':+' | awk '(/import/ && /__future/) || (/coding/)' 

The output looks like this:

.... app/tests/test_views.py:+# -*- coding: utf-8 -*- app/tests/test_views.py:+from __future__ import absolute_import app/tests/test_views.py:+from __future__ import division app2/tests/test_views.py:+from __future__ import division ... 

Comments

0

Ugly, potentially inaccurate, but might be enough to get by if you're just eyeballing it:

git diff origin/master | grep -Eo "(^diff.*|some-keyword)"

Where file1.txt contains some-keyword once, and file3.txt contains some-keyword 3 times:

Outputs:

diff --git a/path/to/file1.txt b/path/to/file1.txt some-keyword diff --git a/path/to/file2.txt b/path/to/file2.txt diff --git a/path/to/file3.txt b/path/to/file3.txt some-keyword some-keyword some-keyword diff --git a/path/to/file4.txt b/path/to/file4.txt 

Comments

0

Just another hacked-together script that produces slightly different formatting of the results, combining https://stackoverflow.com/a/50569950/1836776 and the original question. This will print filenames and lines that have changed in the diff, with the filename on the first line, and changes for the file on subsequent lines.

#!/usr/bin/env bash # Adapted from https://stackoverflow.com/a/50569950/1836776 if [[ $1 == '--help' ]]; then script=`basename "$0"` echo "USAGE" echo " $script term [git diff parameters]" echo echo "DESCRIPTION" echo " Searches git diff results for <term>, returning only lines that have changed" echo echo "EXAMPLES" echo " - Search all changes from master in current branch for 'TODO':" echo " $script TODO master..." echo echo " - As above, with pagination (color support):" echo " $script TODO master... | less -r" exit 1 fi colored=$'(\e\[[0-9;]*[a-zA-Z])' marker="^$colored+diff" pattern="^$colored+.*(\+|\-).*$1" shift # Yeah, okay, both sed and awk for maximum dork here, feel free to combine! git diff --color $* | sed -rn -e "/$marker/! H; /$marker/ ba; $ ba; b; :a; x; /$pattern/ p" | awk "/\+\+\+/{f = \$2 \":\\n\"}; /$pattern/ {print f \$0; f = \"\"}" 

Searches look something like this (you'll have to imagine the beautiful red/green colors for the changes):

# ./git-diff-grep.sh TODO main... b/tsconfig.json: + /* TODO: don't forget to update this */ b/README.md: -# TODO: update instructions -TODO: license 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.