DEV Community

Charles Scholle
Charles Scholle

Posted on • Edited on

8 1 1

Git Smart: Streamlining Your Workflow with the prepare-commit-msg Hook

Overview

If you want to learn how to improve and automate your commit messages, you're in the right place!

Motivation

Most companies use an issue and project tracking software, such as JIRA, for software development. JIRA has integrations for many online VCSs. If you name your branches appropriately, then JIRA can automatically link to branches and pull requests created on many different code hosting platforms (GitHub, GitLab, Bitbucket, etc.)

When several developers are working in parallel, merging branches becomes commonplace. It can be very helpful to prefix your commits with the ticket number. For instance, assume you're working on ticket JIRA-1234 and want to commit a change, you might want to do something like this:

git commit -m "JIRA-1234: add part of my awesome feature"
Enter fullscreen mode Exit fullscreen mode

In theory, every commit could (and probably should) have this reference back to a ticket. If and when a someone needs more context around a commit, it provide the link to further documentation. However, since this is a manual process it can lead to a lack of uniformity and consistency.

But fear not! Git's got your back with its prepare-commit-msg hook.

Setting up the git hook

Here's a painless one-liner to install the hook into an existing repository. Open a terminal, navigate to your repository, then run the following command:

curl  https://gist.githubusercontent.com/chaz8080/36b539ca29a29de11334e57a9dd3b61d/raw/4f6dc9232346b7d866bcfc0fbcdd43b6fb638073/install-hook.sh | sh
Enter fullscreen mode Exit fullscreen mode

Here's what it's doing:

#!/bin/sh
HOOK_NAME="prepare-commit-msg"
REPO_ROOT=$(git rev-parse --show-toplevel)
HOOK_DIR="$REPO_ROOT/.git/hooks"
# Check if the hooks directory exists
if [ ! -d "$HOOK_DIR" ]; then
echo "Hooks directory not found. Creating $HOOK_DIR ..."
mkdir -p "$HOOK_DIR"
fi
# Download the hook script from the Gist URL
GIST_URL="https://gist.githubusercontent.com/chaz8080/36b539ca29a29de11334e57a9dd3b61d/raw/6be14a0225e937ba9396100aee16cc614cfad2f7/prepare-commit-msg"
curl -s "$GIST_URL" > "$HOOK_DIR/$HOOK_NAME"
# Make the hook script executable
chmod +x "$HOOK_DIR/$HOOK_NAME"
echo "Hook '$HOOK_NAME' has been installed in $HOOK_DIR"
view raw install-hook.sh hosted with ❤ by GitHub

It should add a prepare-commit-msg file to your repo's .git/hooks folder, and make it executable.

#!/bin/sh
COMMIT_MSG_FILE=$1
# You may need to make updates here if you're not using JIRA for your ticketing system
JIRA_TICKET=$(git rev-parse --abbrev-ref HEAD | grep -Eo '^(\w+/)?(\w+[-_])?[0-9]+')
# If a ticket is detected in the branch name AND it is not already prefixed, then prefix the commit message
# Eamples of branch to prefix:
# JIRA-1234 -> JIRA-1234: <COMMIT_MSG>
# JIRA-1234_my_feature -> JIRA-1234: <COMMIT_MSG>
# feature/JIRA-1234 -> feature/JIRA-1234: <COMMIT_MSG>
# bugfix/JIRA-1234_bug -> bugfix/JIRA-1234: <COMMIT_MSG>
if ! grep -q "^$JIRA_TICKET" "$COMMIT_MSG_FILE"; then
echo "$JIRA_TICKET: $(cat $COMMIT_MSG_FILE)" > "$COMMIT_MSG_FILE"
fi

Assuming that you're on a branch named after the JIRA ticket, e.g. JIRA-1234_my_awesome_feature, then you can simply commit your message as you normally would, without any ticket info:

git commit -m "add another part of my awesome feature"
Enter fullscreen mode Exit fullscreen mode

And the desired prefix will automatically be added. Running git log, you should see something like:

commit 8014201b6f223ca800c574bac093e53641c1bae9 (HEAD -> JIRA-1234_my_awesome_feature)
Author: John Smith <john.smith@gmail.com>
Date:   Sat Feb 25 21:05:22 2023 -0500

    JIRA-1234: add another part of my awesome feature
