Git hooks to keep my blog clean

· May 12, 2025

As I wrote in the last post I’ve created a little script that spell checks all my blog posts. That was about 3000+ spelling mistakes.

And I then created a similar script to lint the markdown on all blog posts. Another 1250 warnings and errors.

Now that I’ve cleaned that up - I never want to do that again.

No - rather I would like to check for problems every time I make a change. Preferably just before I check stuff into Git.

Turns out - there’s a built-in Git-tool for that.

Let me show you how I put these things together to make a guarded check-in that don’t allow spelling or linting errors to be committed into the repository.

Markdown linting

The markdown linting is another script that I put in the scripts folder of this repository. It looks like this, not the way that I return the error code of the linting through STATUS=$? and exit $STATUS. This will become important later on.

#!/bin/bash

# Usage: bash lint-all.sh _posts

if [ -z "$1" ]; then
  echo "Usage: $0 <directory>"
  exit 1
fi

TARGET_DIR="$1"
FIX_FLAG="$2"

# echo "Linting Markdown files in directory: $TARGET_DIR"

npx --yes markdownlint-cli --config ./.markdownlint.json $TARGET_DIR $FIX_FLAG

STATUS=$?

if [ $STATUS -ne 0 ]; then
  echo "❌ Markdown linting failed."
else
  echo "✅ No markdown linting issues found."
fi

exit $STATUS

Git hooks

Each local git installation resides in the .git folder. Inside that folder there’s a hooks directory which are bash scripts that will be run at certain points in the life cycle of changes to git. For example .git/hooks/pre-commit runs just before each commit. This is the one we want.

Notice that there’s a .git/hooks/pre-commit.sample to take inspiration from.

Pre-commit script

I want my script to first check spelling (of all 1240+ blog posts) and then do markdown linting. If both of those are good I want to allow the commit.

This can be accomplished with the following script:

#!/usr/bin/env bash

# Run spelling check
bash ./scripts/check-spelling.sh _posts
SPELL_STATUS=$?

# Run markdown lint
bash ./scripts/markdown-lint.sh _posts --fix
LINT_STATUS=$?

# Check results
if [ $SPELL_STATUS -ne 0 ] || [ $LINT_STATUS -ne 0 ]; then
  echo "Pre-commit checks failed."
  [ $SPELL_STATUS -ne 0 ] && echo "❌ Spelling check failed"
  [ $LINT_STATUS -ne 0 ] && echo "❌ Markdown linting failed"
  exit 1
fi

echo "✅ All pre-commit checks passed."
exit 0

Here you can see how the exit code from the sub-scripts become important and I store them in $SPELL_STATUS and $LINT_STATUS respectively.

Having a script in .git/hooks/pre-commit means that the script is local to only you. This can be useful but sharing the goodness is better.

Let’s create the pre-commit-script inside the repository at /scripts/git-hooks/pre-commit for example and then make a link to the .git/hooks/pre-commit:

ln -s ../../scripts/git-hooks/pre-commit .git/hooks/pre-commit

Notice that the ../../ are need to get back to the root from the .git/hooks-directory.

Now you need to make the script executable in the .git/hooks/ directory.

chmod +x scripts/git-hooks/pre-commit

Try it out

And then I did a commit, to see the linting and spell-checker run. If it fails the commit is not added, and once the checks pass without errors your commit is made.

git add -A && git commit -m "Fixes a weird sentence"
✅ No spelling issues found.
✅ No markdown linting issues found.
✅ All pre-commit checks passed.
[main cc942bf] Fixes a weird sentence
 1 file changed, 1 insertion(+), 1 deletion(-)

Beautiful.

Twitter, Facebook