Scale your Git workflow with Git hooks – 2

In our previous post, we discussed what are git hooks, how to install git hooks ,few of the local git hooks and custom hooks. This post is continuation of the same and we are going to discuss more types of git hooks and their customization.

Post-Checkout git hook

The post-checkout hook works a lot like the post-commit hook, but it is called whenever you successfully check out a reference with git checkout.

This hook is also run after git clone command as it is usually followed by checking out master branch in the repository, unless the ––no–checkout (–n) option is used. This hook can be used to perform repository validity checks, auto-display differences from the previous HEAD if different, or set working dir metadata properties.

This hook accepts three parameters, and its exit status has no affect on the git checkout command:

  1. The ref of the previous HEAD
  2. The ref of the new HEAD
  3. A flag telling you if it was a branch checkout or a file checkout. The flag will be 1 and 0, respectively.

This can also be used to perform certain actions like clearing specific types of files. For example, one of the problems with Ansible development is that there are .retry files created for every failed run of ansible playbooks. This hook can be used to clear out .retry files when switching to new branch. For this, we can use below python code to clear .retry files:

#!/usr/bin/env python
import sys, os, re
from subprocess import check_output
# Collect the parameters
previous_head = sys.argv[1]
new_head = sys.argv[2]
is_branch_checkout = sys.argv[3]
if is_branch_checkout == "0":
print "post-checkout: This is a file checkout. Nothing to do."
print "post-checkout: Deleting all '.retry' files in working directory"
for root, dirs, files in os.walk('.'):
for filename in files:
ext = os.path.splitext(filename)[1]
if ext == '.retry':
os.unlink(os.path.join(root, filename))

The current working directory for hook scripts is always set to the root of the repository, so the os.walk(‘.’) call iterates through every file in the repository. Then, we check its extension and delete it if it’s a .retry file.

Pre-Rebase git hook

This hook is called by git-rebase and can be used to prevent a branch from getting rebased. The hook may be called with one or two parameters:

  1. The upstream branch that the series was forked from
  2. The branch being rebased.

The second parameter is empty when rebasing the current branch. To abort the rebase, exit with a non-zero status.

For example, if you want to completely disallow rebasing in your repository, you could use the following pre-rebase script:

# Disallow all rebasing
echo "pre-rebase: Rebasing is dangerous, follow the golden rules of rebasing."
exit 1

view raw

hosted with ❤ by GitHub

More good idea would be to allow rebasing in the current branch but not on different branch. For this, below script can be used:

# disallows rebasing on different branch only
if [ ! -z $2 ]; then
echo "pre-rebase: Rebasing is dangerous, follow the golden rules of rebasing."
exit 1
echo "pre-rebase: Be careful of what you do"

view raw

hosted with ❤ by GitHub

For a more in-depth example, one can look at the included pre-rebase.sample script.

Server Side Git Hooks

As discussed in the previous post, server-side hooks are just like the local ones, except they reside in the server-side repository. This generally refers to the repository in popular git repo providers like Azure DevOps, BitBucket, GitHub, etc. Since these scripts reside at the server-side, they can be used for overall source code organization, maintenance and enforcement of certain standards at the organization level. They can also be developed and managed by one central team bringing more efficiency and consistency.

Some of the popular server side hooks are below:

  1. pre-receive
  2. update
  3. post-receive

All of these hooks let you control the different stages of the git push process.

The output from server-side hooks are piped to the client’s console, so it’s very easy to send messages back to the developer. But, you should also keep in mind that these scripts don’t return control of the terminal until they finish executing, so you should be careful about performing long-running operations.

Pre-Receive git hook

The pre-receive hook is executed every time somebody uses git push to push commits to the repository. It should always reside in the remote repository that is the destination of the push, not in the originating repository.

This hook runs before any references are updated, so it is a good place to enforce any kind of development policy that you want. For example, this can be used to control the format of the commit message and decide whether to update/reject/accept incoming push. Most of the organizations uses this to verify like presence of an open issue or backlog item or feature item in the commit message.

The script takes no parameters, but each ref that is being pushed is passed to the script on a separate line on standard input in the following format:

You can see how this hook works by using a very basic pre-receive script that simply reads in the pushed refs and prints them out:

#!/usr/bin/env python
import sys
import fileinput
# Read in each ref that the user is trying to update
for line in fileinput.input():
print "pre-receive: Trying to push ref: %s" % line

view raw

hosted with ❤ by GitHub

One can use incoming SHA1 hashes, along with some lower-level git commands to interpret the changes that are going to be introduced.

Again, exiting with a non-zero status causes all of the incoming refs to be rejected. If the hook exits with zero, updating of individual refs can still be prevented by the update hook. If you want to accept or reject branches on a case-by-case basis, you need to use the update hook instead.

Update git hook

The update hook is called after pre-receive hook, and it works much the same way. It is called separately for each ref that was pushed. That means if the user tries to push 3 branches, update is executed 3 times. Unlike pre-receive, this hook does not need to read from standard input. Instead, it accepts the following 3 arguments:

  1. The name of the ref being updated
  2. The old object name stored in the ref
  3. The new object name stored in the ref

This is the same information passed to pre-receive, but since update is invoked separately for each ref, you can reject some refs while allowing others.

A zero exit from the update hook allows the ref to be updated. Exiting with a non-zero status prevents git receive-pack from updating that ref.

This hook can be used to prevent forced update on certain refs by making sure that the object name is a commit object that is a descendant of the commit object named by the old object name. That is, to enforce a “fast-forward only” policy.

Post-Receive git hook

The post-receive hook gets called after a successful push operation, making it a good place to perform notifications. For many workflows, this is a better place to trigger notifications than post-commit because the changes are available on a public server instead of residing only on the user’s local machine. Emailing other developers and triggering a continuous integration system are common use cases for post-receive.

The script takes no parameters, but is sent the same information as pre-receive via standard input.

This hook does not affect the outcome of git receive-pack, as it is called after the real work is done.

We have discussed many types of git hooks, and there are many other available. As listed in the previous post, out of 17 available git hooks, we discussed only 9. You can check documentation for other hooks here.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s