Git Precommit Hook Awesomeness

- - | Comments

What’s a precommit hook?

Precommit hooks are the most awesome and straightforward line of defense to add into your build system. They’re the earliest intervention we can use to make sure that bad code doesn’t make its way to production.

Precommit hooks are basically bash scripts that are run by the git executable before every single commit. This bash script can do whatever it needs to do to to verify that the commit is good, and should proceed. The script can exit with a non 0 exit code to signal to git that it shouldn’t allow the commit.

Here’s an example of a very basic pre-commit hook.

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh

# run script to check if commit is valid
# you can run anything here, I happen to use a node script
node ./good-to-commit.js
RETVAL=$?

if [ $RETVAL -ne 0 ]
then
  exit 1
fi

If you put this into your .git/hooks/pre-commit file in your local cloned copy of a git repo, it will run every time that you commit. You can do whatever you’d like in good-to-commit.js. You can run a syntax validator, beautification validator, linter, all of your unit tests; it’s completely up to you and your team. The hard part is actually making sure that this precommit hook is installed on everyone’s machines.

Enforcing precommit hooks across your team

Git repos can’t actually include hooks inside when they are cloned. This would be a security issue because precommit hooks are run as the current user. We wouldn’t want someone to add rm -rf ~ in a precommit hook. I get around this in a very sneaky way, and when I say sneaky, I mean awesome.

Chances are, you’re working on a project that uses some build tool to handle common tasks. I am using grunt at the moment, but you can come up with equivalent methods with whatever tool that you use. The key idea is to use your build tools before-every-single-task hook to your advantage.

Using grunt, whenever any task is run, the entire Gruntfile.js is executed. In order to install precommit hooks for everyone who ever uses my project, I add this to the bottom of that file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//inside Gruntfile.js

grunt.registerTask('install-hook', function () {
  var fs = require('fs');

  // my precommit hook is inside the repo as /hooks/pre-commit
  // copy the hook file to the correct place in the .git directory
  grunt.file.copy('hooks/pre-commit', '.git/hooks/pre-commit');

  // chmod the file to readable and executable by all
  fs.chmodSync('.git/hooks/pre-commit', '755');
});

grunt.task.run('install-hook');

If you’re using rake or fabric, you can do very similar things. Every build tool will have a way to do it.

If you’re using npm and writing in node

If you’re using npm and node, you can do all of this sneaky hook registration in postinstall hook or use M. Chase Whittemore’s node-hooks project.

What you should do RIGHT NOW

I want you to go off and add precommit hooks to every project that you have. Let me know your experiences. I’ll talk to you in the comments and on twitter!

Comments