What are Git-hooks & How to use them?

What are Git-hooks & How to use them?

Git hooks are scripts that Git automatically executes before or after events like commit, push, or receive. But why would anyone want to run scripts on git events? Well, let's see a few use-cases. Suppose you are working in a team and want to enforce a certain commit policy, let's say you want that all commit messages be a minimum of 50 characters. Or you want to lint the code before it gets committed. Or let's say each time a developer tries to push the code, you want to run the test cases. Now all of these are perfect use-case for using git-hook. Therefore git-hook can be considered a developer workflow tool for productivity and enforcing certain best practices.

Git-hooks are by default present in .git/hooks directory of the repo, if you list the files there, you will find a bunch of files with .sample extension. These files are git-hooks, however, since they have .sample extension, and they are not active. Hooks can be classified into 2 categories.

  1. Client-Side Hooks: These are prompted by events on the local repository, such as when a developer commits, merges code, or pushes code.
  2. Server-Side Hooks: These hooks reside on the network hosting the repository, and they are prompted by events such as receiving pushes.
git:(master) ✗ ls .git/hooks
applypatch-msg.sample     pre-applypatch.sample     pre-rebase.sample
commit-msg.sample         pre-commit.sample         pre-receive.sample
fsmonitor-watchman.sample pre-merge-commit.sample   prepare-commit-msg.sample
post-update.sample        pre-push.sample           update.sample
All hook have .sample extension by default

In order to activate or install a git-hook, we have to remove the .sample extension and make the file executable by sudo chmod +x pre-commit. Please note that the script can be written in any language as long as it is executable.

Creating a custom Githook:

Let's create a simple githook that simply checks the length of the commit message and fails or passes the commit.  

The logic for the hook is really simple, read the commit message from the commit file, and check, if the length of the commit message is less than 5 words. In case of an invalid commit, simply exit the program with exit code 1. Exit code is how git knows, that it has to fail/pass the commit. Let's see what happens when we try to commit our code with less than 5 words.

Problems with vanilla githook:

Well, the major problem is since the activated githook is on your local machine, and since the file is present in .git/hook, the githook is not git trackable. So although you are getting benefits of githooks locally, it really does not help in a team setting. One solution would be to create a symlink with a committed file.

Another problem would be to write all the logic yourself for common use-cases, like removing whitespace, checking for large files, etc. Also, these checks should be present across all repos and without any frameworks, it's just a repetition of work.

Githooks Frameworks:

Githook frameworks are an abstraction on top of githooks, and they have simple APIs for developers to use githooks. When using these, devs don't have to deal with the intricacies of using vanilla hooks. Few of the popular frameworks are pre-commit, husky.

In the below example, have configured a pre-commit-config.yaml which will do the basic clean-up on each commit.

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v3.2.0
  hooks:
    - id: detect-aws-credentials
    - id: detect-private-key
    - id: end-of-file-fixer
    - id: trailing-whitespace
pre-commit-config.yaml

For the husky example, we have hooks configured at different stages:

  • On pre-commit stage, we are doing some linting and checking if the branch follows certain naming conventions or not.
  • On commit-msg stage, we are checking if the commit message follows the convention or not.
  • On pre-push stage, we are running all the test cases, this ensures that nothing should be pushed which breaks existing test cases.
{
  "hooks": {
    "pre-commit": "npm run check-branch-name && lint-staged",
    "commit-msg": "npm run check-commit-msg -E $HUSKY_GIT_PARAMS",
    "pre-push": "npm run test",
  }
}
.huskyrc

Conclusion:

Githooks are executable scripts that run on certain git events. There are client-side and server-side hooks. Here are a few points to remember when using githooks.

  • All repos have githooks already in them, however, they are deactivated by default. In order to activate/install a githook, we have to remove the .sample extension and write our logic.
  • Githooks can be written in any scriptable language. The example hook I wrote is in python3. Also, anything can be written as a hook, you can order yourself a coffee from Starbucks 😅 each time you commit/push code.
  • Using vanilla githooks can be cumbersome, there are frameworks for using githooks, like pre-commit, husky, etc. Using these solves the distribution problem and also abstracts the nitty-gritty of using vanilla githook.

Git - githooks Documentation
Git Hooks | Atlassian Git Tutorial
Git hooks: scripts that run automatically when an event occurs in a repo. Trigger customizable actions at key points in development life cycle.
Shebang (Unix) - Wikipedia