Git & GitHub · lesson 12 of 12
Blocking Bad Commits with a pre-commit Hook
Write a real executable pre-commit hook that inspects staged files, watch it reject a commit with a nonzero exit code, then fix the file and commit successfully.
By SkillByExample · Published
A hook is just an executable file with a name Git recognizes, sitting in
.git/hooks. No config file, no registration — the presence and the exit code are
the entire mechanism.
1) A pre-commit hook that inspects staged content
$ cat .git/hooks/pre-commit
#!/bin/sh
if git diff --cached --name-only | xargs grep -l "console.log" 2>/dev/null; then
echo "pre-commit: found console.log in a staged file, remove it before committing" >&2
exit 1
fi
exit 0
git diff --cached --name-only lists staged files — exactly what’s about to be
committed, not the whole working tree. The hook greps them for a forbidden string and
exits 1 if it finds one.
2) A commit that trips the hook
$ printf 'export function debugAdd(a, b) {\n console.log("adding", a, b);\n return a + b;\n}\n' > debug.mjs
$ git add debug.mjs
$ git commit -m "Add debugAdd helper"
debug.mjs
pre-commit: found console.log in a staged file, remove it before committing
Git ran the hook before doing anything else, saw exit code 1, and aborted — no commit
object was created. git log --oneline -1 still shows the previous commit; debug.mjs
is still staged, waiting to be fixed.
3) Fix the file, and the same commit succeeds
$ printf 'export function debugAdd(a, b) {\n return a + b;\n}\n' > debug.mjs
$ git add debug.mjs
$ git commit -m "Add debugAdd helper"
[main b31842c] Add debugAdd helper
1 file changed, 3 insertions(+)
create mode 100644 debug.mjs
Same hook, same commit message, different staged content — this time grep finds
nothing, the hook exits 0, and Git proceeds normally.
Practice
-
Make the hook executable and confirm the difference:
chmod -x .git/hooks/pre-commit, attempt a commit withconsole.logstaged, and observe Git silently skips a non-executable hook instead of blocking the commit.Solution
Git only runs hook files that are both present and executable. A non-executable
pre-commitis treated as if it doesn’t exist — no warning, no error, the commit just goes through. -
Add a second check to the hook (for example, blocking a staged file over some size) using the same
git diff --cachedpattern, and confirm both checks can independently block a commit. -
Bypass the hook deliberately with
git commit --no-verifyon a commit that would normally be blocked, and confirm it succeeds — useful to know, and worth explaining to a team why it should be used sparingly. -
Write a
commit-msghook (receives the commit message file path as$1) that rejects a commit message under 10 characters, and test it with a real short message. -
Clone the repository into a fresh directory and confirm
.git/hooks/pre-commitdoes not exist in the clone — hooks are local, not part of the repository’s tracked history.