14

I need to search for text "TEXT" in past 20 git commits (or range)
I need to see filename and line number where I have a match.

Note:
I want to search commits content, but not project on that commit as git grep does.
By commits content I mean what I can see using git diff HEAD^^^..HEAD

Closest I got is using git log --raw -GTEXT, it shows me commit which contains "TEXT" in commit content and show file names. Still no line numbers however.

And with some piping
git diff $(git log -n 20 --pretty=format:%h -GTEXT) | grep -E 'TEXT|\+\+\+|@@\s'
It is still somewhat wordy and with a lot of noise, if you have better solution, please answer.

logcat
  • 3,435
  • 1
  • 29
  • 44
  • I'm imagining something like `git diff` the range of commits, then pipe that to `grep` – OneCricketeer Aug 01 '16 at 07:42
  • 1
    How about `git log -g --grep=TEXT` ? – Shravan40 Aug 01 '16 at 07:43
  • @Shravan40 Are you missing `--grep`? – OneCricketeer Aug 01 '16 at 07:45
  • @cricket_007 git diff does not show file names nor line numbers by default, can you elaborate? – logcat Aug 01 '16 at 07:45
  • 1
    On the contrary, it does show file names and line numbers. `--- a/file.txt` for removed lines. `+++ b/file.txt` for added lines, and `@@ start, stop @@` to show where a change was made – OneCricketeer Aug 01 '16 at 07:49
  • You need to clarify your question. Do you want to search the commit messages (in which case `git log --grep="TEXT"` will do the trick), or the contents of the file committed? – jub0bs Aug 01 '16 at 07:50
  • @Jubobs contents of the commit (not full file, but diff for that commit range) – logcat Aug 01 '16 at 07:58
  • @Shravan40 you should add some explanation around the actual syntax and make it an answer. – Mureinik Aug 01 '16 at 08:05
  • @cricket_007 but if I pipe it to grep I would cut off @@ status line. I can write regex to match to that line or search "TEXT" and that might do the thing – logcat Aug 01 '16 at 08:09
  • @cricket_007 it sorta works, but there are a lot of false positives for @@ status line and commits are huge, so still not readable – logcat Aug 01 '16 at 08:12
  • I didn't say it was the best approach. It was just my first idea, which I didn't really try ;) – OneCricketeer Aug 01 '16 at 08:13
  • @cricket_007 lol I can pipe it further and cut off @ starting lines which has next line starting with @, but I have hope that `git grep` or `git log --grep` can do that and all I need just right params – logcat Aug 01 '16 at 08:25
  • There may be some additional options on `diff` that you could use. Also, `git show` will display commit content and messages, if you did want them – OneCricketeer Aug 01 '16 at 08:32
  • Could you live with `git log -GTEXT -1 --pretty=format:%H | xargs -n1 -I{} git --no-pager diff '{}^!' | grep -e 'TEXT' -e '@@'` and then looking at the `@@` right before the match of `TEXT`? – AnimiVulpis Aug 01 '16 at 09:01
  • @logcat Have you tried my answer yet? – Nick Bull Aug 10 '16 at 16:39
  • @NickBull No, but I've upvoted it, I bet it works, also using gitpython would be more readable, sorry for no acceptance, as I expected something simpler – logcat Aug 12 '16 at 08:31
  • @logcat it's a one-liner with the function. See update – Nick Bull Aug 12 '16 at 10:32

5 Answers5

7

Get the last 20 commits:

git log -n 20

Get each to an associative array:

declare -A COMMITS # Declare associative array
COMMITNUMBER=0

while read -r line; do # For each line, do
    # Add 1 to COMMITNUMBER, if the current line contains "commit [0-9a-f]* (e.g., new associative array index for each commit)
    # As this'll happen straight way, our array is technically 1-indexed (starts from "${COMMITS[1]}", not "${COMMITS[0]}")
    REGEX="commit\s[0-9a-f]*"
    [[ "$line" =~ $REGEX ]] && COMMITNUMBER=$(( COMMITNUMBER+1 ))

    # Append the commit line to the index
    COMMITS[$COMMITNUMBER]="${COMMITS[$COMMITNUMBER]} $line"
done < <(git log -n 20)

Then iterate:

