20

git log -G<regex> -p is a wonderful tool to search a codebase's history for changes that match the specified pattern. However, it can be overwhelming to locate the relevant hunk in the diff/patch output in a sea of mostly irrelevant hunks.

It’s of course possible to search the output of git log for the original string/regex, but that does little to reduce the visual noise and distraction of many unrelated changes.

Reading up on git log, I see there's the --pickaxe-all, which is the exact opposite of what I want: it broadens the output (to the entire changeset), whereas I want to limit it (to the specific hunk).

Essentially, I’m looking for a way to "intelligently" parse the diff/patch into individual hunks and then execute a search against each hunk (targeting just the changed lines), discard the hunks that don’t match, and output the ones that do.

Does a tool such as I describe exist? Is there a better approach to get the matched/affected hunks?

Some initial research I've done...

  • If it were possible to grep the diff/patch output and make the context option values dynamic—say, via regexps instead of line counts—that might suffice. But grep isn't exactly built that way (nor am I necessarily requesting that feature).

  • I found the patchutils suite, which initially sounded like it might suit my needs. But after reading its man pages, the tools doesn't appear to handle matching hunks based on regexps. (They can accept a list of hunks, though...)

  • I finally came across splitpatch.rb, which seems to handle the parsing of the patch well, but it would need to be significantly augmented to handle reading patches via stdin, matching desired hunks, and then outputting the hunks.

  • 1
    Not exacly what you asked but try git log -Gfoo | less +/foo – James Youngman Mar 2 '16 at 4:22
7

here https://stackoverflow.com/a/35434714/5305907 is described a way to do what you are looking for. effectively:

git diff -U1 | grepdiff 'console' --output-matching=hunk

It shows only the hunks that match with the given string "console".

  • thanks. grepdiff is basically what i want; i must've missed its hunk-matching option! however... the git commit info is stripped by grepdiff, so once you locate the relevant hunk, you must divine the commit sha from the object/blob sha in the diff header—quite an expensive operation. (see stackoverflow.com/a/223890/2284440) it'd be something like git find-object SHA --reverse | head -1 | cut -c 1-7 | { read sha ; git log -1 $sha; } – wrksprfct Jan 21 '17 at 5:45
  • also note there's a golang version of grepdiff which is more barebones in terms of accepted arguments. note that when the matched hunk is the last hunk in a diff, it incorrectly includes the git commit header of the following commit—something that completely confused me until i realized what's going on! – wrksprfct Jan 21 '17 at 5:55
0

Not exactly what you're asking for, but one way to grep through hunks is the interactive-add mode. This requires you to check out the commit after the patch you're interested in

git checkout COMMIT_ID

then go back one more step in the VCS, but not in the working directory

git reset --soft HEAD^

(At this point, the difference between the index and the working directory will correspond to the patch you're interested in.)

You can now execute git add -p. This will launch an interactive session which has a / option, that allows you to locate hunks in whichs some line matches a regex. Particularly useful if you actually want to further process those patches (like, preparing a partial cherry-pick).

Unfortunately, at least right now the / command in add -p only works within a single file, so you may need to skip several unrelevant files.

0

Building up on the answer above by @nagu, and the other linked answers, I was able to get git log -G to only show the relevant hunks.

  1. First create a script somewhere in your $PATH with this content:

    #!/bin/bash
    
    # pickaxe-diff : external diff driver for Git.
    #                To be used with the pickaxe options (git [log|show|diff[.*] [-S|-G])
    #                to only show hunks containing the searched string/regex.
    
    path=$1
    old_file=$2
    old_hex=$3
    old_mode=$4
    new_file=$5
    new_hex=$6
    new_mode=$7
    
    filtered_diff=$(diff -u -p $old_file $new_file | \
                    grepdiff "$GREPDIFF_REGEX" --output-matching=hunk | \
                    grep -v -e '+++ ' -e '--- ')
    
    a_path="a/$path"
    b_path="b/$path"
    
    echo "diff --git $a_path $b_path"
    echo "index $old_hex..$new_hex $old_mode"
    echo "--- $a_path"
    echo "+++ $b_path"
    echo "$filtered_diff"
    
  2. Call git log -G and tell Git to use the pickaxe-diff script as an external diff driver:

    export GREPDIFF_REGEX=<string>; 
    GIT_EXTERNAL_DIFF=pickaxe-diff git log -p --ext-diff -G $GREPDIFF_REGEX
    

    This will use the pickaxe-diff script just to generate the diffs, so the rest of the git log output (commit hash, message, etc) will be untouched.

Caveat
The way that the Git pickaxe work is that it limits the output to the files whose hunks change the given string/regex. This means that if another hunk in these files also contain the search string/regex, but does not change it, it will still be displayed with the above script. This is a limitation of grepdiff. There is an open pull request at the patchutils project to add an --only-matching flag to grepdiff, which would provide the needed functionality to correctly filter out these hunks.


I did a write-up of my solution in this gist.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.