Friday, October 17, 2008

Git Hooks

Git hooks are scripts that are run by Git before or after certain commands. Because the hooks are run locally and not on the server it allows for a lot of freedom to write more in depth scripts. The scripts are located in the .git/hooks directory and Git comes with some sample scripts that can be enable by removing the .sample suffix and making it executable.

A nice example is the included pre-commit script which checks for trailing whitespace in the patch and exits with 1 if any are found. If a script exits with non-zero then the command is aborted.

Below is a simple, but very useful script. All it does is run make in your current directory if a Makefile exists. If make fails then it returns non zero preventing the commit from occurring. While this script is crude and verbose, it works in preventing commits the break the build. Compared to a server side script which has to build the entire project from scratch on potentially a slow machine, there should be nothing to build (you did run make before committing right?) so it should return very quickly. With this one script you can prevent most build errors from getting pushed up to the central repository and causing problems for other developers. One liner "fixes" that break the build can be a thing of the past.

pre-commit_make

#!/bin/sh
echo "--Attempting to build--"
if [ -f Makefile ] ; then
make --quiet
if [ $? != 0 ] ; then
echo "--Build failure--";
exit 1
fi
fi
echo "--Attempting to build pass--"
And really you can simplify it down to just this
#!/bin/sh
[ ! -f Makefile ] || make


Files in the .git/hooks directory are not part of the repository and so they are not tracked. A workaround is to have a git_hooks directory at the top of your repository like done in Arora and symlink .git/hooks to git_hooks whenever you clone. This way the hooks will be part of the project, under revision control and accessible to everyone.

When using git commit if you want to skip the hooks you can pass the --no-verify option.

Although many hooks you see are written in shell script they can be in any language don't even need to be a script, just an executable in the hooks directory.

An important thing to remember when writing hooks is that the patch and the list of patched files is available. A script that validates XML shouldn't do anything if the change doesn't modify xml files. 'git diff-index --name-only HEAD' is one simple way to get the list of all the changed files Using this you can make sure that no the hooks will always run quickly and only test/check what is needed.

There are a number of different hooks that git provides access to. For a complete list and details about what each of them are for checkout the Git hooks documentation.

Here are some hooks that I have either thought about doing, have already done, or found via Google:

pre-commit
- Check that the project compiles
- Check for compiler warnings
- Check that the auto tests for the files that are being modified still pass.
- Check that the performance tests for the files that are being modified still pass.
- Check that the code in the patch conforms to a code style
- Validate XML files
- Spell check user strings, documentation, etc
- Check for binary incompatibility
- Try to build a release package
- Check that any public API is documented and has no errors
- Verify any new files have a copyright header
- Check that all public strings are properly wrapped so they can be translated
- Check for calls to functions that are known to be deprecated or inefficient.
- Check for swear words
- Reject commits made between 4am and 7am with a note to go to bed.
- Automatically remove whitespace
- Run RSpec tests before allowing a commit
- Some rake testing

commit-msg
- Spell check the commit message
- Verify an agreed upon commit message format is used.
- Prevent business announcements or key words from leaking out in a message.

pre-rebase
- Useful for preventing an accidental rebase from occurring on a master branch that everyone shares.

post-commit
- Send out an e-mail to the mailing list
- Build a package
- Build/Update API documentation for the website
- Update/close bugs in a bug tracking system
- Spawn off builds on other operating systems

post-receive
- Update tickets in Lighthouse

I have mostly played around with the user side where each commit is one patch and life is pretty simple, but on the server a push can contain a number of commits and the scripts are only called once. Checking out the docs and the scripts in the git source code contrib/hooks directory for more information on how to extract each commit.

Taking advantage of the hooks system one can easily add checks for all sorts of things that can be automated resulting in always building source that is higher quality, more consistent, and with less embarrassing errors. The ease that anyone can add hooks will hopefully cause more and more projects to utilize this built in part of git.

If you have written an interesting hook feel free to post a link to it in the comments for others to checkout.

Hook photo taken by *L*u*z*a*

5 comments:

Cristian said...

"- Check for swear words"
WTF?! :-)

rails said...

this article is very good

peterbe said...

Looking at getting the last commit message as a parameter somehow into my post-commit script. Any idea how to do that?

peterbe said...

To answer my own question:

git log master | awk '/commit/ {id=$2} /\s+\w+/ {print $0}' | head -n 1

peterbe said...

Even better:

git log --pretty=oneline -n1

Popular Posts