Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git: discover which commits ever touched a range of lines

Tags:

git

blame

I'm having trouble figuring out how to use git blame for getting the set of commits that ever touched a given range of lines. There are similar questions like this one but the accepted answer doesn't bring me much further.

Let's say I have a definition that starts on line 1000 of foo.rb. It's only only 5 lines long, but the number of commits that ever changed those lines is enormous. If I do

git blame foo.rb -L 1000,+5 

I get references to (at most) five distinct commits that changed these lines, but I'm also interested in the commits "behind them".

Similarly,

git rev-list HEAD -- foo.rb | xargs git log --oneline 

is almost what I want, but I can't specify line ranges to git rev-list

Can I pass a flag to git blame to get the list of commits that ever touched those five lines, or what's the quickest way to build a script that extracts such information? Let's ignore for the moment the possibility that the definition once had more or less than 5 lines.

like image 793
Joao Tavora Avatar asked Jan 03 '13 16:01

Joao Tavora


People also ask

What git command do you need to use to know who changed certain lines in a specific file?

The git blame command is used to examine the contents of a file line by line and see when each line was last modified and who the author of the modifications was.

How do I know which commit changed a file?

Find what file changed in a commit To find out which files changed in a given commit, use the git log --raw command. It's the fastest and simplest way to get insight into which files a commit affects.

How do you see all commits for a file?

Use git log --all <filename> to view the commits influencing <filename> in all branches.

How do I show more lines in git diff?

git diff has the -U<n> option which allows you to customize the number of lines to show around a change. For example, git diff -U5 ... will show 5 lines of context.


2 Answers

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:

((aa27064...))[mlm@macbook:~/w/mlm/git] $ 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:

((aa27064...))[mlm@macbook:~/w/mlm/git] $ git log --topo-order --graph -u -L 155,155:git-web--browse.sh * commit 81f42f11496b9117273939c98d270af273c8a463 | Author: Giuseppe Bilotta <[email protected]> | Date:   Fri Dec 3 17:47:38 2010 +0100 |  |     web--browse: support opera, seamonkey and elinks |      |     The list of supported browsers is also updated in the documentation. |      |     Signed-off-by: Giuseppe Bilotta <[email protected]> |     Signed-off-by: Junio C Hamano <[email protected]> |  | 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]> | Date:   Fri Dec 3 17:47:36 2010 +0100 |  |     web--browse: coding style |      |     Retab and deindent choices in case statements. |      |     Signed-off-by: Giuseppe Bilotta <[email protected]> |     Signed-off-by: Junio C Hamano <[email protected]> |  | 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]>   Date:   Sat Feb 2 07:32:53 2008 +0100        Rename 'git-help--browse.sh' to 'git-web--browse.sh'.        Signed-off-by: Christian Couder <[email protected]>       Signed-off-by: Junio C Hamano <[email protected]>    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) 

If you use this functionality frequently, you might find a git alias useful. To do that, put in your ~/.gitconfig:

[alias]     # Follow evolution of certain lines in a file     # arg1=file, arg2=first line, arg3=last line or blank for just the first line     follow = "!sh -c 'git log --topo-order -u -L $2,${3:-$2}:"$1"'" - 

And now you can just do git follow git-web--browse.sh 155.

like image 151
Matt McClure Avatar answered Sep 27 '22 20:09

Matt McClure


I think this is what you want:

git rev-list HEAD -- foo.rb | (      while read rev; do         git blame -l -L 1000,+5 $rev -- foo.rb | cut -d ' ' -f 1     done; ) | awk '{ if (!h[$0]) { print $0; h[$0]=1 } }' 

That'll output the the revision number for each commit that has an edit to the lines you've chosen.

Here are the steps:

  1. The first part git rev-list HEAD -- foo.rb outputs all revisions in which the chosen file is edited.

  2. Each of those revisions then goes into the second part, which takes each one and puts it into git blame -l -L 1000,+5 $rev -- foo.rb | cut -d ' ' -f 1. This is a two-part command.

    1. git blame -l -L 1000,+5 $rev -- foo.rb outputs the blame for the chosen lines. By feeding it the revision number, we are telling it to start from that commit and go from there, rather than starting at the head.
    2. Since blame outputs a bunch of info we don't need, cut -d ' ' -f 1 gives us the first column (the revision number) of the blame output.
  3. awk '{ if (!h[$0]) { print $0; h[$0]=1 } }' takes out non-adjacent duplicate lines while maintaining the order they appeared in. See http://jeetworks.org/node/94 for more info about this command.

You could add a last step here to get prettier output. Pipe everything into xargs -L 1 git log --oneline -1 and get the corresponding commit message for the list of revisions. I had a weird issue using this last step where I had to keep pressing next every few revisions that were output. I'm not sure why that was, which is why I didn't include it in my solution.

like image 30
Jonathan Wren Avatar answered Sep 27 '22 21:09

Jonathan Wren