Logo

dev-resources.site

for different kinds of informations.

Client side Git hooks 101

Published at
3/31/2024
Categories
git
hook
commit
automation
Author
svoop
Categories
4 categories in total
git
open
hook
open
commit
open
automation
open
Author
5 person written this
svoop
open
Client side Git hooks 101

Git hooks are simple yet powerful, essentially just scripts executed when certain events like commit or push occur. Most notably, they are really useful for enforcing code and commit quality. However, there's one problem: Since the hook scripts are stored inside the .git/ directory, they cannot be committed to the repository and shared with other developers as is. Let's see what we can do about this.

(Side note: Every fresh Git repository created with git init comes with a bunch of example hooks in .git/hooks/. They are named in a pretty self-explanatory fashion such as pre-commit.sample. Go ahead and take a look at them.)

Issues everywhere

Here's a real life example: Imagine a Ruby on Rails app on which a team of developers are working. The code is hosted on GitLab and all the work is coordinated using GitLab issues. In other words: For every commit, there's an associated issue and the issue number acts as a sort of primary key for documentation, time reporting and so forth. This convention has a few advantages, most notably the ability to easily learn more about how, when and by whom features were implemented as well as how this implementation came to be.

As far as Git is concerned, there are two rules:

  • Feature branches reference the issue ID in their name like 1234_signin_with_passkey.
  • Every commit message begins with a reference to the issue ID as well, for instance GL-1234: Add webauthn gem.

Putting these references to the respective beginning makes it easy to scan and search for them down the road. Also, GitLab hyperlinks all occurrences of GL-.... with the corresponding issue which greatly improves navigation.

