Skip to content

Instantly share code, notes, and snippets.

@mschuerig
Created June 23, 2012 12:51
Show Gist options
  • Save mschuerig/2978195 to your computer and use it in GitHub Desktop.
Save mschuerig/2978195 to your computer and use it in GitHub Desktop.
Pre-commit hook for git
#! /usr/bin/ruby -w
# Michael Schuerig, <[email protected]>, 2012.
# This file is in the public domain.
# Any trouble it may cause is yours, not mine.
#
# This is a pre-commit hook for git with a few checks I find useful
# useful in Rails projects. The checks are:
#
# * Bad tags (such as XXX, WTF, ...) as specified in
# git config hooks.pre-commit-bad-tags
# This options may contain a comma-separated list of strings
# or regular expressions. The later must be written as /expr/.
#
# * Paths in Gemfile.
#
# * Only allowed platforms in Gemfile.lock as specified in
# git config hooks.pre-commit-allowed-platforms
#
#
# Installation
#
# Put this file anywhere that's convenient. If it is on your PATH
# and executable, you can invoke it manually to run the checks.
#
# To use it as a pre-commit hook in a git repository, place a
# symbolic link to this file at <repository>/.git/hooks/pre-commit.
#
# To override checks when committing, invoke git commit with the
# --no-verify option.
require 'open3'
def main
ok = true
ok &&= check_staged_files
ok &&= check_gemfile
ok &&= check_gemfile_lock
exit ok ? 0 : 1
end
def shell(*cmd)
out, err = Open3.popen3(cmd.join(' ')) do |stdin, stdout, stderr|
[stdout.read, stderr.read]
end
unless err.empty?
raise "Git exited with an error #{cmd}:\n#{err}"
end
out
end
def git_config(name)
shell("git config '#{name}'").strip
end
def git_configs(name)
git_config(name).split(/\s*,\s*/)
end
def bad_tags
tags = git_configs('hooks.pre-commit-bad-tags')
tags.map { |t|
if %r{^/(?<pat>.*)/$} =~ t
Regexp.new(pat)
else
Regexp.new("\\b#{t}\\b")
end
}
end
def allowed_platforms
git_configs('hooks.pre-commit-allowed-platforms')
end
def staged_text_files
files = shell(%q{git status --porcelain | sed -n 's/^M. \(.*\)$/\1/p' | file --mime-type -f - | sed -n 's/\(.*\): *text\/[^ /]*$/\1/p'})
files.lines.map { |f| f.chomp }
end
def cat_staged_file(file)
shell("git show ':#{file}'")
end
def in_repo?(file)
!shell("git status --porcelain '#{file}'").strip.empty?
end
def gemfile_lock_platforms(gl = 'Gemfile.lock')
section = cat_staged_file(gl).lines.select do |l|
!!((/^PLATFORMS/ =~ l) .. (/^$/ =~ l))
end
section[1..-2].map(&:strip)
end
def report_bad_lines(bad_lines, message)
ok = true
if !bad_lines.empty?
ok = false
puts message
bad_lines.each do |line, num|
printf "%5d: %s\n", num + 1, line
end
end
ok
end
def check_staged_files
ok = true
tags = bad_tags
staged_text_files.each do |file|
bad_lines = cat_staged_file(file).lines.with_index.select { |line, num|
tags.any? { |t| t =~ line }
}
ok2 = report_bad_lines(bad_lines, "File #{file} contains lines with prohibited tags:")
ok &&= ok2
end
ok
end
def check_gemfile(g = 'Gemfile')
ok = true
if in_repo?(g)
bad_lines = cat_staged_file(g).lines.with_index.select { |line, num|
/^[^#]*\bpath\b/ =~ line
}
ok2 = report_bad_lines(bad_lines, "#{g} contains paths:")
ok &&= ok2
end
ok
end
def check_gemfile_lock(gl = 'Gemfile.lock')
ok = true
allowed = allowed_platforms
if !allowed.empty? && in_repo?(gl)
bad_platforms = gemfile_lock_platforms(gl).reject { |p|
allowed.include?(p)
}
unless bad_platforms.empty?
ok = false
puts "#{gl} for prohibited platform(s): #{bad_platforms.join(', ')}"
end
end
ok
end
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment