# Git Hooks

## Summary


Hooks in Git are **executable scripts** that are triggered when certain events happen in Git. It's a way to customize Git's internal behaviour, automate tasks, enforce policies, and bring consistency across the team. 

For example, hooks can check that passwords or access tokens are not committed, validate that commit messages conform to an agreed format, prevent unauthorized developers from pushing to a branch, update local directories after a checkout, and so on.

Git supports both client-side and server-side hooks. Hooks can be written in any programming language though it's common to use Bash, Perl, Python or Ruby.

## Discussion

### What use cases can benefit from Git Hooks?

The figure illustrates local add/commit followed by a push to the remote repository. Also shown are the relevant hooks. Each hook is given a specific name. Hooks are invoked **by naming convention** before or after specific events.

The `pre-commit` hook is called when the commit procedure begins. This hook can format the code against the project's styling guide, run linting or execute unit tests. By checking for missing semicolons, trailing whitespace, and debug statements code reviews can focus on more important issues.  Hooks `prepare-commit-msg` and `commit-msg` can be used to format/validate the commit message. 

After a successful commit, the hook `post-commit` could be used to send notifications. More commonly, when collaboration is via a remote repository, notifications are sent from the `post-receive` hook that's triggered after all references are updated. This hook can also deploy the latest changes to production.  The `update` hook that runs once per branch can be used to enforce access control, that is, only authorized users can push to a branch. 


### What are client-side and server-side hooks?

Client-side hooks run on developer machines whereas server-side hooks run on the server that hosts the repository. Committing, merging, rebasing, applying patches, and checkout are common Git operations that developers do on their development machines. Any of these commands can trigger their relevant client-side hooks. Server-side hooks are triggered by the `git-receive-pack` command, which is a result of developers pushing changes to the remote repository on a cloud-hosted Git server. 

Developers have full control of client-side hooks. Developers can choose to disable all client-side hooks if they wish. If a project's best practices need to be strictly enforced, server-side hooks are the way to do it. 

Some Git repository providers impose restrictions on the use of server-side hooks on their cloud-based servers. For example, GitHub's free tier doesn't allow developers to define server-side hooks though GitHub Enterprise Server supports them. However, there are alternatives to achieve server-side automation such as webhooks, GitHub Apps/Actions, GitLab CI/CD, GitLab push rules, etc. 


### What are pre- and post- Git Hooks?

Client-side and server-side hooks can be either `pre-` and `post-` hooks. The `pre-` hooks can be used to validate stuff before allowing the requested operation. For example, `pre-commit` hook executes at the start of a commit workflow. If the commit is deemed invalid for any reason, the hook can return a non-zero value. This terminates the commit operation. By returning zero, the hook states that the commit operation can continue, including the execution of other relevant hooks. 

The `post-` hooks execute after the requested operation is done. Such a hook's return value doesn't matter since the operation is already complete.  For example, `post-commit` executes after a successful commit. These hooks are typically used to send emails or other notifications. They may also be used to trigger CI/CD pipelines. For example, `post-receive` could be used to deploy the updated code to production. 


### What options help skip the execution of hooks?

It's possible to skip some hooks if their associated commands are called with certain options. Hooks `pre-commit`, `pre-merge-commit`, `prepare-commit-msg`, and `commit-msg` can be skipped if `commit` and `merge` commands are invoked with `--no-verify` option. 

The hook `post-checkout` can be skipped if `clone` and `worktree add` commands are called with `--no-checkout` option. However, this hook is always called for `checkout` and `switch` commands. 


### What essentials should a developer know to use Git Hooks?

By default, hooks are stored in `.git/hooks` within the project's repository. Git populates this folder with sample scripts that developers can use as a starting point. However, the file suffix `.sample` must be removed and scripts must be made executable. Developers can rewrite the scripts in their preferred language. Accordingly, the shebang (`#!` first line in script) must be updated with the correct path to the language. 

If these sample scripts are deleted by mistake, we can retrieve them from Git templates folder. This may be `/usr/local/git-core/templates/hooks` (Linux) or `C:\Program Files\Git\mingw64\share\git-core\templates\hooks` (Windows). In fact, this copying from templates folder to the project's `.git/` folder is what happens when we run `git init`.  

While hooks are triggered in response to specific events, we can also manually trigger the execution of a hook. The command for this is `git hook run <hook-name> <hook-args>`. For example, we could trigger one hook from another hook. This command is a low-level internal helper command. 

Beginners can start by reading the official documentation on Git Hooks and Chapter 14 of Loeliger's book on Git. 


### What does it take to create and maintain reusable Git Hooks?

It's a good practice to commit hooks as part of the repository so that all developers reuse and execute the same hooks. However, it's not permitted to store them within a `.git/hooks` folder within the repository. One approach is store them in another folder, say `hooks`, and then create a soft link from `.git/hooks` to `hooks` folder after cloning the repository. Another approach is to configure the path to the hooks with the command `git config core.hooksPath <path>`. 

