Master

Git that checks its own work

Hooks felt like cheating the first time my pre-commit hook caught a secret I had pasted into a file three files deep. Git started watching my back before I could make a mistake.

What you will see

[main def7890] cleancommit 1 file changed, 1 insertion(+) create mode 100644 clean.txt def7890 cleancommit abc4567 initial

About 2 min of reading

Try It

Make git check its own work before it commits. I wrote a pre-commit hook that refuses to save anything containing a secret, and watched a bad commit get blocked before it ever landed.

1

Point git at a hooks folder you control. By default hooks live in .git/hooks, but setting the config makes a single folder the source of truth for the checks on your machine.

git config core.hooksPath hooks

Before you run it, say out loud what it will print. Then open the answer.

What it prints
No output. The command sets the config quietly and prints nothing on success. Confirm it took by running `git config core.hooksPath`, which now echoes `hooks`.
An empty output means the command succeeded quietly. Git now runs any executable script it finds in the `hooks` folder before committing.
2

Write a pre-commit hook that greps staged changes for the word SECRET and refuses to commit if it finds it. Make the script executable.

printf '#!/bin/sh\nif git diff --cached --diff-filter=ACMR -p | grep -q SECRET ; then\n echo BLOCKED_SECRET; exit 1\nfi\n' > hooks/pre-commit chmod +x hooks/pre-commit

Before you run it, say out loud what it will print. Then open the answer.

What it prints
No output. The two commands write the hook script to `hooks/pre-commit` and make it executable. List the folder with `ls -l hooks/pre-commit` to confirm the file exists and carries the execute bit (`-rwxr-xr-x`).
The script must be executable and must `exit 1` to block the commit. The shebang `#!/bin/sh` lets the shell run it on most systems.
3

Stage a file that contains the word SECRET and try to commit. The hook fires and blocks the commit, and git refuses to record the change.

printf 'token=SECRET\n' > key.txt git add key.txt git commit -m addkey

Before you run it, say out loud what it will print. Then open the answer.

What it prints
BLOCKED_SECRET
The commit is rejected and the file stays staged, not saved. Fix the file, remove the secret, then commit again to see the hook let it through.
4

Stage a clean file and commit again. This time the hook finds nothing and the commit goes through normally.

printf 'all good here\n' > clean.txt git add clean.txt git commit -m cleancommit git log --oneline

Before you run it, say out loud what it will print. Then open the answer.

What it prints
[main def7890] cleancommit 1 file changed, 1 insertion(+) create mode 100644 clean.txt def7890 cleancommit abc4567 initial
The commit succeeds and shows its normal summary line. Hooks are how you make git enforce rules, like blocking secrets, before anything reaches the remote.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A hook fires at a stage of a git action and can block the action by exiting with an error.
1 You start a commit
↓
2 git runs the pre-commit hook
↓
3 The hook checks your staged files
↓
4 If it fails, the commit is cancelled
↓
5 If it passes, the commit proceeds
Why this works
What hooks can and cannot do

Hooks are local scripts, so by default they live on your machine and do not travel with the repo. That is a feature, not a bug: it means your hooks are your own. To share them, put them in the repo and point git at them with core.hooksPath, but make sure the whole team wants the same checks before you force them on everyone.

The cost of an always-on gate

Every hook adds a small delay before your commit, and a strict one can get annoying when you are just throwing together a scratch change. The trick is to make hooks check the things that actually hurt, secrets, formatting, tests, and let the noise through. A hook you routinely work around is worse than no hook at all.

Examples

Stop a bad commit before it happens

git config core.hooksPath .git/hooks

A pre-commit hook is a small script git runs right before it records a commit. If the script exits with an error, the commit does not happen. I wrote one that greps for API keys in my staged files, and it caught a real secret on its first run. That single hook saved a much uglier incident later.

Force a readable message

echo "if [ ${#1} -lt 10 ]; then echo 'too short'; exit 1; fi" > .git/hooks/commit-msg

A commit-msg hook inspects the message you typed and can reject one that is too short or missing a prefix. This is the gentle form of nagging: instead of a teammate commenting "please explain this commit", git refuses to take it in the first place.

Choose the hook you need

.git/hooks/pre-commit      # before the commit
.git/hooks/commit-msg      # checks the message
.git/hooks/pre-push        # before sending commits
.git/hooks/post-commit     # after a commit lands

Each hook runs at a different moment. Most people only ever need pre-commit, but knowing the others opens up automation you cannot do any other way. Drop a hook in and name it without the .sample extension to switch it on.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Many checkouts, one repo
Previous Lesson
Many checkouts, one repo
Next Lesson
Undoing anything, eventually
Undoing anything, eventually →