Enter fullscreen mode Exit fullscreen mode

Configure the hook globally

Instead installing the hook in every new repo cloned, let's configure global templates and configure the global init.templatedir.

Assuming that you do not already have templates set up, run the following command:

curl  https://gist.githubusercontent.com/chaz8080/36b539ca29a29de11334e57a9dd3b61d/raw/d1768296cbd15d59149097b8f8840ac107399567/configure-git-template.sh | sh
Enter fullscreen mode Exit fullscreen mode

Again, a peek under the hood:

#!/bin/sh
TEMPLATE_DIR=~/.git-template
HOOKS_DIR=$TEMPLATE_DIR/hooks
# Configure the global git template directory
git config --global init.templatedir $TEMPLATE_DIR
# Create the hooks directory inside the global git template directory
mkdir -p $HOOKS_DIR
GIST_URL="https://gist.githubusercontent.com/chaz8080/36b539ca29a29de11334e57a9dd3b61d/raw/6be14a0225e937ba9396100aee16cc614cfad2f7/prepare-commit-msg"
# Copy the prepare-commit-msg from the gist to the hooks directory
curl -s "$GIST_URL" > "$HOOKS_DIR/prepare-commit-msg"
# Make the hook executable
chmod a+x $HOOKS_DIR/prepare-commit-msg

This configures the global git template directory and installs the previous hook.

This will configure a new directory ~/.git-template. Anytime that you initialize or clone a new repository, these templates will be copied over.

Configure existing repositories

Now that you know how to create a one off hook for a single repo, and how to make sure that all new repos get the same config, you might want a script to also add the prepare-commit-msg hook to all of your existing repos.

In terminal, and at the directory for which you store all of your repositories, simply run:

curl https://gist.githubusercontent.com/chaz8080/36b539ca29a29de11334e57a9dd3b61d/raw/95a196a6d01aeee505f4ac66f2ef6256b6ed7d3e/install-to-all-repos.sh | sh
Enter fullscreen mode Exit fullscreen mode

For posterity, it simply searches all subdirectories for any git repositories and copies the prepare-commit-msg that was previously configured in your global git template:

#!/bin/sh
# Set the path to the updated prepare-commit-msg script
PREPARE_COMMIT_MSG_SCRIPT=$(git config --global init.templateDir)/hooks/prepare-commit-msg
# Find all Git repositories under the current directory
repos=$(find . -name ".git" -type d -prune -exec dirname {} \;)
# Loop through each repository and copy the updated script to the hooks directory
for repo in $repos; do
hooks_dir="$repo/.git/hooks"
if [ -d "$hooks_dir" ]; then
echo "Updating prepare-commit-msg hook in $repo"
cp "$PREPARE_COMMIT_MSG_SCRIPT" "$hooks_dir/prepare-commit-msg"
fi
done

Conclusion

In conclusion, using the prepare-commit-msg hook is a simple and effective way to improve the organization and readability of your Git commits, particularly in a collaborative development environment. By automatically prepending JIRA ticket numbers to commit messages, you can make it easier to track changes and tie them back to specific tickets or issues.

Setting up the prepare-commit-msg hook globally across your organization is easy and can be done with just a few simple steps. By following the instructions outlined in this article, you can ensure that all of your Git repositories have the hook installed and ready to use.

I hope that this article has been helpful in guiding you through the process of setting up the prepare-commit-msg hook and that you can start using it to improve your Git workflow today. Happy coding!

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (3)

Collapse
 
venzy profile image
venzy

Just checking, is the install-to-all-repos.sh script meant to read:
cp "$PREPARE_COMMIT_MSG_SCRIPT" "$hooks_dir/prepare-commit-msg"
instead of:
cp "$PREPARE_COMMIT_MSG_SCRIPT" "$hooks_dir/commit-msg"
?

Collapse
 
chaz8080 profile image
Charles Scholle • Edited

Just updated, thanks! 🙏

Collapse
 
logarithmicspirals profile image
logarithmicspirals • Edited

Very cool, bookmarking this! Haven't seen this before. Good post 🎉.

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay