#!/usr/bin/env bash: Create Your Own Git Pre-Commit Hook from Scratch
Git hooks are one of Git’s most powerful yet underused features. They allow you to automate parts of your workflow by executing scripts at specific points in the Git lifecycle. One of the most commonly used hooks is the pre-commit hook, which runs before a commit is finalized. In this article, we’ll walk through how to write a custom Git pre-commit hook in Bash that automatically formats code and runs validation checks before each commit. This technique can save you from committing broken or inconsistent code.
1. What Are Git Hooks and Why Use Them?
Git hooks are scripts located in the .git/hooks directory of your repository. Each script corresponds to a particular Git event—like pre-commit, commit-msg, or post-merge. These hooks run automatically when the associated event is triggered.
The pre-commit hook runs before a commit is created and can be used to lint code, run tests, or even block commits entirely if validation fails. This allows teams to enforce code quality standards automatically.
Out of the box, Git provides sample hook scripts with a .sample extension. They aren’t active until you rename them and make them executable.
2. Creating and Installing a Pre-Commit Hook
To create a new pre-commit hook, navigate to your repository’s .git/hooks directory and create a Bash script named pre-commit:
cd my-project/.git/hooks
nano pre-commit
Start the script with the standard shebang line:
#!/usr/bin/env bash
Make sure the script is executable:
chmod +x .git/hooks/pre-commit
Now, Git will run this script every time you attempt to commit changes. Let’s turn it into something useful.
3. Auto-Formatting with Code Formatters
Suppose your project uses Prettier (common in JavaScript projects) or Black (for Python). You can configure the hook to auto-format staged files before allowing the commit.
Example for JavaScript using Prettier:
#!/usr/bin/env bash
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.js$|\.ts$')
if [[ "$STAGED_FILES" != "" ]]; then
echo "Running Prettier on staged files..."
echo "$STAGED_FILES" | xargs npx prettier --write
echo "$STAGED_FILES" | xargs git add
fi
This script identifies all staged JavaScript/TypeScript files, formats them using Prettier, and re-stages them. This ensures that only properly formatted code makes it into commits.
4. Adding Validation Checks
Beyond formatting, you can also validate files—for example, using ESLint for linting or running unit tests. Here’s how to block a commit if ESLint fails:
#!/usr/bin/env bash
STAGED_JS_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.js$')
if [[ "$STAGED_JS_FILES" != "" ]]; then
echo "Running ESLint..."
npx eslint $STAGED_JS_FILES
if [[ $? -ne 0 ]]; then
echo "ESLint failed. Aborting commit."
exit 1
fi
fi
This hook will abort your commit if linter errors are detected, ensuring code quality rules are enforced locally before pushing to your repository.
5. Best Practices and Tips
- Speed matters: Keep your pre-commit hooks fast. Slow hooks can frustrate developers.
- Scope the files: Run tools only on staged files to avoid unnecessary computation.
- Make hooks project-aware: Use tools like
nvmorpyenvto ensure correct versions are used. - Use dependency-free bash where possible: Especially in small teams, minimizing dependencies helps portability.
- Store hooks outside .git: Track them in your repo (e.g., under
scripts/git-hooks) and symlink them with a setup script.
Bonus tip: Automate hook installation using a setup script:
#!/usr/bin/env bash
cp scripts/git-hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
6. Going Further: Shared Hooks and Tooling
If your team wants a reusable and centralized hook mechanism, consider using tools like pre-commit or Lefthook. These tools manage hook installation and make it easy to maintain a consistent workflow across multiple repositories and contributors.
However, hand-rolled hooks like the one we built provide more flexibility and transparency, particularly when onboarding to new workflows or working on less-common stacks.
Conclusion
Git pre-commit hooks are a powerful way to level up your developer workflow. Whether you want to enforce code styling, run security scanners, or catch bugs early, writing your own Bash-based hook gives you precise control without adding new dependencies. Start small, keep it fast, and customize it to fit your team’s needs. Happy hooking!
Useful links:


