Prevent direct commits to master

I did it again. I pushed my local changes directly into remote `master` branch, but it was the last time.

Git Hooks

In a nutshell git hooks allow to run custom scripts when some Git actions happen. There are two types of them, client-side which are triggered by local operations like `commit` or `merge`, while server-side ones run on network operations like receiving pushed commits. The scripts can be pure shell scripts, but there are no limitations, you can use Ruby or Python as well.

All hooks are stored in `hooks` subfolder of the Git directory (`.git/hook` by default) and to be enabled they need to be named correctly (without any extension) and be executable.

A list of all available hooks can be found [here](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks). The most interesting one for my particular problem is `pre-push`, which runs during `git push`, after the remote refs have been updated but before any objects have been transferred.

Building the script

A first thing that needs to be done to have the hook running is creating a file named `pre-push` and making it executable. Inside a directory with Git repository initialized inside it can be done by:

touch .git/hooks/pre-push && chmod +x .git/hooks/pre-push

After the file is created we can start writing the script, to make it easy and understandable it will be a shell script. The most important part is having an access to a name of the current branch. In Git v. 1.8 and later it is as easy as running `git symbolic-ref –short HEAD` command.

The only thing the script needs to do is comparing the branch’s name with `master` and prevent executing `push` command if they are equal.

#!/bin/bash

protected_branch='master'
current_branch=$(git symbolic-ref --short HEAD)

if [ $protected_branch = $current_branch ]
then
  echo 'Pushing directly to master is forbidden'
  exit 1 # prevent push
else
  exit 0 # push will execute
fi

Bypassing the hook

In general pushing directly to remote `master` branch is considered as a bad practice and usually causes problems. However sometimes you just need to do it. To accomplish it you can bypass the `pre-push` hook with `git push –no-verify`.

Extra tip: Git templates to avoid repeated work

Do you want to have `pre-push` or any other hook defined by default for all future Git local repositories? Where there’s a will, there’s a way.

When you initialize a new repository Git uses a template to create required files and directories. Assuming you have Git installed in `/usr/local`, the template is placed in `/usr/local/share/git-core/templates`. You can place the `pre-push` (without any extension) file in `hooks` subfolder and it will be automatically copied to all new repos.

If you have updated the template after a repository was initialized, running `git init` again will pick up the new template.

 

Igor Springer

I build web apps. From time to time I put my thoughts on paper. I hope that some of them will be valuable for you. To teach is to learn twice.

 

Leave a Reply

Your email address will not be published. Required fields are marked *