Skip to content

Setup — Git Hook Collection

Create the project structure and verify ShellCheck is available.

Directory Structure

mkdir -p ~/projects/git-hooks
cd ~/projects/git-hooks

# Create hook files (will write content in implementation)
touch pre-commit commit-msg pre-push install.sh
chmod +x pre-commit commit-msg pre-push install.sh

Your project:

~/projects/git-hooks/ ├── pre-commit ← ShellCheck + whitespace check ├── commit-msg ← conventional commit format validator ├── pre-push ← main branch protection └── install.sh ← symlinks hooks into .git/hooks/

Install ShellCheck

ShellCheck is a static analysis tool for shell scripts. Install it before writing the pre-commit hook.

# Debian/Ubuntu
sudo apt install shellcheck

# macOS
brew install shellcheck

# Fedora/RHEL
sudo dnf install ShellCheck

# Verify
shellcheck --version
ShellCheck - shell script analysis tool version: 0.9.0

Create a Test Git Repository

You need a git repo to test hooks against:

mkdir -p /tmp/hook_test_repo
cd /tmp/hook_test_repo
git init
git config user.email "test@example.com"
git config user.name "Test User"

Understand Staged Files

The pre-commit hook must inspect only staged files (not all files):

# List staged files
git diff --cached --name-only
scripts/deploy.sh scripts/backup.sh
# List staged .sh files only
git diff --cached --name-only --diff-filter=ACM | grep '\.sh$'
scripts/deploy.sh scripts/backup.sh

The --diff-filter=ACM flag includes Added, Copied, and Modified files — it excludes Deleted files that you wouldn't want to check.

Read the commit-msg Argument

Git passes the path to the commit message file as $1 to the commit-msg hook:

# In commit-msg hook:
MSG_FILE="$1"
MSG="$(cat "$MSG_FILE")"
echo "Message: $MSG"

Test this manually:

echo "feat: add new feature" > /tmp/test_msg
bash commit-msg /tmp/test_msg
echo "Exit: $?"

Read pre-push Arguments

The pre-push hook receives the remote name and URL on stdin and as arguments:

# In pre-push hook:
REMOTE="$1"
URL="$2"
echo "Pushing to remote: $REMOTE ($URL)"

# Reads lines: <local_ref> <local_sha1> <remote_ref> <remote_sha1>
while IFS=' ' read -r local_ref local_sha remote_ref remote_sha; do
    echo "Ref: $local_ref$remote_ref"
done

requirements | implementation