Overcommit: The Opinionated Git Hook Manager
At Causes, we care deeply about code quality. We promote thorough, offline code review through Gerrit and take pride in each commit we make. Due to the sheer volume of code review and the number of engineers on our team, it’s important that by the time other engineers review our code we have an established baseline of quality.
There are a few important ingredients to making a good commit:
- Correctness: The code does what you expect it to do
- Commit message: Tim Pope provides an excellent summary of what makes a good commit message
- Style: The code matches our team’s coding styles
- Test coverage: relevant tests have been run, and any new features have spec coverage
Enter overcommit. This evolved from a single file linter into a full-fledged, extensible hook architecture. Available as a Ruby gem:
gem install overcommit
What does it do? In short, it automates away all the tedium before a commit reaches code review. It ships with a set of opinionated lints that ensure a level of consistency and quality in our commits.
In Action
Here’s an example of overcommit saving me from committing janky code:
❯❯❯ echo "eval('alert(\"hello world\")');" > eval.js
❯❯❯ git add eval.js
❯❯❯ git commit
Running pre_commit checks
Checking causes_email...........OK
Checking test_history...........No relevant tests for this change...write some?
Checking restricted_paths.......OK
Checking js_console_log.........OK
Checking js_syntax..............FAILED
eval.js: line 1, col 1, eval can be harmful.
1 error
Checking author_name............OK
Checking whitespace.............OK
!!! One or more pre_commit checks failed
Installation
After installing the gem, a new binary, overcommit
, will be available. You can
use this binary to install git hooks into an existing repository like so:
overcommit my-project
Where my-project
is the directory of a git repository. In addition to
installing hooks into my-project/.git/hooks
, this will also write an
overcommit.yml
file containing repository-specific configuration. You can use
this, for example, to always skip a certain type of lint. See more options by
running overcommit --help
.
Built-in functionality
pre-commit hooks
coffee_lint
uses CoffeeLint to keep your CoffeeScript clean and consistent.haml_syntax
verifies that any Haml file to be committed is syntactially valid.js_syntax
uses jshint to ensure best JavaScript practices are followed.python_flake8
uses flake8 to lint Python code.ruby_syntax
makes sure that any Ruby files to be committed are syntactically valid by runningruby -c #{staged_file}
.scss_lint
integrates with our scss-lint gem to ensure our stylesheets are the best they can be. This includes alphabetizing properties, removing unnecessary units, and so much more. Read more about what gets linted.whitespace
verifies that no hard tabs are used and that no trailing whitespace is included. The devil is in the details.
There are more hooks included, including Causes-specific ones (such as making sure your @causes.com email address is used), but these are excluded by default. See the rest of the lints here.
commit-msg hooks
russian_novel
is just for fun, to reward developers for writing exemplary (long) commit messages.text_width
ensures that the body of the commit is hard-wrapped to 72 characters, and that the subject is <= 60 characters.trailing_period
warns the author if their commit message subject ends with a period.
Extensibility
In addition to the gem-provided, global hooks, overcommit
also allows for
repository-specific hooks to be added. An example from the documentation is our
Chef repository-specific hook to run
Foodcritic against any cookbooks being
committed.
This file lives in kitchen.git/.githooks/pre_commit/food_critic.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Skipping checks
There are, of course, times when you’re going to need to break the rules. You
can skip any lint by passing the underscore-ized name into the SKIP_CHECKS
environment variable. This can either be a single lint, or a
comma/colon-separated list of lints to skip:
SKIP_CHECKS=js_syntax:restricted_paths git commit
There’s also the special value of all
, which will skip all of the non-required
lints.
The future
Until recently, overcommit has only been useful inside Causes due to the specific lints we run and the lack of easy installation. We hope others find it useful, and have released it under the MIT license. Pull requests are welcome. View the source on GitHub.
Happy hacking.