Git, like SVN and most other version control systems, allows for a very convenient usage of hooks. Hooks are, more or less, various scripts that you can use that will accompany the development process and will carry out certain all kinds of automated processes. In a team I work in, for example, we have Git hooks that carry out static code analysis to the entire system every time a developer runs a commit. If the developer deviates from the set standards, the commit will fail.
Each time the developer runs push, all of the unit testing is run. If there is a failure in one of the unit tests, the push doesn’t work. In this, we achieve a few goals. First, the programmer doesn’t need to remember to run a static code analysis or automatic checks or anything else related. Second is preventing unnecessary build failures.
There are a few ways to work with Git hooks; let’s look at the simplest way. You can make a random Git project using git init or go into an existing project. If you’re on Windows, make sure you can see hidden files. Go into the main project folder and you’ll see a mysterious folder name .git (notice the dot). This folder is pretty interesting and contains several sub-folders.
The folder we’re interested in for this article is, no surprise, called hooks and contains all of the hooks. Go into that folder and you’ll see there are lots of files with a .sample ending. Each one is a hook, by which I mean a specific action. For instance, if I see pre-commit.sample, then the name of the hook is pre-commit. You don’t need to be a genius to figure out that we’re talking about an action that occurs before a commit. If I see pre-push.sample, I can assume that we’re talking about a hook called pre-push that runs before a push.
What the GIT program does is go into the hooks folder and run each file in order. If there’s a pre-commit file, then everything inside of it will run before any user in the project runs commit.
Everything is clearer with an example, no? Let’s make a file called pre-commit (with no ending) or we can remove the .sample from the existing pre-commit file. If we peek inside, we’ll see that there is a script that checks if the user put in a file whose name has a foreign character. Here’s how that script looks:
#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat < <\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against --
This script is written in BASH. Don’t be too afraid of this code and don’t worry about analyzing it. There are many BASH scripts that do countless routine actions on the server and this script is no exception. What it does is check to see if there are any non-ASCII characters in the file name. If there are it will print out an error and stop the commit. In other words, it returns an error if a script in the hooks returns an error, and then the action is not carried out.
Once we put this script in the pre-commit file (without the ending!) every commit that’s run in that specific project will be run with the script. Here’s an example in which I added a file with a Hebrew name and tried to run commit:
Pretty nice, no? This particular script is found in pre-commit.sample, but there are many other scripts around the net.
And what happens if on a specific occasion I want to bypass the hook? No problem—just add the flag --no-verify:
git commit --no-verify
In the team I work on we don’t put our hooks directly in the project file—we use grunt in order to manage our git hooks. But we’ll cover this in a future article.
About the author: Ran Bar-Zik is an experienced web developer whose personal blog, Internet Israel, features articles and guides on Node.js, MongoDB, Git, SASS, jQuery, HTML 5, MySQL, and more. Translation of the original article by Aaron Raizen.