When using Git, like most of projects today do, code goes through 4 stages. Those stages are:
- Untracked
- Files/changes are visible, but those will not be committed.
- Staged
- Changes get into this state by executing git add command. Once changes are staged, with next commit those will be committed.
- Committed
- After executing git commit command, these changes are committed and will be sent to main repository with git push. If changes are done after commit, those have to be added again with git add command.
- Pushed
- By executing git push change are sent to remote server.
This all sounds very clear and simple but sometimes, just going from stage to stage is not enough and some other action might be needed. That can be anything like sending mail notification to rest of team that code is pushed, running tests before push or performing code analysis before each commit. This is where Git hooks come very useful.
What are git hooks?
Git hooks are scripts that Git executes before or after events such as commit, push or merge.
To simplify it, hooks are actions that get automatically triggered. These actions are bash scripts and once git project gets initialized, they can be found in .git/hooks folder. In this folder, there is already script for each event, and those can be used as example of a hook. Events, for which hooks can be defined are:
- applypatch-msg
- pre-applypatch
- post-applypatch
- pre-commit
- prepare-commit-msg
- commit-msg
- post-commit
- pre-rebase
- post-checkout
- post-merge
- pre-receive
- update
- post-receive
- post-update
- pre-auto-gc
- post-rewrite
- pre-push
This is a long list of different events, and most of them you will never need to use. Reason for that can be found in two categories hooks are divided into. Those categories are client-side and server-side hooks. Client-side are ones that are executed in your local machine, like pre-commit and post-commit, server-side are ones that are executed in central-machine like pre-receive.
There is sadly not a lot of materials around on git hooks but probably best one, and most complete is githooks.com. This site contains examples for all hooks and links to many other libraries and projects using them or helping to work with them.
While that all sounds great, there are some problems with hooks that need to be considered.
- Hooks are bash scripts, and most of developers today are not comfortable with writing bash.
- Hooks are located in .git/hooks folder, that means they are not cloneable and each developer would need to manually add them to hooks folder.
Husky
As a solution for these problems we can use Husky library. Husky is npm library that helps easily create and manage hooks easily.
Requirements:
Node >= v8.6.0
Git >= v2.13.2
Instalation:
Npm
npm install husky –save-dev
Yarn
yarn add husky
Adding first hook:
To use hooks with husky following steps need to be done:
- Create .huskyrc file in root of your project
- Inside of this file create object with property hooks that will contain all hooks
- Add your hooks under that object where key is event name and value is command to be executed.
Example:
// .huskyrc
{
"hooks": {
"pre-commit": "echo \"Hello, pre-commit hook!\""
}
}
In the example above, before commit, in command line we should see text “Hello, pre-commit hook!”. This is very simple example that won’t be much use. However, we could also run different things. We could run npm task, another bash script or some node script.
NPM task example:
“pre-commit”: “npm run test”
NodeJS example:
“pre-commit”: “node script.js”
Bash script:
“pre-commit”: “sh script.sh”
When these configurations are added, husky will create hooks for us. Also, since configuration file is added to project root, it can be tracked with and available to all team members. This is far simpler than writing bash scripts in hooks folder and sending them to each individual person.
Custom hooks
When writing custom hooks, return value of process is important. If your custom hook needs to prevent going to next stage, it needs to exit with any non-zero value. If it exits with value 0, stage will be treated as successful and it will proceed to next. Bellow is example of bash script that would prevent commit if it is in master branch, otherwise it will allow it.
#!/bin/sh
branch="$(git rev-parse - abbrev-ref HEAD)"
a branch name where you want to prevent git push. In this case, it's "master"
if [ "$branch" = "master" ]; then
echo "You can't commit directly to '"${branch}"' branch"
exit 1
fi
Problems with Husky
Husky also has few issues to take into concern.
One of them is adding it to existing project. There are situations when hooks wouldn’t be properly created when adding husky to existing npm project. Solution I found to work was to delete node_modules and yarn.lock and/or package.lock files and reinstalling all dependencies.
Second problem that could happen is duration. While hook add only few milliseconds, process that they trigger can last long time. Example would be running tests before each commit. Triggering it is short and trivial, but executing them can take a long time.
Conclusion
As you could see above, hooks can help with improving development process a lot. With husky on top this is even easier. There are few limitations to be aware of, however, possibilities are only limited by developer’s creativity.
Small demo project with few samples can be found in this github repository.
Top comments (2)
Thx for this! This is really what I wanted. Helped A LOT.
Can I translate in Korean this article? If you don't mind, I wanna share this awesome information in Korean. Surely, There will be a link directing to this original one.
Sorry saw it only now, yeah feel free :)