2

A change in my working copy of a Git repository caused an error which mentions a variable foo and I want to find which files and lines contain a changed line in my working copy which mentions foo.

If I do git diff path/file it launches a visual editor, so I can't grep in the shell. Even if that worked, it would omit the name of the file in question.

This answer seems closer to what I want, but it is overstuffed with irrelevant examples using terminology I don't really understand.

I don't want to search through any history (i.e. commits), I want to search for foo within the changes that I currently made since the last checkout/commit.

"Hey, Git, what did I change which contains foo and hasn't been committed yet? File names and line numbers, please."

How do I do this?

(I'd also appreciate being able to ask the same question of a visual diff editor, if you happen to know one on Linux which does this.)

Community
  • 1
  • 1
spraff
  • 32,570
  • 22
  • 121
  • 229
  • 1
    `git diff` is not expected to prompt any editor while `git difftool` does if a diff tool has been set up. Is it a `pager`? How about run `git diff path/file | grep foo`? If this cannot work, just `git add .` and `git commit` and `git show` or `git show | grep foo`. If it works, run `git reset HEAD^` to go back to the uncommitted state. – ElpieKay Sep 02 '16 at 15:35

2 Answers2

2

A normal git diff command will give you the file names and line numbers, so if we can limit it properly that should do the job. You could do:

git diff -G foo

But this will not work if you have staged changes. (You can use the --cached argument to only check staged changes, but that won't include unstaged changes.)

This should work better:

git diff-index -G foo -p -U0 HEAD

Both of these commands will include all changes in matching files, not just changes containing foo, but otherwise should do what you are asking.

Note also that both of these commands will include the patch output, so if you have a large set of changes it will require some extra scrolling. In addition, to get exact line numbers, you'll need to do some simple math if there are other changes in the same hunk. E.g. if you see this:

@@ -101,6 +103,8 @@
+  int bar = 1;
+  int baz = 2;
+  int foo = 3;

That means the top of the hunk was line 101, and is now line 103, and thus the foo line is now line 105.

Scott Weldon
  • 9,673
  • 6
  • 48
  • 67
  • This doesn't directly provide file and line number info, as requested by OP – Leon Sep 02 '16 at 16:57
  • @Leon It actually does provide that info, it just includes a lot of superfluous output. – Scott Weldon Sep 02 '16 at 16:59
  • 1
    That's why I wrote that it doesn't provide the info directly. For large diffs you must scroll to find out the file name and do some simple math to find the line numbers of the changes. – Leon Sep 02 '16 at 17:05
1

The following script will do almost what you want:

Usage

GIT_EXTERNAL_DIFF="mygitdiff --grep foo" git diff

This will output those lines in you changes that contain foo (including lines where foo disappeared because of your changes).

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.

Note that if you have both staged and unstaged changes, to achieve the desired effect you must run git diff against HEAD:

GIT_EXTERNAL_DIFF="mygitdiff --grep foo" git diff HEAD

mygitdiff

#!/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

CREDITS Scott Weldon helped to test this script and found a bug in its initial version. Thank you Scott!

Community
  • 1
  • 1
Leon
  • 31,443
  • 4
  • 72
  • 97
  • Looks like this doesn't include staged changes. – Scott Weldon Sep 02 '16 at 17:03
  • @ScottWeldon This script is just a diff tool. You can still run `git diff --staged` with it. – Leon Sep 02 '16 at 17:06
  • 1. As I noted in my answer, if the `--cached` / `--staged` argument is given, then unstaged changes aren't included. 2. I tried that just now, and it didn't give me any output, even though I staged some changes containing `foo`. – Scott Weldon Sep 02 '16 at 17:13
  • @ScottWeldon Do you mean that your staged changes contain `foo` but `GIT_EXTERNAL_DIFF="mygitdiff --grep foo" git diff --staged` doesn't produce any output? – Leon Sep 02 '16 at 17:27
  • @ScottWeldon That's strange. It works on my side. Did it work at all on your side? – Leon Sep 02 '16 at 17:34
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/122537/discussion-between-scott-weldon-and-leon). – Scott Weldon Sep 02 '16 at 17:37