[git] Retrieve the commit log for a specific line in a file?

Is there any way to get git to give you a commit log for just commits that touched a particular line in a file?

Like git blame, but git blame will show you the LAST commit that touched a particular line.

I'd really like to get a similar log of, not the list of commits to anywhere in the file, but just the commits that touched a particular line.

This question is related to git

The answer is


Simplifying @matt's answer -

git blame -L14,15 -- <file_path>

Here you will get a blame for a lines 14 to 15.

Since -L option expects Range as a param we can't get a Blame for a single line using the -L option`.

Reference


You can get a set of commits by using pick-axe.

git log -S'the line from your file' -- path/to/your/file.txt

This will give you all of the commits that affected that text in that file. If the file was renamed at some point, you can add --follow-parent.

If you would like to inspect the commits at each of these edits, you can pipe that result to git show:

git log ... | xargs -n 1 git show

Try using below command implemented in Git 1.8.4.

git log -u -L <upperLimit>,<lowerLimit>:<path_to_filename>

So, in your case upperLimit & lowerLimit is the touched line_number

More info - https://www.techpurohit.com/list-some-useful-git-commands


An extremely easy way to do this is by using vim-fugitive. Just open the file in vim, select the line(s) you're interested in using V, then enter

:Glog

Now you can use :cnext and :cprev to see all the revisions of the file where that line is modified. At any point, enter :Gblame to see the sha, author, and date info.


In my case the line number had changed a lot over time. I was also on git 1.8.3 which does not support regex in "git blame -L". (RHEL7 still has 1.8.3)

myfile=haproxy.cfg
git rev-list HEAD -- $myfile | while read i
do
    git diff -U0 ${i}^ $i $myfile | sed "s/^/$i /"
done | grep "<sometext>"

Oneliner:

myfile=<myfile> ; git rev-list HEAD -- $myfile | while read i; do     git diff -U0 ${i}^ $i $myfile | sed "s/^/$i /"; done | grep "<sometext>"

This can of course be made into a script or a function.


If the position of the line (line number) stays the same through the history of the file, this will show you the contents of the line at each commit:

git log --follow --pretty=format:"%h" -- 'path/to/file' | while read -r hash; do echo $hash && git show $hash:'path/to/file' | head -n 544 | tail -n1; done

Change 544 to the line number and path/to/file to the file path.


You can mix git blame and git log commands to retrieve the summary of each commit in the git blame command and append them. Something like the following bash + awk script. It appends the commit summary as code comment inline.

git blame FILE_NAME | awk -F" " \
'{
   commit = substr($0, 0, 8);
   if (!a[commit]) {
     query = "git log --oneline -n 1 " commit " --";
     (query | getline a[commit]);
   }
   print $0 "  // " substr(a[commit], 9);
 }'

In one line:

git blame FILE_NAME | awk -F" " '{ commit = substr($0, 0, 8); if (!a[commit]) { query = "git log --oneline -n 1 " commit " --"; (query | getline a[commit]); } print $0 "  // " substr(a[commit], 9); }'

Here is a solution that defines a git alias, so you will be able use it like that :

git rblame -M -n -L '/REGEX/,+1' FILE

Output example :

00000000 18 (Not Committed Yet 2013-08-19 13:04:52 +0000 728) fooREGEXbar
15227b97 18 (User1 2013-07-11 18:51:26 +0000 728) fooREGEX
1748695d 23 (User2 2013-03-19 21:09:09 +0000 741) REGEXbar

You can define the alias in your .gitconfig or simply run the following command

git config alias.rblame !sh -c 'while line=$(git blame "$@" $commit 2>/dev/null); do commit=${line:0:8}^; [ 00000000^ == $commit ] && commit=$(git rev-parse HEAD); echo $line; done' dumb_param

This is an ugly one-liner, so here is a de-obfuscated equivalent bash function :

git-rblame () {
    local commit line
    while line=$(git blame "$@" $commit 2>/dev/null); do
        commit="${line:0:8}^"
        if [ "00000000^" == "$commit" ]; then
            commit=$(git rev-parse HEAD)
        fi
        echo $line
    done
}

The pickaxe solution ( git log --pickaxe-regex -S'REGEX' ) will only give you line additions/deletions, not the other alterations of the line containing the regular expression.

A limitation of this solution is that git blame only returns the 1st REGEX match, so if multiple matches exist the recursion may "jump" to follow another line. Be sure to check the full history output to spot those "jumps" and then fix your REGEX to ignore the parasite lines.

Finally, here is an alternate version that run git show on each commit to get the full diff :

git config alias.rblameshow !sh -c 'while line=$(git blame "$@" $commit 2>/dev/null); do commit=${line:0:8}^; [ 00000000^ == $commit ] && commit=$(git rev-parse HEAD); git show $commit; done' dumb_param

This will call git blame for every meaningful revision to show line $LINE of file $FILE:

git log --format=format:%H $FILE | xargs -L 1 git blame $FILE -L $LINE,$LINE

As usual, the blame shows the revision number in the beginning of each line. You can append

| sort | uniq -c

to get aggregated results, something like a list of commits that changed this line. (Not quite, if code only has been moved around, this might show the same commit ID twice for different contents of the line. For a more detailed analysis you'd have to do a lagged comparison of the git blame results for adjacent commits. Anyone?)


I don't believe there's anything built-in for this. It's made tricky by the fact that it's rare for a single line to change several times without the rest of the file changing substantially too, so you'll tend to end up with the line numbers changing a lot.

If you're lucky enough that the line always has some identifying characteristic, e.g. an assignment to a variable whose name never changed, you could use the regex choice for git blame -L. For example:

git blame -L '/variable_name *= */',+1

But this only finds the first match for that regex, so if you don't have a good way of matching the line, it's not too helpful.

You could hack something up, I suppose. I don't have time to write out code just now, but... something along these lines. Run git blame -n -L $n,$n $file. The first field is the previous commit touched, and the second field is the line number in that commit, since it could've changed. Grab those, and run git blame -n $n,$n $commit^ $file, i.e. the same thing starting from the commit before the last time the file was changed.

(Note that this will fail you if the last commit that changed the line was a merge commit. The primary way this could happen if the line was changed as part of a merge conflict resolution.)

Edit: I happened across this mailing list post from March 2011 today, which mentions that tig and git gui have a feature that will help you do this. It looks like the feature has been considered, but not finished, for git itself.