To reuse across projects, hooks can be in a separate repository and cloned locally. Configure the hooks path globally to that folder: `git config --global core.hooksPath <path>`. This configuration variable has been available since Git version 2.9.0. 

There's also a framework named pre-commit to manage pre-commit hooks. This framework includes dozens of tested pre-commit hooks written in Python that developers can reuse in their own projects. These include syntax checking (`check-json`, `check-yaml`), fixing problems (`fix-byte-order-marker`, `trailing-whitespace`), protecting secrets (`detect-private-key`, `detect-aws-credentials`), enforcing policies (`no-commit-to-branch`, `forbid-new-submodules`), and more. 

The githooks.com lists many useful hooks and related tools that developers can use.


### How do I configure my project environment to use Git Hooks?

No special configuration is needed other than updating `.git/hooks` folder or configuring `core.hooksPath` variable. However, many developer tools attempt to simplify this further.

In Maven, a plugin can setup the hooks path. In Gradle, we can have a dependent build script to do this setup. In Node.js, Husky project allows us to wire up all the hooks in the `package.json` file. In PHP, hooks can be set up in `composer.json` file via the `post-install-cmd` configuration. 

In different languages, frameworks are available to manage hooks: pre-commit (Python), Overcommit (Ruby), Lefthook (Ruby or Node.js). 


### What are some criticisms of Git Hooks?

Hooks change Git's default behaviour. If a hook does something unusual, it may confuse developers new to the project. Moreover, hooks can be buggy and make things counterproductive. For this reason, some recommend using Git aliases and invoking scripts directly rather than relying on hooks. 

Git doesn't provide any easy mechanism to share hooks among developers or from one repository to another. No doubt this is more secure but at the expense of convenience and reusability. Hooks problem with installation, maintainability, and reusability is to some extent solved by frameworks such as *pre-commit*. 

Hooks are powerful. Developers may be tempted to run unit tests before each commit. This can slow things quite a bit and discourage developers from committing often. This can lead to data loss. 

Server-side hooks are not universally enabled by cloud-hosted Git repository service providers. 

## Milestones

2005

Linus Torvalds creates Git as a replacement to BitKeeper that's a distributed version control system. Even in Git v1.0.0 (December), **hooks** are included as a feature. 

Mar  
2007

Hook `post-receive` is introduced to replace `post-update`. The latter is expected to be deprecated from v1.6.0. The hook `post-receive` is more useful since it has access to both old and new references; `post-update` knows only the new references. 

Sep  
2007

Git 1.5.4 is released. In this release, `git merge` command calls the `post-merge` hook following a successful merge.  

Feb  
2008

Git 1.6.0 is released. This release ships sample hook scripts with the **suffix** `.sample` since on some filesystems it's not sufficient to ship them as non-executables. In Windows, all scripts are executable by default. Developers can rename the scripts if they wish to enable the hooks. 

Dec  
2008

Git 1.6.1 is released. In this release, `git rebase` command when used without the `--no-verify` option calls the `pre-rebase` hook. This hook can be used to prevent the rebasing of a branch. 

May  
2009

Git 1.6.3 is released.  In this release, `git clone` command when used without the `--no-checkout` option calls the `post-checkout` hook. This hook is normally called after an update to the worktree, such as with the `git checkout` command. 

Dec  
2014

A **critical vulnerability** is discovered pertaining to Git hooks. Usually it's not possible to create a top-level folder named `.git` in a repository. However, in case-sensitive filesystems it's possible to create a folder named `.GIT`, `.Git`, etc. In Windows, which is case-insensitive, such a folder overwrites the default `.git` folder. By including malicious hooks, a hacker could execute arbitrary code on the client's machine. This threat is greatest for developers using third party or untrusted repositories. This vulnerability is patched within a week. 

Apr  
2015

Git 2.4.0 is released. This release adds a new server-side hook named `push-to-checkout`. By default, pushes to checked out branch would fail. With this hook, we can customize the behaviour by merging with or overwriting server-side changes. 

Apr  
2016

**GitHub Enterprise Server 2.6** is released. This includes the `pre-receive` server-side hook that can be used to enforce push policies and optimize workflows. However, GitHub has included server-side hooks since 2013, it's first hook tasked with warning users when a repository is renamed. 

Jun  
2016

Git 2.9.0 is released. The hooks directory can now be configured via the `core.hooksPath` configuration variable. 

Nov  
2019

Git 2.24.0 is released. This release adds a new hook named `pre-merge-commit`. This hook is called after a successful merge but before obtaining the commit message. It can be bypassed with `--no-verify` option to the merge command. 

Feb  
2022

On GitHub, it's noticed that pushes to repositories can take as long as 880ms on average. The implementation is in Ruby. A lot of time is spent loading Ruby dependencies. By rewriting the hook as a Go service, average processing time is brought down to 10ms. This improvement also becomes part of GitHub Enterprise Server 3.4.