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_lintuses CoffeeLint to keep your CoffeeScript clean and consistent.haml_syntaxverifies that any Haml file to be committed is syntactially valid.js_syntaxuses jshint to ensure best JavaScript practices are followed.python_flake8uses flake8 to lint Python code.ruby_syntaxmakes sure that any Ruby files to be committed are syntactically valid by runningruby -c #{staged_file}.scss_lintintegrates 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.whitespaceverifies 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_novelis just for fun, to reward developers for writing exemplary (long) commit messages.text_widthensures that the body of the commit is hard-wrapped to 72 characters, and that the subject is <= 60 characters.trailing_periodwarns 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.