Keine Werbung mögen? Gehen Werbefrei Heute

Git Hooks Pre-commit, Pre-push, and Stopping Bad Code at the Door

Aktualisiert am

Pre-commit, commit-msg, and pre-push hooks are shell scripts that run before git writes a commit or sends a push. Here's how to wire them up to catch linting failures, bad commit messages, and leaked secrets — with real examples you can drop in today.

Git Hooks: Pre-commit, Pre-push, and Stopping Bad Code at the Door 1
ANZEIGE Entfernen?

Git ships with hooks — shell scripts that fire at specific points in your workflow. Most repos have them sitting dormant in .git/hooks/ as .sample files. Most developers ignore them until a broken commit or a leaked API key makes them wish they hadn’t.

This covers the three hooks worth wiring up on every project: pre-commit, commit-msgund pre-push. Each one catches a different class of mistake. Each one is a shell script you can paste in and use today.

Where hooks live

Every git repo has a .git/hooks/ directory. Run ls .git/hooks/ and you’ll see sample files. Git ignores anything with the .sample suffix. To activate a hook, remove the suffix and make it executable:

cp .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

The contract is simple: exit 0 and git continues. Exit non-zero and git aborts, printing whatever you wrote to stderr.

pre-commit: your highest-value hook

Pre-commit runs after you type git commit but before the commit object is written. It can’t see the commit message — that isn’t written yet. What it kann do is inspect staged files and bail if something looks wrong.

The critical detail: use git diff --cached --name-only to get only the files you actually staged. Linting the entire project on every commit is slow and surfaces issues you didn’t introduce.

Linter example (ESLint)

#!/bin/sh
# Lint only staged JS files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.js$')

if [ -z "$STAGED_FILES" ]; then
  exit 0
fi

echo "Running ESLint on staged files..."
echo "$STAGED_FILES" | xargs ./node_modules/.bin/eslint

if [ $? -ne 0 ]; then
  echo "ESLint failed. Fix errors before committing."
  exit 1
fi

exit 0

Der --diff-filter=ACM flag skips deleted files — no point trying to lint something you just removed.

Secret scanning example

A blunt pattern scan that catches the obvious mistakes — hardcoded API keys, passwords in config files committed by accident:

#!/bin/sh
# Block commits with obvious secrets
PATTERNS="(AWS_SECRET|api_key\s*=|password\s*=|PRIVATE KEY)"

if git diff --cached | grep -qiP "$PATTERNS"; then
  echo "Potential secret detected in staged changes. Aborting commit."
  exit 1
fi

exit 0

For anything beyond toy projects, pair this with a dedicated scanner. detect-secrets from Yelp works well as a pre-commit hook — it maintains a baseline file so existing flagged strings don’t block every commit. trufflehog is better for scanning history after the fact or running in CI.

commit-msg: enforce message format

This hook receives one argument: the path to a temp file containing your draft commit message. Read it, validate it, exit 1 to reject. The file is writable — you can normalise the message instead of rejecting it, though that surprises people the first time.

Enforcing Conventional Commits format is the most common use. The payoff is automated changelogs, readable git log output, and CI pipelines that can parse commit types to decide what to release:

#!/bin/sh
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

# type(scope): description — scope is optional
PATTERN="^(feat|fix|chore|docs|style|refactor|test|perf|ci|build|revert)(\(.+\))?: .{1,72}"

if ! echo "$COMMIT_MSG" | grep -qP "$PATTERN"; then
  echo ""
  echo "Invalid commit message. Use Conventional Commits format:"
  echo "  type(scope): short description"
  echo ""
  echo "Valid types: feat, fix, chore, docs, style, refactor, test, perf, ci, build, revert"
  echo "Example:     feat(auth): add OAuth2 login flow"
  echo ""
  echo "Your message: $COMMIT_MSG"
  exit 1
fi

exit 0

This won’t catch a valid-format message that’s still meaningless like fix: fix things. That’s a culture problem, not a tooling one.

pre-push: the last gate before origin

