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.
I think this is what you want:
That'll output the the revision number for each commit that has an edit to the lines you've chosen.
Here are the steps:
The first part
git rev-list HEAD -- foo.rb
outputs all revisions in which the chosen file is edited.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.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.cut -d ' ' -f 1
gives us the first column (the revision number) of the blame output.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.A few thoughts..
This sounds similar to this post, and it looks like you might get close with something like this:
As long as you know the definition to match against (for the regex).
There is a thread discussion here, about using
tig
andgit gui
(which apparently might handle this). I haven't tried this myself yet, so can't verify it (I'll give this a try later).I liked this puzzle, it's got its subtleties. Source this file, say
init foo.rb 1000,1005
and follow the instructions. When you're done, file@changes
will have the correct list of commits in topological order and@blames
will have the actual blame output from each.This is dramatically more complex than the accepted solution above. It produces output that will sometimes be more useful, and hard to reproduce, and it was fun to code.
The problem with trying to track line-number ranges automatically while stepping backward through history is if a change hunk crosses line-numbered range boundaries you can't automatically determine where in that hunk the new range boundary should be, and you'll either have to include a big range for big additions and so accumulate (sometimes lots of) irrelevant changes, or drop into manual mode to be sure it's right (which of course gets you right back here), or accept extreme lossage at times.
If you want your output to be exact, use the answer above with trustworthy regex ranges like `/^type function(/,/^}/', or use this, which isn't actually that bad, a couple seconds per step back in time.
In exchange for the extra complexity, it does produces the hitlist in topological sequence and it does at least (fairly successfully) try to ameliorate the pain at each step. It never runs a redundant blame, for instance, and update-ranges makes adjusting line numbers easier. And of course there's the reliability of having had to individually eyeball the hunks... :-P
To run this on full auto, say
{ init foo.rb /^class foo/,/^end/; auto; } 2>&-
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:And you want to know the history of what is now line 155.
Then:
If you use this functionality frequently, you might find a git alias useful. To do that, put in your
~/.gitconfig
:And now you can just do
git follow git-web--browse.sh 155
.Please refer to the answer posted here List all commits for a specific file. Its exactly what you need.
Not sure what you want to do, but maybe git log -S can do the trick for you:
You can put in string the change (or part of the change) you are trying to follow and this will list the commits that ever touched this change.