Showing posts with label git hooks. Show all posts
Showing posts with label git hooks. Show all posts

Wednesday, 15 June 2022

Git Hooks Simplified

 

ABCs of All Hooks

Today’s top companies give the highest priority to the quality of the code you have written. So, we have a concept of git hook which does not allow you to commit in “master branch” unless your code validation is accepted. I think it is helpful to maintain a better quality of code.

If you’ve ever worked on an open-source project, or you’re working with a team, it’s very likely you’re using some kind of version control. Version Control System (VCS) has become one of the main requirements for any project, Git being the foremost 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 end in overheads for the core maintenance team. To enforce these rules and validate the code being pushed, Git provides a great feature, called Git Hooks.

So what are Git hooks?

Git hooks are custom scripts that git executes before or after events such as commitpush. Git hooks are a built-in feature there is no need to download anything. They are unique and run locally and resides inside the .git/hooks directory.

Git hooks can be used to:

  • Check commits for errors before they are pushed.
  • Ensure code meets project standards.
  • Notify team members about changes.
  • Push code into a production environment, and more.

There are two types of hooks:

  • Client-Side (Local) Hooks
  • Server-Side (Remote) Hooks

Server-Side Hooks, as the name suggests, are installed on the server and are triggered only just in case of network operations. For example — Post-Receive may be a sort of server-side hook triggered after a successful push. This makes it an ideal place to send notifications to all the contributors.

e.g: pre-receive, update, post-receive

  1. The pre-receive hook is executed every time somebody uses git push to push commits to the repository.
  2. The post-receive hook gets called after a successful push operation.

Client-Side Hooks reside on one’s local repository and are executed when a git event is triggered. Here, a git event can commit, push, rebase, etc. When we run certain git commands, git search for the hooks within the git repository to ascertain if there’s 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.

e.g: pre-commit, prepare-commit-msg, commit-msg, post-commit

  1. The pre-commit script is executed every time you run git commit command.
  2. The prepare-commit-msg hook is called after the pre-commit hook to populate the text editor with a commit message. This is where auto-generated commit message is created.
  3. The commit-msg hook is much like the prepare-commit-msg hook, but it is called after the user enters a commit message.
  4. The post-commit hook is called immediately after the commit-msg hook. This is after a commit has taken place.

In this image, we have different hooks name with their event (means which command executes which hook).

Implementing Git Hooks

Git hooks are a built-in feature that comes with every git repository. When initializing a new project git populates the hooks folder with template files.

  1. Navigate to the hooks directory
$ ls .git/hooks/

Notice the files inside, namely:

sample files inside the hooks directory

2. Install your hook

To enable the hook scripts, simply remove the .sample extension from the file name. Git will automatically execute the scripts based on the naming. For my purpose, I renamed the “commit-msg.sample” file to “commit-msg”.

3. Select a language to write your hook script.

The default script files are written in shell scripts, but you can use any scripting language you are familiar with as long as it can be run an executable. This includes BashPythonRubyPerlRustSwift, and Go.

Open up the script file in your code editor and define your language of choice in the first line, using the shebang (#!) sign, so git knows how to interpret the subsequent scripts. Note that you need to include the path of your interpreter. For Mac users who wish to write the scripts in Python, for instance, the Apple-provided build of Python is located in /usr/bin. So, the first line would look like:

#!/usr/bin python

If you want to use Bash, on the other hand, the first line would be:

#!/bin/bash

And for shell:

#!/bin/sh

4. Write your script

From here on, you could write any script and Git will execute it before any commits to this project. For reference, I wrote my script in Bash, and here is what I ended with up.

  • commit-msg hook

#!/bin/bash
Color_Off='\033[0m'
BRed="\033[1;31m" # Red
BGreen="\033[1;32m" # Green
BYellow="\033[1;33m" # Yellow
BBlue="\033[1;34m" # Blue
MSG_FILE=$1
FILE_CONTENT="$(cat $MSG_FILE)"
# Initialize constants here
export REGEX='(Add: |Created: |Fix: |Update: |Rework: )'
export ERROR_MSG="Commit message format must match regex \"${REGEX}\""
if [[ $FILE_CONTENT =~ $REGEX ]]; then
printf "${BGreen}Good commit!${Color_Off}"
else
printf "${BRed}Bad commit ${BBlue}\"$FILE_CONTENT\"\n"
printf "${BYellow}$ERROR_MSG\n"
printf "commit-msg hook failed (add --no-verify to bypass)\n"
exit 1
fi
exit 0

Let’s understand this script:
  • Line 3–7 define text-decoration variables, which are used to enhance my console message. For coloring the variables, I have used Git shell coloring.
  • Line 9–10 gets the commit message form the console.
  • Line 12–13 defines the REGEX pattern from which the commit message will be validated.
  • Line 14–21 is a conditional statement that will check the commit message if it matches with REGEX pattern then successful commit otherwise failed.
  • Notice that if the conditional above evaluates to a 1 status, line 19 extends that by exiting with a 1 status to indicate failure. This prevents changes from being commit.

We have to give executable permission to our script file using the chmod command.

$ chmod +x .git/hooks/commit-msg

After writing your hook scripts, just sit back and watch Git do all the work:

Let’s see the example below:

  1. Commit with an improper commit message
Bad commit

2. Commit with a proper commit message.

Good commit

3. If you want to bypass the commit-msg hook then use --no-verify flag.

Bypass commit
  • pre-push hook

#!/bin/sh
echo "Skip pre-push hooks with --no-verify (not recommended).\n"
BRANCH=`git rev-parse --abbrev-ref HEAD`
if [ "$BRANCH" = "master" ];
then
echo "You are on branch $BRANCH. You must not push to master\n"
exit 1
fi
if [ `date +%w` -ge 5 ] && [ "$BRANCH" = "develop" ];
then
echo "Please, do not push code to develop before the weekend!\n"
exit 1
fi
Let’s understand this script:
  • Line 5 determines the current branch with git rev-parse --abbrev-ref HEAD.
  • Line 6–10 is a conditional statement, will check in which branch you are in. If you’re in the master branch then the script will not allow you to push code in the master branch. If not in master, then you can push.
  • Line 12–16 is also a conditional statement, will check your current branch and also check the date and week. If a week is greater than means you i.e Saturday and Sunday then the script will not allow you to push the code.

This pre-push script has some advantages as well because in the IT industry who wants to work at the weekend, no one really? So, this hook prevents you to push code on a Friday night because it’s having a high chance to get an error when you pushed the code. So, it prevents that’s awesome!

  1. Error while pushing to the master branch.

2. Successfully pushed to another branch on weekdays except for weekends.

  • pre-commit hook

#!/bin/bash
# Git Shell Coloring
RESTORE='\033[0m' # Text Reset means no color change
RED='\033[00;31m' # Red color code
YELLOW='\033[00;33m' # yellow color code
BLUE='\033[00;34m' # blue color code
FORBIDDEN=( 'TODO:' 'console.log' )
FOUND=''
for j in "${FORBIDDEN[@]}"
do
for i in `git diff --cached --name-only`
do
# the trick is here...use `git show :file` to output what is staged
# test it against each of the FORBIDDEN strings ($j)
if echo `git show :$i` | grep -q "$j"; then
FOUND+="${BLUE}$i ${RED}contains ${RESTORE}\"$j\"${RESTORE}\n"
fi
done
done
# if FOUND is not empty, REJECT the COMMIT
# PRINT the results (colorful-like)
if [[ ! -z $FOUND ]]; then
printf "${YELLOW}COMMIT REJECTED\n"
printf "$FOUND"
exit 1
fi
# nothing found? let the commit happen
exit 0
While developing any software, at some point the developers have to debug the code to check the bugs or API response. If you are from the JavaScript background then developers mostly use “console.log()” statement to print something in console. Consider a scenario, where you have debugged the code and forget to remove those console.log()” statements and you’re ready to commit the changes. But, you don’t want those statements to commit. So, in that case, we can take the help of the pre-commit hook in which we have written a script will automatically check the code before commit. If the scripts found any “console.log()” statements then it will not allow you to commit. That’s amazing, right?                                                                                                               

Let’s understand the code:                                                                                      The script uses the grep command to search for the forbidden strings like “TODO:” and “console.log()” in the staged codebase. If strings are found then abort the commit.                                                           Commit Rejected                                                                                                                                                                             Now, If we remove those statements commit will be accepted.                     
Commit Accepted

Be aware of the --no-verify option to git commit. This bypasses the pre-commit hook when committing.

Prevents bad commit or push (git hooks, pre-commit/pre-commit, pre-push/pre-push, post-merge/post-merge, and all that stuff…)

How to upgrade Maven

  java.lang.IllegalStateException I had installed maven in my ubuntu using command  apt install maven This installed maven in path /usr/shar...