Get hooked !!
Photo by Alvaro Reyes on Unsplash
If you’ve ever worked on an open-source project, or you’re working in a team, it’s very likely you are using some kind of version control. Version Control System(VCS) has become one of the major requirement for any project, Git being the most popular one. However, as the team grows in number, it becomes difficult to handle the different code styles and to enforce certain rules across all the contributors. These issues aren’t noticed by a contributor until he has pushed his changes which result in overheads for the core maintenance team. To enforce these rules and validate the code being pushed, Git has a great feature, called Git Hooks.
These are custom scripts which are triggered on certain specific git events. Hooks allow defined scripts to run before, or after, certain Git events occur. There are two types of hooks :
Server Side Hooks, as the name suggests, are installed on the server and are triggered only in case of network operations. For example - Post-Receive is a type of server-side hook triggered after a successful push. This makes it a perfect place to send out the notifications to all the other contributors.
Client Side Hooks reside on one’s local repository and are executed when a git event is triggered. Here, a git event can be commit, push, rebase etc. When we run certain git commands, git look for the hooks within the git repository to see if there is an associated script to run. For example, one could have a pre-push hook to validate that the code enforces certain rules before it’s pushed to the remote repository. Since we can’t do much in the case of server-side hooks, we will talk about client-side hooks only for the rest of the article.
In this section, we will take a deeper look at hooks and see one of the hooks in action. We will execute a simple hook to validate that the code being pushed doesn’t have any commit with ‘WIP’ in the commit message. (WIP — Work in Progress). Let’s Begin!!
First, let’s create a new directory.
Create a new folder — ‘hooks’
Now, initialize a git repository in the same folder with git init
. We can see, initializing a repo creates a hidden folder .git
. The hidden files and folders can be viewed using -a
flag. -l
flag shows the basic details of the files. So, combining both the flags, we can have ls -la
to view the details of all the files present in a directory. Since we will be working a lot with hidden files, we are going to use ls -la
throughout the article.
git init
But, what’s inside the mysterious.git folder
? Let’s have a look. Simply put, it contains all the metadata related to your git repository including commits, remote repository address etc. It also contains a log of all the commits. If you look closely, you can see a hooks
folder. This is where all the magic happens!
Content of .git folder
So, let’s have a look at the hooks
folder. It has a sample script for all kinds of hooks. Since these files end with a .sample
extension, these are not executed on the specified git events. For example, pre-commit.sample
file would be executed prior to any commit if we rename it to pre-commit
. And that’s exactly what we are gonna do 😂 !
Hooks folder
First, let’s understand one of the existing hook, pre-push.sample
.
If it seems like too much, you can have a look at the comments at the top of the file. Basically, it looks for all the latest commits in your local repo and checks if the new commit message has the string ‘WIP’ in it. To find the commits, it takes into account the remote repository hash and the current local repository hash, and looks for all the commits between the two. If it finds the string, it prints the Found WIP commit in...
and aborts the push. Note that if the script ends with a non-zero exit code, it won’t push to the remote repo.
Let’s test this quickly. Till now, we have initialized our git repo locally but it needs a remote repo to push. So, create a new repository on GitHub (or BitBucket) and copy the remote git url. I created one named hooks-demo
.
Now, we need to add this remote reference to our local git repository using git remote add origin "url"
.
Adding reference
Now, Let’s create a new file index.html
with some text. Commit the file and then push it, it should get pushed with no issues.
Push successful
Now, let’s apply the magic and make the hooks executable. Rename the pre-push.sample file : mv .git/hooks/pre-push.sample .git/hooks/pre-push
. Just make sure the file is executable. In case, it’s not, chmod +x filename
would help.
Renamed the file
With all these done, change the content of the file and do a new commit with ‘WIP’ word in it.
pre-push hook in action
ta-da !! 😍 The push failed. Since we removed the extension of the file, the file was executed and hence the push didn’t go through.
Similarly, based on your requirement, you can write your own hooks to enforce certain rules. In the example above, we checked for a particular string in the commit message but what if, we want to ensure that a particular string is not present in the committed code. Well, we can use pre-commit hook for that.
The above pre-commit hook will look for a ‘TODO’ string in your commit. If it finds one, the commit will be rejected.
Since these hooks are installed on local machines, they can always be bypassed.
git commit --no-verify
Using the flag
_--no-verify_
won’t trigger hooks.
We have used bash script for the above examples. However, it supports any scripting language that your system can execute.
Let’ say, You’ve written a ‘pre-push’ hook which don’t allow anyone to push the code if it has ‘TODO’ word in it. It’s cool and it’s working great for you. Now, how are you gonna distribute this script to others working on the same project? As I mentioned, hooks are present in .git folder along with all the other git metadata. Since the data can be user-specific, .git folder is not versioned, meaning the contents of this folder is never pushed to the remote repository.
The simplest way is to store the hooks in a normal folder in the repo and copy them over to .git/hooks on each user’s machine. However, we can’t rely on users to manually copy the scripts to the hooks folder. We need some scripts or tools that can install the hooks without any user intervention. Let’s assume the hooks are present in git-hooks.
If you’re using Git version 2.9 or greater, you can simply run the below command to change the hooks folder to git-hooks instead of .git/hooks.
git config core.hooksPath git-hooks
You can also make above command to execute automatically by placing it in a setup file i.e Makefile, pom.xml , package.json etc. Husky is one of the best options for installing git hooks in node project. However, if you are using an earlier version of git, you need to create a symbolic link by executing -
ln -s -f ../../git-hooks/pre-push .git/hooks/pre-push
Git Hooks are super cool and a great way to improve a team’s development workflow. The examples are just to show what the hooks are capable of. You can use these to check the lint issues or run unit tests before pushing the code or practically anything, you can think of. I am sure you guys can find a ton of use-cases to leverage the power of git hooks, if not using already.
Hope you have learned something new !!
Please clap 👏 👏 if you found this useful !!
Thanks for reading.