The Importance of Git Hooks
So I was working this morning and got one of those bloody awful Rails stack traces – every error was at the system layer and I could not make any progress debugging it. So I looked at git status and there were like 25 + files all tied to an overall CSV import process (2 data models, 1 controller, system level UI changes, etc) and then it hit me – I need my .git hooks setup on this project! Every bit of source code under management by git has a .git directory at the top level and under it a hooks directory. Hooks are executable files that are run when you do an atomic level git action. For example there is one called pre-commit and this is code that is executed BEFORE a commit actually happens. What you can do with a pre-commit hook is tie in a code evaluation process (think lint if you're a C guy) to find any errors before they are committed. Now I've set these up in almost every project but not here so a quick copy of a file from another project to .git/hooks gave me this:
app/controllers/csv_imports_controller.rb: 8: syntax error, unexpected ')', expecting ']' @csv_import = current_user.csv_imports.find(params[:id)) ^ 24: syntax error, unexpected $end, expecting keyword_end
And here's the offending line in question:
@csv_import = current_user.csv_imports.find(params[:id))
Yep. params[:id]) was needed (second to last char was a ) instead of a ]. I looked at that line like 4 times and didn't see it.
Having used these now for several years, its hard for me to imagine working in a dynamic language without them. Kudos to Dv for setting these up once upon a time.
##.git/hooks for the win!
And here's the code for .git/hooks/pre-commit Adjust the shebang line accordingly and make sure its an executable file.
#!/usr/bin/env ruby # # A hook script to verify that only syntactically valid ruby code is commited. # Called by git-commit with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # Put this code into a file called "pre-commit" inside your .git/hooks # directory, and make sure it is executable ("chmod +x .git/hooks/pre-commit") puts "in .git/hooks/pre-commit" require 'open3' include Open3 # Set this to true if you want warnings to stop your commit stop_on_warnings = false stop_on_errors = true # compiler_ruby = `which rbx`.strip # compiler_ruby = `which ruby`.strip if compiler_ruby.length == 0 # setting compiler to ruby-1.9.3 since `which ruby` was setting it to 1.8.7 compiler_ruby = "$HOME/.rvm/rubies/ruby-1.9.3-p484/bin/ruby" changed_ruby_files = `git diff-index --name-only --cached HEAD`.split("\n").inject([]) do |files, line| files << line.chomp if line =~ /(.+\.(e?rb|task|rake|thor)|[Rr]akefile|[Tt]horfile)/ files end problematic_files = changed_ruby_files.inject([]) do |problematic_files, file| if File.readable? file cmd = if file =~ /\.erb\z/ # Set trim mode to "-", just as Rails does # Replacing all <%= tags with <% for the syntax check "sed 's/<%=/<%/g' #{file} | erb -xT - | #{compiler_ruby} -wc" else "#{compiler_ruby} -wc #{file}" end errors = nil popen3(cmd) do |stdin, stdout, stderr| errors = stderr.read.split("\n") end errors.reject!{ |line| line =~ /[0-9]+:\s+warning:/ } unless stop_on_warnings errors.reject!{ |line| line =~ /[0-9]+:\s+syntax error/ } unless stop_on_errors unless errors.empty? errors.map!{ |line| line.sub(/#{file}:/, '') } problematic_files << "#{file}:\n#{errors.join("\n")}" end end problematic_files end if problematic_files.size > 0 $stderr.puts problematic_files.join("\n\n") exit 1 else puts "no syntax errors" # All is well exit 0 end