DEV Community

Cover image for Tackling Deployment Failures with Git Hooks
Ravishanker
Ravishanker

Posted on

Tackling Deployment Failures with Git Hooks

It's 3 am and you worked hard to fix a bug/implement a feature and push your commits to GitHub and went to sleep peacefully only to see a failing deployment build email in your inbox when you wake up.

If you are someone like me who uses CI/CD to deploy code instead of just pushing the files directly to the production server via SFTP without any VCS, You would have gone through this at least once.

It can be overcome by using stricter lint configs but will end up with more red squiggly lines in your IDE while doing quick edits, and If you are working on a team not everyone enjoys stricter lint configs for quick edits and ends up not using it anyways. A better solution to this would be using Git Hooks.


What are Git Hooks?

Hooks are a way to fire off custom scripts when certain important actions occur. - Git Documentation

As the documentation says we can write our custom scripts that can be triggered when a specific set of actions occur in our repository. These hooks are divided into two types Client-Side Hooks and Server-side Hooks. In this blog, we are going to implement Client-Side hooks.


How to implement these hooks?

To implement hooks in a git repository we need to go to .git/hooks/.

When we do ls in this directory we get to see several sample scripts that are present already as follows:

applypatch-msg.sample*      pre-push.sample*
commit-msg.sample*          pre-rebase.sample*
fsmonitor-watchman.sample*  pre-receive.sample*
post-update.sample*         prepare-commit-msg.sample*
pre-applypatch.sample*      push-to-checkout.sample*
pre-commit.sample*          sendemail-validate.sample*
pre-merge-commit.sample*    update.sample*
Enter fullscreen mode Exit fullscreen mode

Each of these scripts is executed when the specified action occurs in the repository. The one we are going to see now is pre-push. This script executes before the commits are pushed to the remote repository.

Now create a new file called pre-push and open it in a text editor of your choice. I'm using nvim here :

touch pre-push && chmod +x pre-push && nvim pre-push 
Enter fullscreen mode Exit fullscreen mode

In that script add the following:

if npm run build; then
    echo "Build successful. Proceeding with push."
    exit 0
else
    echo "Build failed. Please fix the issues before pushing"
    exit 1
fi
Enter fullscreen mode Exit fullscreen mode

We are running npm run build (you can change the command if needed) and if it builds successfully we are exiting with exit 0 else exiting with exit 1. Exiting with a non-zero value in sh world denotes that the script is executed with errors. Which means the push will not happen.

So is that it? Not really. If you are the person who just pushes everything to the main branch then this is done. Otherwise, we need to specify the deployment branch so that this hook runs only when the commits are pushed to that branch.

To do that we need to find the branch we are currently on. It can be done by using the rev-prase command in git. However just calling the rev-parse will return the branch id instead of the name so we need to also add the --abbrev-ref argument to get the branch name. Now our script will look like this.

##!/bin/bash

branch=$(git rev-parse --abbrev-ref HEAD)
dep_branch="main"

if [ "$branch" == "$dep_branch" ]; then
    if npm run build; then
        echo "Build successful. Proceeding with push."
        exit 0
    else
        echo "Build failed. Please fix the issues before pushing to the main branch."
        exit 1
    fi
fi

exit 0
Enter fullscreen mode Exit fullscreen mode

You can change the dep_branch variable to the actual name of your deployment branch.

So is that it? Yes, but there's more. Optionally we can track changes on specific folders and only do the build if there are changes in specific folders. For example, we don't need to check if the build passes if we just modified your README.

To do that, We need to use the diff command to see if there are changes in the directory we need. In my case, it's the src/ directory.

function are_changes_in_dir {
    local changed_files=$(git diff --name-only origin/main HEAD)
    echo "$changed_files" | grep -q "^src/"
}
Enter fullscreen mode Exit fullscreen mode

Alternatively, we can write the function like this so that we can track multiple directories.

function are_changes_in_folders {
    local folders=("src/" "lib/" "utils/")  # Add your desired folders to this array
    local changed_files=$(git diff --name-only origin/main HEAD)

    for folder in "${folders[@]}"; do
        echo "$changed_files" | grep -q "^$folder" && return 0
    done

    return 1
}
Enter fullscreen mode Exit fullscreen mode

This above function checks if there are any changes in the specified directory. Our final script will look something like this:

##!/bin/bash

branch=$(git rev-parse --abbrev-ref HEAD)

function are_changes_in_dir {
    local folders=("src/" "lib/" "utils/")  # Add your desired folders to this array
    local changed_files=$(git diff --name-only origin/main HEAD)

    for folder in "${folders[@]}"; do
        echo "$changed_files" | grep -q "^$folder" && return 0
    done

    return 1
}


if [ "$branch" == "main" ]; then
    if are_changes_in_dir then
        echo "Changes detected in the specified directories. Running npm run build..."

        if npm run build; then
            echo "Build successful. Proceeding with push."
            exit 0
        else
            echo "Build failed. Please fix the issues before pushing to the main branch."
            exit 1
        fi
    else
        echo "No necessary changes detected. Skipping build."
        exit 0
    fi
fi

exit 0
Enter fullscreen mode Exit fullscreen mode

Save this and goodbye to failed deployment builds*

*Your Team members should also use this hook or else they can push failing commits.


Top comments (0)