Universal Git hooks that work everywhere β zero config, any language, any project.
Production-grade git hooks that auto-detect your stack and enforce quality gates.
No frameworks. No dependencies. Pure Bash. Works on every git init and git clone.
Quick Install β’ Features β’ Supported Languages β’ Configuration β’ Skip Hooks
bash <(curl -fsSL https://raw.githubusercontent.com/ca971/git-templates/main/install.sh)Or clone and install:
git clone https://github.com/ca971/git-templates.git
cd git-templates
bash install.shThat's it. Every future git init and git clone will include the hooks automatically.
| Hook | When | What it does |
|---|---|---|
commit-msg |
On commit | Validates Conventional Commits format |
pre-commit |
On commit | Quality gate β secrets, formatting, syntax, large files |
pre-push |
On push | Final gate β WIP detection, full lint, branch protection, tests |
post-merge |
On pull/merge | Smart summary β deps changes, version bumps, action items |
_lib.sh |
Shared | Colors, config system, skip logic, tool detection |
β
Zero config Works out of the box on ANY repo
β
Auto-detection Only checks languages actually present
β
Fail gracefully Missing tools = skip, never crash
β
Per-repo config .githooks.conf for fine-tuning
β
3 skip levels Env var, config file, --no-verify
β
No dependencies Pure Bash β no Node, no Python, no framework
β
Non-destructive Warnings vs errors β you choose what blocks
β Conventional Commits format (feat, fix, docs, chore, ...)
β Breaking change detection (! marker + BREAKING CHANGE footer)
β Subject length (configurable, default 72)
β Imperative mood ("add" not "added")
β No trailing period
β Body formatting (blank line separator, line length)
β Auto-skip: merge, revert, WIP/fixup commits
πΈ Example output
π« Invalid commit message format
Expected: <type>(<scope>): <subject>
Types:
feat New feature β MINOR
fix Bug fix β PATCH
docs Documentation only β PATCH
refactor Code restructuring β PATCH
...
Your message: updated the readme
Examples:
feat(auth): add OAuth2 login flow
fix(api): resolve null pointer on empty response
docs: update installation guide
Universal checks (always active):
β Debug markers (TODO, console.log, debugger, binding.pry, ...)
β Backup/temp files (.bak, .swp, .old, ...)
β Large files (> 5MB, configurable)
β Secrets & credentials (API keys, tokens, passwords, AWS, GitHub, ...)
β Merge conflict markers (<<<<<<<, =======, >>>>>>>)
β .env files (should be gitignored)
β Trailing whitespace
Language checks (auto-detected):
β Lua β StyLua + syntax (LuaJIT/luac)
β Python β Ruff / Black + syntax
β JavaScript β Prettier + ESLint
β TypeScript β Prettier + ESLint
β Go β gofmt + go vet
β Rust β rustfmt + Clippy
β Shell β ShellCheck
β YAML β yamllint
β JSON β jq / python3
β TOML β Taplo
β Markdown β markdownlint
β Dockerfile β Hadolint
β Terraform β terraform fmt
β CSS/SCSS β Stylelint
πΈ Example output
π Pre-commit checks...
β No debug markers
β No backup files
β No large files
β No credentials detected
β No merge conflict markers
β No .env files
β No trailing whitespace
ββ Language checks ββ
β StyLua formatting β 42 file(s)
β Lua syntax (LuaJIT) β 42 file(s)
β ShellCheck β 3 file(s)
β
All pre-commit checks passed
β WIP/fixup/squash commit detection (blocks push)
β Full language checks (same as pre-commit, all tracked files)
β Tag validation (version file β tag consistency)
β Changelog entry check (on tag push)
β Branch protection (interactive confirm on main/master/production)
β Uncommitted changes warning
β Test suite runner (opt-in, auto-detected)
β File changes breakdown (dynamic directory grouping with icons)
β Dependency lockfile detection (22 package managers)
β Version bump detection (10+ ecosystems)
β Changelog updates
β New / deleted files summary
β Config change detection (Docker, CI, Terraform, Makefile, ...)
β Actionable recommendations
β Commit count & authors
πΈ Example output
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π Post-merge summary β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
Files changed: 23 total
+ 5 added
~ 16 modified
- 2 deleted
By area:
π¦ src/ 12 file(s)
π§ͺ tests/ 5 file(s)
π€ .github/ 3 file(s)
π config/ 2 file(s)
π (root) 1 file(s)
π¦ Dependencies changed:
package-lock.json (npm)
π·οΈ Version β v2.1.0
π ## [2.1.0] β 2025-01-15
π Recommended actions:
1. npm install
2. Review build targets
βββββββββββββββββββββββββββββββββββββββββββββ
8 commit(s) merged
Authors: Alice, Bob
β
Merge complete
| Language | Formatter | Linter | Syntax Check |
|---|---|---|---|
| Lua | StyLua | β | LuaJIT / luac |
| Python | Ruff / Black | Ruff | py_compile |
| JavaScript | Prettier | ESLint | β |
| TypeScript | Prettier | ESLint | tsc --noEmit |
| Go | gofmt |
go vet |
go build |
| Rust | rustfmt |
Clippy | cargo check |
| Shell | β | ShellCheck | β |
| YAML | β | yamllint | β |
| JSON | β | β | jq / python3 |
| TOML | Taplo | β | β |
| Markdown | β | markdownlint | β |
| Dockerfile | β | Hadolint | β |
| Terraform | terraform fmt |
terraform validate |
β |
| CSS/SCSS | β | Stylelint | β |
No tool installed? No problem. Checks are skipped gracefully β never crash, never block.
22 package managers
| Lockfile | Manager | Action |
|---|---|---|
package-lock.json |
npm | npm install |
yarn.lock |
Yarn | yarn install |
pnpm-lock.yaml |
pnpm | pnpm install |
bun.lockb |
Bun | bun install |
requirements.txt |
pip | pip install -r requirements.txt |
Pipfile.lock |
Pipenv | pipenv install |
poetry.lock |
Poetry | poetry install |
uv.lock |
uv | uv sync |
Gemfile.lock |
Bundler | bundle install |
composer.lock |
Composer | composer install |
go.sum |
Go modules | go mod download |
Cargo.lock |
Cargo | cargo build |
mix.lock |
Mix | mix deps.get |
pubspec.lock |
Flutter/Dart | flutter pub get |
Podfile.lock |
CocoaPods | pod install |
flake.lock |
Nix | nix flake update |
lazy-lock.json |
lazy.nvim | :Lazy sync |
build.zig.zon |
Zig | zig build |
pdm.lock |
PDM | pdm install |
packages.lock.json |
.NET | dotnet restore |
Place a .githooks.conf at your repo root to customize behavior:
# Example: Python API project
[commit-msg]
subject_max_length = 72
strict_warnings = true # warnings block commit
[pre-commit]
max_file_size = 10485760 # 10MB
debug_markers_block = true
disable = prettier,eslint # not a JS project
[pre-push]
protected_branches = main,staging,production
run_tests = true
test_command = pytest -q -x
disable = prettier,eslint
[post-merge]
show_stat = true
max_files_shown = 15π Docs/Notes repo
[pre-commit]
disable = stylua,shellcheck,ruff,eslint,prettier,gofmt,rustfmt
[pre-push]
disable = stylua,lua-syntax,ruff,eslint,prettier,tsc,gofmt,govet,rustfmt,cargo-checkπ Neovim plugin (Lua)
[pre-commit]
debug_markers_block = true
[pre-push]
run_tests = falseβοΈ React/Next.js app
[pre-push]
run_tests = true
test_command = npm test -- --watchAll=false
protected_branches = main,stagingπ¦ Rust project
[pre-push]
run_tests = true
test_command = cargo test --quietπΉ Go service
[pre-push]
run_tests = true
test_command = go test ./...
protected_branches = main,production| Section | Key | Default | Description |
|---|---|---|---|
[global] |
skip_all |
false |
Disable all hooks for this repo |
[commit-msg] |
skip |
false |
Skip this hook |
subject_max_length |
72 |
Max subject line length | |
body_max_length |
100 |
Max body line length | |
strict_warnings |
false |
Warnings become errors | |
[pre-commit] |
skip |
false |
Skip this hook |
max_file_size |
5242880 |
Max file size in bytes (5MB) | |
debug_markers_block |
false |
Debug markers become errors | |
trailing_ws_block |
false |
Trailing whitespace becomes error | |
disable |
(empty) | Comma-separated checkers to skip | |
[pre-push] |
skip |
false |
Skip this hook |
protected_branches |
main,master,production,release,staging |
Branches requiring confirmation | |
allow_wip |
false |
Allow WIP commits to be pushed | |
run_tests |
false |
Run test suite before push | |
test_command |
(auto) | Custom test command | |
disable |
(empty) | Comma-separated checkers to skip | |
[post-merge] |
skip |
false |
Skip this hook |
show_stat |
true |
Show git diff --stat | |
max_files_shown |
10 |
Max files in stat output |
Three levels of escape hatches:
# Skip ALL hooks (one-time)
SKIP_HOOKS=1 git commit -m "feat: emergency fix"
SKIP_HOOKS=1 git push
# Skip SPECIFIC hook (one-time)
SKIP_COMMIT_MSG=1 git commit -m "wip"
SKIP_PRE_COMMIT=1 git commit
SKIP_PRE_PUSH=1 git push
SKIP_POST_MERGE=1 git pull
# Git native (skips pre-commit + commit-msg)
git commit --no-verify
git push --no-verify
# Permanent skip for a repo (.githooks.conf)
# [pre-commit]
# skip = true# Check installation status
bash install.sh --check
# Update to latest version
bash install.sh --update
# Uninstall completely
bash install.sh --uninstall
# Inject hooks into existing repo
cd your-existing-repo && git init # safe β won't erase anything
# Inject into ALL repos under a directory
find ~/Projects -name ".git" -type d -maxdepth 3 -exec dirname {} \; | \
xargs -I{} sh -c 'cd "{}" && git init'# Bonus script : Inject in ALL your existing repos
inject-hooks() {
local dir="${1:-$HOME/Projects}"
find "$dir" -name ".git" -type d -maxdepth 3 2>/dev/null | while read -r gitdir; do
repo=$(dirname "$gitdir")
echo -e " β ${repo}"
(cd "$repo" && git init) 2>/dev/null
done
echo -e "\nβ
Done"
}
# Usage:
inject-hooks ~/Projects# ββ Git hooks shortcuts βββββββββββββββββββββββββββββββββββββββ
alias ghcheck='bash ~/.git-templates/install.sh --check'
alias ghupdate='bash ~/.git-templates/install.sh --update'
# Skip shortcuts
alias gc!='SKIP_HOOKS=1 git commit'
alias gp!='SKIP_HOOKS=1 git push'
# Reinject hooks in current repo
alias ghinit='git init' # safe β just copies hooks
# Edit hooks config for current repo
alias ghconf='${EDITOR:-vim} .githooks.conf'
alias ghconf-init='cp ~/.git-templates/githooks.conf.example .githooks.conf && ${EDITOR:-vim} .githooks.conf'~/.git-templates/hooks/
βββ _lib.sh Shared foundation
β βββ Colors & formatting
β βββ Tool detection (command_exists)
β βββ File detection (staged/tracked by extension)
β βββ Config system (.githooks.conf parser)
β βββ Skip logic (3 levels)
β βββ Checker registry (enable/disable)
β
βββ commit-msg Conventional Commits enforcement
β βββ Format β Type β Length β Mood β Body
β
βββ pre-commit Quality gate (staged files only = fast)
β βββ Universal: secrets, large files, markers, .env
β βββ Language: 14 auto-detected ecosystems
β
βββ pre-push Final gate (all tracked files)
β βββ WIP/fixup detection
β βββ Language checks (full scan)
β βββ Tag β version consistency
β βββ Branch protection
β βββ Test runner (opt-in)
β
βββ post-merge Intelligence (informational, never blocks)
βββ Dynamic file breakdown
βββ 22 lockfile detections
βββ Version bump detection
βββ Actionable recommendations
βββ hooks/
β βββ _lib.sh β Shared: colors, config, skip, detection
β βββ commit-msg β Conventional commits (universal)
β βββ pre-commit β Quality gate (14 languages)
β βββ post-merge β Post-pull intelligence (22 lockfiles)
β βββ pre-push β Final gate (6 languages + tests + tags)
βββ githooks.conf.example β Configuration template
βββ install.sh β One-command installer- β 14 languages auto-detected
- β 22 lockfiles recognized
- β 10+ versioning formats supported
- β 3 skip levels (env / config / global)
- β Per-repo deactivatable checkers
- β Zero-crash design on empty or minimal repos
- β Full lifecycle: install / update / uninstall / check
- β Automatic backup before every update
PRs welcome! Some ideas:
- Add support for more languages (Kotlin, Swift, Dart, Zig, ...)
- Add
pre-rebasehook - Add
prepare-commit-msghook (auto-prefix with branch name) - Performance benchmarks
- Fish shell support
MIT β Use it, fork it, make it yours.
Made with π§ by ca971
If this saves you time, β the repo!