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.

  • How do you fork your own project on github?
  • Git rebase: Combine non-subsequent commits
  • How to resolve merge conflicts in Git?
  • Git repository structure and modularity
  • Pipes in a git Alias?
  • Branch Remote from other computer
  • 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.

  • Middleman asks if I want to use compass when starting a new project
  • git: different branch creation and merging techniques
  • How do I log unique authors in git?
  • Stage all but one folder
  • Git: Pulling from remote
  • A merge was done wrong. How can I fix it?
  • 9 Solutions collect form web for “Retrieve the commit log for a specific line in a file?”

    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 -L150,+11 means “only look at the lines 150 to 150+11”:

    $ git blame -L150,+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 <giuseppe.bilotta@gmail.com>
    
        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 <giuseppe.bilotta@gmail.com>
    
        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 <chriscool@tuxfamily.org>
    
        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)
    

    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 --pretty=short -u -L <upperLimit>,<lowerLimit>:<path_to_filename>
    

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

    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.

    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.

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

    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
    

    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.

    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); }'
    
    Git Baby is a git and github fan, let's start git clone.