Red Green Repeat Adventures of a Spec Driven Junkie

How-to: Find Who Wrote A Line in git Repository

I’m building on a previous article where I categorize all the rubocop issues.

My goal is to find out who committed the infraction. Not to point fingers, just to see if there are problems I can solve further up in the development process.

I review two ways to figure out how to get who wrote a line of code in a git repository using git blame, one using grep and another using additional git blame syntax: -L.

Understanding what built-in options to a program are can solve problems better than working around it with external tools.

This article will take you less than four minutes to read.

Emperor Xuanzong's Flight to Shu source and more information

Introduction

I would just like a command that tells me who wrote a specific line of a file.

Something like:

who-wrote filename line_number

Is that too much to ask for?? :-)

git blame

If the version control of the code I am investigating uses git, then there’s git blame:

git blame <filename>

For example from my TDD Callbacks repository:

$ git blame spec/model/order_spec.rb
4aefedef (Andrew Leung 2019-01-18 22:58:31 +0000  1) require 'rails_helper'
4aefedef (Andrew Leung 2019-01-18 22:58:31 +0000  2)
4aefedef (Andrew Leung 2019-01-18 22:58:31 +0000  3) RSpec.describe Order, type: :model do
68b6a236 (Andrew Leung 2019-01-18 22:59:51 +0000  4)   it 'new orders are created with :open status' do
68b6a236 (Andrew Leung 2019-01-18 22:59:51 +0000  5)     order = Order.new
68b6a236 (Andrew Leung 2019-01-18 22:59:51 +0000  6)
68b6a236 (Andrew Leung 2019-01-18 22:59:51 +0000  7)     order.save
68b6a236 (Andrew Leung 2019-01-18 22:59:51 +0000  8)
68b6a236 (Andrew Leung 2019-01-18 22:59:51 +0000  9)     expect(order.status).to eq('open')
68b6a236 (Andrew Leung 2019-01-18 22:59:51 +0000 10)   end
...

The format of the output of git blame is:

<Commit SHA> (<Author> <Timestamp> <Line number>) <code>

This is good, it has what I want: for a given filename and line number, return who wrote the code for a specific line.

It’s great to get details of the whole file. How can I narrow this down further?

  • grep
  • git blame -L

Let’s try both and see how they work.

How: grep

One way to do this is to use grep for the specific line number you want:

git blame <file> | grep <line number>

For example, line 25:

[email protected]:~/tdd_indepth_callbacks$ git blame spec/models/order_spec.rb | grep 25
5bcbbe57 (Andrew Leung 2019-01-18 23:10:06 +0000 25)     order.save

Done, right?

What if I want line 33?

[email protected]:~/tdd_indepth_callbacks$ git blame spec/models/order_spec.rb | grep 33
8c9461cc (Andrew Leung 2019-01-18 23:15:53 +0000 31)     order = Order.create(:amount => 0.33)
8c9461cc (Andrew Leung 2019-01-18 23:15:53 +0000 32)     order.received = 0.33
8c9461cc (Andrew Leung 2019-01-18 23:15:53 +0000 33)

Oh, it’s easy to just grep for 33) instead:

[email protected]:~/tdd_indepth_callbacks$ git blame spec/models/order_spec.rb | grep 33)
bash: syntax error near unexpected token `)'

What if I escape it:

[email protected]:~/tdd_indepth_callbacks$ git blame spec/models/order_spec.rb | grep 33\)
8c9461cc (Andrew Leung 2019-01-18 23:15:53 +0000 31)     order = Order.create(:amount => 0.33)
8c9461cc (Andrew Leung 2019-01-18 23:15:53 +0000 33)

Better, not exactly what I want and it would produce false positives or require additional filtering, definitely possible with awk.

Is there a better way? Let’s try the next option: git blame -L

How: -L

git blame has a feature to list a specific range of lines of a file:

git blame <file> -L <start>,<end>

documentation

[email protected]:~/tdd_indepth_callbacks$ git blame spec/models/order_spec.rb -L 25
5bcbbe57 (Andrew Leung 2019-01-18 23:10:06 +0000 25)     order.save
5bcbbe57 (Andrew Leung 2019-01-18 23:10:06 +0000 26)
5bcbbe57 (Andrew Leung 2019-01-18 23:10:06 +0000 27)     expect(order.status).to eq('received')
5bcbbe57 (Andrew Leung 2019-01-18 23:10:06 +0000 28)   end
8c9461cc (Andrew Leung 2019-01-18 23:15:53 +0000 29)
8c9461cc (Andrew Leung 2019-01-18 23:15:53 +0000 30)   it 'also handles potential irrational numbers' do
8c9461cc (Andrew Leung 2019-01-18 23:15:53 +0000 31)     order = Order.create(:amount => 0.33)
...

Hmm, too much output. I wonder if I can just specify the beginning to be the same as the end.

[email protected]:~/tdd_indepth_callbacks$ git blame spec/models/order_spec.rb -L 25,25
5bcbbe57 (Andrew Leung 2019-01-18 23:10:06 +0000 25)     order.save

Perfect.

What about line 33? That failed miserably using grep:

[email protected]:~/tdd_indepth_callbacks$ git blame spec/models/order_spec.rb -L 33,33
8c9461cc (Andrew Leung 2019-01-18 23:15:53 +0000 33)

Nice! Exactly the output expected. No additional processing needed.

Conclusion

Figuring out who wrote a specific line of code is best using the following syntax:

git blame <filename> -L <line number>,<line number>

Which returns the output in the following format:

<Commit SHA> (<Author> <Timestamp> <Line number>) <code>

I am glad I dug a bit more into git documentation as using grep would have been an OK solution, overlapping numbers in the code section would require additional (surprising) work.