Pre-push fires after git push is invoked but before any data goes to the remote. It receives the remote name and URL via stdin. This is where tests belong — not a linter (that runs in pre-commit), but actual tests that verify behavior.

#!/bin/sh
echo "Running tests before push..."

npm test

if [ $? -ne 0 ]; then
  echo "Tests failed. Push aborted."
  exit 1
fi

exit 0

One hard truth: if your test suite takes more than 60–90 seconds, people will use --no-verify. Run only fast unit tests here. Integration and E2E tests belong in CI where slow is acceptable. A hook that’s slow enough to bypass is worse than no hook.

Bypassing hooks — and when that’s fine

git commit --no-verify
git push --no-verify

Both flags exist for a reason. A broken hook blocking a hotfix is the kind of thing that turns an entire team against git hooks as a concept. Use --no-verify when you have a legitimate reason: a hook misfiring on unrelated files, or an emergency where the fix matters more than the gate.

If you’re reaching for it regularly, the hooks need tuning. The most common culprits: running too slowly, failing on files that weren’t changed, or pattern-matching false positives you’ve never cleaned up.

Sharing hooks with your team

.git/hooks/ is local to each clone — it’s intentionally not committed to the repository. Two approaches to get hooks to stick:

Option 1: core.hooksPath

Store hooks in a committed directory (e.g. .githooks/), then point git at it:

git config core.hooksPath .githooks

To automate this for new clones, add a prepare script to package.json — npm runs it automatically on npm install:

{
  "scripts": {
    "prepare": "git config core.hooksPath .githooks"
  }
}

Make the scripts executable, commit them, and anyone who clones and runs npm install gets the hooks configured automatically.

Option 2: Husky

Husky is the standard choice for JavaScript/Node projects. It handles the core.hooksPath wiring for you and gives each hook its own file in .husky/:

npx husky init

Then add hooks as plain shell scripts:

echo "npm test" > .husky/pre-push
chmod +x .husky/pre-push

Husky 9 (released early 2024) dropped JSON config entirely in favor of bare shell scripts. Simpler than v4/v8 for new setups, but migrating from Husky 4 is a breaking change the official migration guide undersells — the old .huskyrc format no longer works at all. If you’re upgrading an existing repo, budget time for it.

Non-JavaScript repos are better served by the core.hooksPath approach. There’s no reason to pull in a Node dependency just for hook management.

Review your diff before the hook runs it

git diff --cached shows staged changes in the terminal. When you’re comparing two versions of a config file or checking what a refactor actually changed across multiple edits, a visual diff is faster to scan. IO Tools’ Text Diff lets you paste in two versions and see exactly what changed — useful when you want to audit what’s going into a commit before any hooks run.

Git hooks are one of the few automation tools that live entirely in your existing workflow — no CI account, no extra service, no cost. A pre-commit hook that rejects a linting failure is exactly as reliable as the shell script you wrote. That’s a feature: you can read, debug, and modify it in a minute. Wire up the three hooks above and you’ve closed the most common gap between “it works locally” and “it works on main.”

Möchten Sie werbefrei genießen? Werde noch heute werbefrei

Erweiterungen installieren

IO-Tools zu Ihrem Lieblingsbrowser hinzufügen für sofortigen Zugriff und schnellere Suche

Zu Chrome-Erweiterung Zu Kantenerweiterung Zu Firefox-Erweiterung Zu Opera-Erweiterung

Die Anzeigetafel ist eingetroffen!

Anzeigetafel ist eine unterhaltsame Möglichkeit, Ihre Spiele zu verfolgen. Alle Daten werden in Ihrem Browser gespeichert. Weitere Funktionen folgen in Kürze!

ANZEIGE Entfernen?
ANZEIGE Entfernen?
ANZEIGE Entfernen?

Nachrichtenecke mit technischen Highlights

Beteiligen Sie sich

Helfen Sie uns, weiterhin wertvolle kostenlose Tools bereitzustellen

Kauf mir einen Kaffee
ANZEIGE Entfernen?