Notes to self

Automating tasks and improving developer workflow with pre-commit git hooks

Probably the most usual git workflow is adding changes to the staging area, committing them, and pushing them to the remote repository. Depending on the project we are working on there are some things that we should be checking, validating or generating before heading for the commit. That’s where pre-commit hooks come in.

What’s pre-commit hook? Git has various checkpoints that we can hook into by providing an executable in .git/hooks/ with the name of the hook. One of them is the pre-commit hook that is ran automatically by git before commiting changes to the repository. If we want git to run arbitrary code before each and every commit we simply create .git/hooks/pre-commit executable file with the said code.

So what would be some things that would make sense to do in the pre-commit? It could be things that guards you before committing bad code, but at the same time not those that you don’t want to run every time you git commit.

A great example would a code formatter that fix styling issues before there are committed. Or any kind of static analyses (such as Brakeman) or validator that does not run too long. Test suites are usually out of the question because they run too long, but if you can run only unit tests of the project for the changed files, than that would be awesome too.

My recent git hook is a task that generate multi-language documentation from a single API Blueprint. If the API Blueprint file changed, it generates the new per-language variations of the file automatically. It’s pretty neat, because it’s easy to forget to regenerate those files by hand, and so they never go out of sync.

It looks similary to the following:

$ cat .git/hooks/pre-commit
#!/usr/bin/sh
if [ $(git diff --cached --name-only | grep specification.apib) ]; then
  echo "API Blueprint changed."
  ...task...
  fi
fi

Now, there are few things to pay attention too. First, you can see I am running git diff --cached instead of regular git diff so that git gives me the changes I would expect. Similarly, we also have to git add all new changes too so that they get committed as expected.

Also note that if the pre-commit fails, the commit is aborted. If your check already returns nonzero status code and that it’s the last command, this should already be preventing wrong commits. For soft checks I recommend to be forgiving. For example, I would first check if the dependencies to regenerate this documentation are present, and if not I would let the check succeed with a warning.

At last, distribution. It would be a shame to make great hooks without sharing them with your colleagues. Since hooks live in a place where we cannot directly add them to the given repository, we should put them elsewhere and document the following command that will add the said hook:

$ git config core.hooksPath ../git-hooks-in-repo/

Work with me

I have some availability for contract work. I can be your fractional CTO, a Ruby on Rails engineer, or consultant. Write me at strzibny@strzibny.name.

RSS