Retrieve the commit log for a specific line in a file?
Asked Answered
G

12

675

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.

Grampositive answered 8/12, 2011 at 17:51 Comment(1)
See also: Git blame -- prior commitsCamise
S
820

See also Git: discover which commits ever touched a range of lines.


Since Git 1.8.4, git log has -L to view the evolution of a range of lines.

For example, suppose you look at git blame's output. Here -L 150,+11 means "only look at the lines 150 to 150+11":

$ git blame -L 150,+11 -- git-web--browse.sh
a180055a git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:36 +0100 150)            die "The browser $browser is not
a180055a git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:36 +0100 151)    fi
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 152) fi
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 153) 
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 154) case "$browser" in
81f42f11 git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:38 +0100 155) firefox|iceweasel|seamonkey|iceape)
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 156)    # Check version because firefox < 2.0 do
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 157)    vers=$(expr "$($browser_path -version)" 
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 158)    NEWTAB='-new-tab'
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 159)    test "$vers" -lt 2 && NEWTAB=''
a0685a4f git-web--browse.sh (Dmitry Potapov   2008-02-09 23:22:22 -0800 160)    "$browser_path" $NEWTAB "$@" &

And you want to know the history of what is now line 155.

Then, use git log. Here, -L 155,155:git-web--browse.sh means "trace the evolution of lines 155 to 155 in the file named git-web--browse.sh".

$ git log --pretty=short -u -L 155,155:git-web--browse.sh
commit 81f42f11496b9117273939c98d270af273c8a463
Author: Giuseppe Bilotta <[email protected]>

    web--browse: support opera, seamonkey and elinks

diff --git a/git-web--browse.sh b/git-web--browse.sh
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
@@ -143,1 +143,1 @@
-firefox|iceweasel)
+firefox|iceweasel|seamonkey|iceape)

commit a180055a47c6793eaaba6289f623cff32644215b
Author: Giuseppe Bilotta <[email protected]>

    web--browse: coding style

diff --git a/git-web--browse.sh b/git-web--browse.sh
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
@@ -142,1 +142,1 @@
-    firefox|iceweasel)
+firefox|iceweasel)

commit 5884f1fe96b33d9666a78e660042b1e3e5f9f4d9
Author: Christian Couder <[email protected]>

    Rename 'git-help--browse.sh' to 'git-web--browse.sh'.

diff --git a/git-web--browse.sh b/git-web--browse.sh
--- /dev/null
+++ b/git-web--browse.sh
@@ -0,0 +127,1 @@
+    firefox|iceweasel)
Symbiosis answered 3/11, 2013 at 20:8 Comment(11)
'git log --topo-order --graph -u -L 155,155:git-web--browse.sh' - this has given a fatal error: 'invalid object name 155,155'. Git version: 1.8.3.2. Any suggestions?Playa
Upgrade to Git 1.8.4 or later.Symbiosis
what if I want to know "the history of what is line 155 in commitA" (instead of line 155 in the HEAD). Can I simply use git log commitA-hash -L 155,155:file-name?Restharrow
@Flimm, I don't have a strong preference.Symbiosis
this works well, except if the file has been moved/renamed...it seems that --follow does not like to be combined with line range args.Anthropophagi
The --oneline option doesn't seem to work with the -L option in git log. Since there is a command which does a similar thing (git blame) I wonder if it's intentional?Bolton
indeed --pretty=short is ignored here. Please remove itShaduf
Consider stashing your local changes if any in order to have the 'right' line numbers.Orientalism
This is useful, but didn't help me in a large file with many line insertions and deletions, such that the lines that interest me are currently 1412:1420 but these lines were at different positions in earlier versions due to other unrelated edits.Neona
How to do this without diffOrleanist
This works but offers a complete diff of the range. This is very cumbersome as I only want to view the commit messages.Emilioemily
C
76

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
Communism answered 8/12, 2011 at 18:46 Comment(3)
I'm not sure I see how this helps. If the text was affected, then the line's not the same anymore, so pickaxe will only show you the most recent change. You'd then have to do git log -S'the previous version of the line' and so on, exactly like you'd end up doing with git blame -L. And it'll be way slower than git blame, since it has to look for the text everywhere, not just in the given place.Jebel
You can use a regex in there to make it more accepting. Currently there is no way to "navigate through patches" back in time without some elaborate scripting. I'm hoping that gitk will get this functionality in it's patch view in the future.Communism
why is this called pick-axe ? :)Exonerate
S
70

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 information can be found here.

Saturday answered 13/8, 2015 at 9:56 Comment(2)
use -s if you don't want to see diffHanahanae
What is the purpose of the -u flag? I struggled to find any proper explanation of it in the docs, and ChatGPT tells me it's not a valid option (GPT could be outdated or wrong of course).Orest
M
25

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

Marya answered 9/11, 2017 at 13:26 Comment(1)
git blame -L14,14 -- <file_path> works also if you're interested only in Line 14.Tonita
E
22

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.

Eraser answered 25/3, 2015 at 16:48 Comment(0)
J
13

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.

Jebel answered 8/12, 2011 at 19:45 Comment(0)
S
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
Spice answered 19/8, 2013 at 13:17 Comment(1)
I think this wont work in mac correct?Heterodox
R
7

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?)

Rossman answered 23/7, 2013 at 7:24 Comment(2)
Again I think this doesn't work, because it doesn't track the previous location of that line. So if a line was added 2 commits ago, you would be looking at another lineShaduf
I couldn't get the above to work property for me, but this works just nicely. It's kind of a combination of all the answers here: git log --format=format:%H path/to/file | xargs -L 1 -I % git blame -L '/var myVar = */',+1 % -- path/to/file Where you use '/var myVar = */' as whatever regular expression match you need for the line to account for any changes. For me, I needed to check some values that kept being added/removed from a config file, so it was fairly easy.Execratory
B
6

Simple and easy git blame command Line Start & end (735,750)

L735,750 (Syntax)

git blame -L735,750 patient.php (File path & name)

Bindweed answered 13/1, 2022 at 11:24 Comment(0)
H
4

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.

Hyperon answered 13/1, 2017 at 10:20 Comment(0)
A
3

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); }'
Artiste answered 5/3, 2016 at 0:34 Comment(0)
T
1

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.

Thaddeusthaddus answered 1/12, 2020 at 20:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.