(Side note: Issues are usually hash-prefixed like #1234 both on GitLab and GitHub. However, commit messages must not begin with a hash, they would be considered a comment and ignored. Therefore, GitHub has introduced the alternative prefix GH- and I've contributed a similar prefix GL- to GitLab a while ago.)

This convention works fine until one day, the unavoidable happens and someone commits Fix typo (#123).

Hook me up

First, we need a place inside the repository to store the hook scripts. You might go for .githooks/, but since this example is a Rails app, we'll use lib/git/hooks/.

The first script lib/git/hooks/commit-msg makes sure the commit messages are formatted the way we want:

#!/bin/sh

# Assure the commit message to correctly reference a GitLab issue, CVE
# or GHSA identifier.

regexps=(
    "^GL-\d+: [A-Z0-9]"
    "^CVE-\d{4}-\d+: [A-Z0-9]"
    "^GHSA-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{4}: [A-Z0-9]"
)

passed=false
for regexp in "${regexps[@]}"; do
  if head -n 1 "$1" | grep -qE "$regexp"; then
    passed=true
  fi
done
if ! $passed; then
  echo "The commit message does not match any of the following constraints:"
  for regexp in "${regexps[@]}"; do
    echo "* $regexp"
  done
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

This hook script receives a file which contains the commit message as the first argument $1 and compares it against the regular expressions which only allow our GitLab issue prefixes as well as two more prefixes for CVE and GHSA advisories.

(Side note: If you want to allow commit messages without any prefixes as well, you might add something like "^[^:]+$" to the regexps array.)

To make our lives a little easier, we'd like all commits to a properly named branch to get the issue prefix prefilled for us – enter lib/git/hooks/prepare-commit-msg:

#!/bin/sh

# Extract the issue number from the beginning of the branch name (e.g.
# 1234_my_cool_feature) and create a commit message template with the 
# corresponding prefix for GitLab:

prefix="GL"
if ! grep -qv '^#\|^$' "$1"; then
  issue=$(git branch --show-current | grep -oE "\d+[_-]" | grep -oE "\d+")
  if ! [ -z "$issue" ]; then
    echo "$prefix-$issue: $(cat "$1")" >"$1"
  fi
fi
Enter fullscreen mode Exit fullscreen mode

Make sure both scripts are executable!

At their current location, Git won't use them. You could symlink them into .git/hooks/, but there's a better way: Tell Git to look for the hook scripts where you've just put them.

git config core.hooksPath lib/git/hooks
Enter fullscreen mode Exit fullscreen mode

On a Rails app, you might want to add a Rake task lib/tasks/git.rake for this:

namespace :git do
  namespace :hooks do
    desc "Install client side hooks"
    task :install do
      `git config core.hooksPath lib/git/hooks` if Dir.exist? 'lib/git/hooks'
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Make sure, new developers install the hooks before they start committing by adding the Rake task to the README or the setup script of your project:

rake git:hooks:install
Enter fullscreen mode Exit fullscreen mode

You could even add this line somewhere in the Gemfile to always run this task when the bundle is installed, however, it's a little bit too intrusive for my taste.

And action!

Time to see this at work. Let's create a feature branch and commit some stuff:

git checkout -b 1234_my_awesome_feature
touch foobar.txt
git add .
git commit
Enter fullscreen mode Exit fullscreen mode

The last command opens the preferred editor and shows:

GL-1234: 
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch 1234_my_awesome_feature
# Changes to be committed:
#       new file:   foobar.txt
#
Enter fullscreen mode Exit fullscreen mode

Great, all that's missing is the actual message behind the prefilled GH-1234: and the commit is complete. But unfortunately, it's early morning, the coffee maker is broken and by accident, we edit the message to either:

My awesome commit
GL: My awesome commit
GL-ABCD: My awesome commit
GL-1234 My awesome commit
(...)
Enter fullscreen mode Exit fullscreen mode

The commit-msg hook won't accept any of those and output:

The commit message does not match any of the following constraints:
* ^GL-\d+: [A-Z0-9]
* ^CVE-\d{4}-\d+: [A-Z0-9]
* ^GHSA-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{4}: [A-Z0-9]
Enter fullscreen mode Exit fullscreen mode

Hook management

This lightweight approach is great for projects which require simple hook scripts only.

In case you wish to run entire pipelines of actions, say a combination of linting, style and license checking along with a required sign-off in the commit message, you should take a look at hook management tools like Lefthook or overcommit.

(Photo by engin akyurt on Unsplash)

commit Article's
30 articles in total
Favicon
Why I Built commit-ai: A Story About Git Security and Team Safety
Favicon
使用 AI 自動生成 Git Commit 訊息
Favicon
# How to write good commit messages
Favicon
Conventional Git Commits With Best Practices.
Favicon
Understanding Git Rebase Merge: Chronological vs Logical Order and Commit History
Favicon
Improving Commit Message Quality in VSCode with Copilot
Favicon
TIL how to see the entire commit column on GitLab using JS
Favicon
Why Going Back in Git Doesn't Have to Be Scary
Favicon
🤖 Use AI to speed up writing commit messages (bonus: custom prompt for improved generation)
Favicon
How to commit
Favicon
Commits Semânticos: Organizando o Caos com Padrões de Mensagens
Favicon
Consequences of for-Git-ting to merge the master into feature branch!
Favicon
Git: Commit Messages
Favicon
Yazılım Projelerinde Düzen ve Verimlilik İçin: Conventional Commits Nedir?
Favicon
Good commit message V/S Bad commit message 🦾
Favicon
วิธี sign commit ด้วย GPG บน GitHub
Favicon
Git Together: Pro Tips for Commits and Branches
Favicon
Commit vs. Rollback: Database Transaction Essentials
Favicon
Sign Git Commits and Authenticate to GitHub with SSH Keys
Favicon
Developers Hate This One Weird Trick To Be Improve Their Craft
Favicon
Conventional Commits
Favicon
Cara memperbaiki commit git yang terlanjur commited
Favicon
Multiple SSH id
Favicon
💻 Semantic Commits
Favicon
Client side Git hooks 101
Favicon
How to write GIT commit messages
Favicon
The Power of Conventional Commits ✨✨
Favicon
What I've Learned About Git from Senior Colleagues (Part 2 - thoughful commit)
Favicon
GitHub Commits Color Scheme: Lets commit to commit.
Favicon
How do I make a Git commit in the past?

Featured ones: