Git
Git is a widely used version control system that is commonly used in modern software development. It is a software tool that helps developers to track changes in their code base over time, enabling them to collaborate with other developers on the same project more efficiently.
In Git, developers create a “repository” that stores all the source code files and related assets for their project. As developers work on their code, they can make changes to the repository, which Git tracks and records in a log of “commits.” Each commit includes a message that describes the changes made to the code.
Git hooks
Git hooks are scripts that run automatically every time a particular event occurs in a Git repository. They let you customize Git’s internal behavior and trigger customized actions at key points in the development life cycle. These events could include committing changes to the repository, pushing changes to a remote repository, or merging changes from a branch into another branch.
How git hooks work
Git determines which hooks to run based on their names and the events that trigger them. The names of the hooks correspond to specific Git events, such as committing changes or pushing changes to a remote repository. When a Git event occurs, Git will automatically run any hooks with the corresponding name in the .git/hooks directory.
Implementing git hooks
Git hooks are a built-in feature that come with every Git repository. When you initialize a new repository with git init
, Git populates the hooks folder with template files. Here are the steps to enable them:
Navigate to the hooks directory
we need to change our directory to the following:
$ cd /repository/.git/hooks
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. To do so, we can use the following command:
mv hookname.sample hookname
After that, you have to provide the execute permission for the hook, using the following command:
chmod +x hookname
Select a language to write your hook scripts
The default files are written as shell scripts, but you can use any scripting language you are familiar with as long as it can be run as an executable. This includes Bash, Python, Ruby, Perl, Rust, Swift, and Go. In order to write a script first, you need to specify that in the first line of the script.
The first line of the script will be:
Bash : #!/bin/bash
Shell : #!/bin/sh
Python : #!/usr/bin python
Write your script
Each hook will contain a predefined script. You can modify this script as necessary, or you can write your own script from scratch and git will execute it before performing any actions or events.
Client-side hooks
Client-side or local hooks are scripts that are executed on the local machine when certain Git events occur, such as a commit or a push. These hooks can be used to perform checks or enforce policies on the code being committed or pushed.
Here are some of the most commonly used client-side hooks:
pre-commit
The pre-commit hook is a client-side Git hook that is executed before a commit is made. It can be used to enforce code formatting, check for syntax errors, or run tests.
Here is an example of a pre-commit hook that checks for console statements in the code:
#!/bin/sh
# Check for console statements in the code being committed
if grep -q -E 'console\.' $(git diff --cached --name-only | grep '.js$'); then
echo "Console statements detected in code. Please remove any console statements before committing."
exit 1
fi
exit 0
This script when run will print the message on the console in case the commit is aborted. In this case, git aborted the commit operation due to the exit code 1, which is a non-zero exit status.
Here is the output when any commit operation is done on the repository:
prepare-commit-msg
The prepare-commit-msg hook is executed after the commit message is created but before the commit is actually created. It can be used to modify the commit message or add additional information to it. It is useful for adding Ticket-ID, Branch name, Style Checklist, and Rules for commits.
Here is an example for prepare-commit-msg hook which adds branch name to the commit:
#!/bin/sh
# Get the name of the current branch
BRANCH_NAME=$(git symbolic-ref --short HEAD)
# Read the commit message from the file
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat $COMMIT_MSG_FILE)
# Prepend the branch name to the commit message
echo "[Branch: $BRANCH_NAME] $COMMIT_MSG" > $COMMIT_MSG_FILE
This script uses the git symbolic-ref
command to get the name of the current branch and reads the commit message from the file specified as the first argument to the hook. This can be useful for providing additional context when looking at the commit history in complex projects with many branches.
Here is the output when any commit operation is done on the repository:
commit-msg
The commit-msg hook is executed after the user has entered a commit message but before the commit is finalized. The purpose of the commit-msg hook is to allow you to perform checks on the commit message and to ensure that it meets certain standards or guidelines.
Here is an example for commit-msg hook that checks for the presence of a valid ticket ID (like Jira ticket ID) in the commit message:
#!/bin/sh
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat $COMMIT_MSG_FILE)
# Check for the presence of a valid ticket ID in the commit message
if ! echo "$COMMIT_MSG" | grep -qE '\b[A-Z]+-[0-9]+\b'; then
echo "Commit message does not include a valid ticket ID." >&2
exit 1
fi
This script when run will print the message on the console in case the commit is aborted. In this case, git aborted the commit operation due to the exit code 1, which is a non-zero exit status.
Here is the output when any commit operation is done on the repository:
post-commit
The post-commit hook is run after a commit is made. The purpose of the post-commit hook is to allow you to perform additional actions after a commit has been made, such as triggering a build process or sending notifications.
Here is an example for post-commit hook which sends notification to your slack channel when a commit is done:
#!/bin/sh
# Set the Slack webhook URL
SLACK_WEBHOOK_URL="<your_slack_webhook_url>"
# Get the name of the current branch
BRANCH_NAME=$(git symbolic-ref --short HEAD)
# Get the commit message
COMMIT_MSG=$(git log --format=%B -n 1 HEAD)
# Get the name of the committer
COMMITTER_NAME=$(git log --format="%an" -n 1 HEAD)
# Create the payload for the Slack message
PAYLOAD="payload={\"channel\": \"#general\", \"username\": \"Git Bot\", \"text\": \"New commit on branch *$BRANCH_NAME* by *$COMMITTER_NAME*:\n\`\`\`$COMMIT_MSG\`\`\`\"}"
# Send the message to Slack using curl
curl -X POST --data-urlencode "$PAYLOAD" "$SLACK_WEBHOOK_URL"
I have created a slack bot and added it to one of my slack channel, And provided the slack webhook url in the above code. When a commit is done, the notification will be sent to the slack channel.
Here is the output on slack channel when any commit operation is done on the repository:
pre-rebase
The pre-rebase hook is a script that runs before a git rebase
command is executed. This hook is useful for enforcing certain rules or checks before allowing a branch to be rebased.
Here is an example of a pre-rebase hook that prevents rebasing a branch that is behind the master branch:
#!/bin/sh
# Get the name of the current branch
BRANCH=$(git symbolic-ref --short HEAD)
# Check if the current branch is behind master
if [ $(git rev-list --count $BRANCH ^master) -gt 0 ]; then
echo "ERROR: Branch $BRANCH is behind the master branch. Please pull changes from master and try again."
exit 1
fi
exit 0
This script uses git rev-list
to check if the current branch is behind the master branch. If the current branch is behind master, the hook prints an error message and exits with a status of 1, which prevents the rebase from proceeding. If the current branch is not behind master, the hook exits with a status of 0, which allows the rebase to proceed.
Here is the output when trying to rebase a branch with master branch if the current branch is behind the master branch:
post-checkout
The post-checkout hook is executed after a branch is checked out. It can be used to trigger post-checkout actions, such as updating files, generating documentation, rebuilding the project.
Here is an example of a post-checkout hook that runs npm install
when switching to a new branch:
#!/bin/sh
# Run npm install after checkout
if [ -f "package.json" ]; then
echo "Running npm install..."
npm install --quiet
fi
exit 0
This script checks if the package.json
file exists in the root of the repository, and if it does, it runs npm install
to install any dependencies defined in the file.
Here is the output when checkout operation is done on the repository:
Server-side hooks
Server-side or remote hooks are scripts that are executed on the server when certain Git events occur, such as a push or a receive. These hooks can be used to automate tasks or enforce policies for a repository.
(Note: To show server side hooks, I created two directories, Git-Hooks-Client and Git-Hooks-Server. Installed git in both directories, but initialized Git-Hooks-Server as a Bare Git Repository. On the Git-Hooks-Server repository, add the server-side hooks.)
Here are some of the most commonly used server-side hooks:
pre-receive
The pre-receive hook is a script that runs on the Git server before it accepts incoming changes from a client. It allows you to enforce rules or policies on the changes being pushed to the server.
Here is an example of a pre-receive hook that prevents a user from pushing changes to the master branch:
#!/bin/bash
while read oldrev newrev refname; do
if [[ "$refname" == "refs/heads/master" ]]; then
echo "Pushes to the master branch are not allowed."
exit 1
fi
done
exit 0
This script checks if the user is trying to push changes to the master branch. If so, it prints an error message and exits with a non-zero status code, which causes the push to be rejected.
Here is the output when any push operation is done on the client repository:
update
The update hook is called after the pre-receive hook, and is used to enforce more fine-grained control over which references are updated. It allows you to prevent certain branches from being updated or to perform additional checks on the changes being pushed.
Here is an example of an update hook that just echoes a message to the console to ensure that it works:
#!/bin/bash
echo "The update hook is running"
exit 0
Here is the output when any push operation is done on the client repository:
post-receive
The post-receive hook is executed after a push has been accepted by the server. It is typically used for post-processing tasks, such as sending email notifications, updating a database, or triggering build and deployment pipelines. This hook works just like the post-commit hook.
Here is an example of post-receive hook just to ensure that it works:
#!/bin/bash
while read oldrev newrev refname
do
if [ "$refname" = "refs/heads/master" ]; then
echo "Push to master branch detected"
# do something here, such as deploy the changes to a server
fi
done
Here is the output when any push operation is done on the client repository:
Precis:
Creating custom scripts that Git can run automatically at these key points in the development life-cycle allows you to enforce coding standards, run automated tests, and enforce code base quality. As a result, developers save time and effort while also ensuring that the code base remains consistent. Although setting up custom hooks can be challenging initially, the investment is usually worthwhile, as it saves the team manual work later on.