for i in "${!COMMITS[@]}"
do
  echo "key  : $i"
  echo "value: ${COMMITS[$i]}"
done

This gives similar output to below:

key  : 1
value:  commit 778f88ec8ad4f454aa5085cd0c8d51441668207c Author: Nick <nick.bull@jgregan.co.uk> Date:   Sun Aug 7 11:43:24 2016 +0100  Second commit 
key  : 2
value:  commit a38cd7b2310038af180a548c03b086717d205a61 Author: Nick <nick.bull@jgregan.co.uk> Date:   Sun Aug 7 11:25:31 2016 +0100  Some commit

Now you can search each in the loop using grep or anything, and match what you need:

for i in "${!COMMITS[@]}"
do
    REGEX="Date:[\s]*Sun\sAug\7"
    if [[ "${COMMITS[$i]}" =~ $REGEX ]]; then
        echo "The following commit matched '$REGEX':"
        echo "${COMMITS[$i]}"
    fi
done

Altogether:

search_last_commits() {
    [[ -z "$1" ]] && echo "Arg #1: No search pattern specified" && return 1
    [[ -z "$2" ]] && echo "Arg #2: Number required for number of commits to return" && return 1

declare -A COMMITS # Declare associative array
COMMITNUMBER=0

    while read -r line; do # For each line, do
        # Add 1 to COMMITNUMBER, if the current line contains "commit [0-9a-f]* (e.g., new associative array index for each commit)
        # As this'll happen straight way, our array is technically 1-indexed (starts from "${COMMITS[1]}", not "${COMMITS[0]}")
        REGEX="commit\s[0-9a-f]*"
        [[ "$line" =~ $REGEX ]] && COMMITNUMBER=$(( COMMITNUMBER+1 ))

        # Append the commit line to the index
        COMMITS[$COMMITNUMBER]="${COMMITS[$COMMITNUMBER]} $line"
    done < <(git log -n $2)

    for i in "${!COMMITS[@]}"
    do
        REGEX="$1"
        if [[ "${COMMITS[$i]}" =~ $REGEX ]]; then
            echo "The following commit matched '$REGEX':"
            echo "${COMMITS[$i]}"
        fi
    done
}

EDIT:

Usage:

search_last_commits <search-term> <last-commits-quantity>

Example:

search_last_commits "Aug" 20
Nick Bull
  • 9,518
  • 6
  • 36
  • 58
2

If you want to search through modified files in the range (and don't care about matching lines of the files that weren't actually modified in those commits) you can run:

git grep TEXT -- $(git diff --name-only RANGE)
sdeleon28
  • 329
  • 1
  • 11
1

The easiest way: GIT_SEQUENCE_EDITOR=: git rebase --exec "git rev-parse HEAD; git --no-pager grep -n 'TEXT_TO_FIND'; echo " -i HEAD~20

Details:

git rev-parse HEAD - show the current sha1

git --no-pager grep -n 'TEXT_TO_FIND' - the grep command to find your text, you can use here any other command (like regular grep etc.)

echo - a command to print empty line between each commit, and make sure that the return value is always OK

HEAD~20 - how many commit back do you want to search (here I did 20)

Some helpful answers: link1 link2

Community
  • 1
  • 1
Chananel P
  • 1,704
  • 1
  • 18
  • 19
1

Try this out :

echo `git log --raw --pretty=format:%h -GTEXT | xargs | awk '{print $NF}'` ;grep -ni 'MAX_STR_LEN' $(git log --raw --pretty=format:%h -GMAX_STR_LEN | xargs | awk '{print $NF}')

This works for me for single commit version. May be you can loop for n number of times to achieve your goal.

Indra Uprade
  • 773
  • 5
  • 12
1
git grep SEARCH `git rev-list HEAD~20..HEAD`

Specify whatever range you need.

The result will be a grep for each revision. That is, you see all occurrences in each revision. You do not see what changed relative to the previous revision. (other by comparing yourself to the lines listed for the previous revision)

Also it is quite slow. Since it does a full grep on each revision in the range.


Alternatively you can limit it to revisions (within the range) in which there was a change related to the search term.

git grep SEARCH  `git log -G SEARCH  --format=%H HEAD~20..HEAD`

While limited to revisions with related search, again in each such revision you get all lines.

Martin
  • 521
  • 5
  • 8