tekin.co.uk

Better Git diff output for Ruby, Python, Elixir, Go and more

The regular Git users amongst you will be familiar with the diff output that breaks down into “hunks” like so:

@@ -24,7 +24,7 @@ class TicketPdf
     ApplicationController.render(
       "tickets/index.html.haml",
       layout: "tickets",
-      assigns: { tickets: tickets }
+      assigns: { tickets: tickets, event_name: event_name }
     )
   end

The first line (starting @@) is known as the hunk header, and is there to help orientate the change. It gives us the line numbers for the change (the numbers between the @@..@@), but also a textual description for the enclosing context where the change happened, in this example "class TicketPdf". Git tries to figure out this enclosing context, whether it’s a function, module or class definition. For C-like languages it’s pretty good at this. But for the Ruby example above it’s failed to show us the immediate context, which is actually a method called tickets_as_html. That’s because out of the box Git isn’t able to recognise the Ruby syntax for a method definition, which would be def ticket_as_html.

What we really want to see is:

@@ -24,7 +24,7 @@ def tickets_as_html
     ApplicationController.render(
       "tickets/index.html.haml",
       layout: "tickets",
-      assigns: { tickets: tickets }
+      assigns: { tickets: tickets, event_name: event_name }
     )
   end

And it’s not just Ruby where Git struggles to figure out the correct enclosing context. Many other programming languages and file formats also get short-changed when it comes to the hunk header context.

Thankfully, it’s not only possible to configure a custom regex specific to your language to help Git better orient itself, there’s even a pre-defined set of patterns for many languages and formats right there in Git. All we have to do is tell Git which patterns to use for our file extensions.

We can do this by defining a gitattributes file inside our repo that maps the Ruby file extensions to the diff pattern for Ruby:

*.rb diff=ruby
*.rake diff=ruby

Some open source projects define their own .gitattributes file. There’s one in Rails. There’s even one in the Git source that enables the diff patterns for Perl and Python.

Configure a global .gitattributes file

Instead of adding a .gitattributes file to every repo we can configure a global .gitattributes file. Just create a .gitattributes file in your home directory, fill it with all the file formats you are interested in and point Git at it:

$ git config --global core.attributesfile ~/.gitattributes

I’ve put together an example .gitattributes file with some common file formats to get you started.

I have no idea why Git doesn’t have these file format patterns configured by default. Thanks to Tom whose exasperated tweet brought this non-obvious feature to my attention.

Update: Bonus diff pattern for Rspec users

Thanks to @jryan727 for sharing a custom pattern for Rspec. Add this to your Git config:

[diff "rspec"]
  xfuncname = "^[ \t]*((RSpec|describe|context|it|before|after|around|feature|scenario)[ \t].*)$"

And *_spec.rb diff=rspec to your .gitattributes file for improved hunk headers for Rspec files.

More articles on Git

Authored by Published by

Want more juicy Git tips like this straight to your inbox?

I send an email whenever I have a new insight or tip to share. Zero spam, and you can unsubscribe whenever you like with a single click.