Automatisieren Sie Ihren Workflow: Ein praktischer Leitfaden zu Git-Client-seitigen Hooks

Nutzen Sie Git-Client-seitige Hooks für schnelle lokale Prüfungen, gemeinsame Einrichtung, Commit-Nachrichtenregeln und sicherere Post-Merge-Automatisierung.

Automatisieren Sie Ihren Workflow: Ein praktischer Leitfaden zu Git-Client-seitigen Hooks

Git-Client-seitige Hooks sind kleine Skripte, die auf Ihrem Rechner ausgeführt werden, wenn Git bestimmte Punkte in einem Workflow erreicht. Ein pre-commit-Hook wird ausgeführt, bevor ein Commit erstellt wird. Ein commit-msg-Hook wird ausgeführt, nachdem Sie die Nachricht geschrieben haben, aber bevor Git sie akzeptiert. Ein post-merge-Hook wird ausgeführt, nachdem ein Merge abgeschlossen ist. Gut eingesetzt, fangen Hooks langweilige Fehler frühzeitig ab: vergessene Formatierung, kaputte generierte Dateien, fehlende Abhängigkeitsinstallationen oder Commit-Nachrichten, die nicht der Konvention Ihres Teams entsprechen.

Die wichtige Einschränkung ist, dass Client-seitige Hooks lokal sind. Sie werden nicht automatisch mit dem Repository übertragen, wenn jemand es klont. Das macht sie großartig für schnelles Feedback und lokale Bequemlichkeit, aber schwach als einzige Durchsetzungsebene für eine Teamregel. Wenn eine Prüfung wirklich den Hauptbranch schützt, setzen Sie sie auch in CI oder einer serverseitigen Regel ein.

Jedes Repository hat ein Hooks-Verzeichnis unter .git/hooks:

ls .git/hooks

Ein neues Repository enthält normalerweise Beispieldateien wie pre-commit.sample. Ein Beispiel-Hook tut nichts, bis Sie eine ausführbare Datei ohne das Suffix .sample erstellen:

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

Hooks können Shell-Skripte, Python-Skripte, Ruby-Skripte, Node-Skripte oder alles andere sein, was Ihr Rechner ausführen kann. Die erste Zeile sollte auf den Interpreter verweisen:

#!/usr/bin/env bash

Für die meisten Teams ist das bessere langfristige Muster nicht, .git/hooks auf jedem Laptop manuell zu bearbeiten. Speichern Sie Hook-Skripte im Repository und konfigurieren Sie dann Git, um dieses Verzeichnis zu verwenden:

git config core.hooksPath .githooks
mkdir -p .githooks

Jetzt kann ein Hook unter .githooks/pre-commit wie normaler Projektcode committet und überprüft werden. Jeder Entwickler benötigt immer noch die core.hooksPath-Einstellung, aber die Einrichtung kann zu einem Bootstrap-Skript hinzugefügt oder im Onboarding dokumentiert werden.

Ein Nützlicher Pre-Commit-Hook

Ein guter pre-commit-Hook sollte schnell und fokussiert sein. Wenn er bei jedem Commit zwei Minuten dauert, werden die Leute ihn mit git commit --no-verify umgehen, und der Hook wird zu Rauschen. Sparen Sie sich vollständige Testsuiten für CI auf, es sei denn, das Projekt ist klein genug, dass sie wirklich schnell sind.

Hier ist ein praktischer Shell-Hook, der nur gestagte Dateien überprüft. Diese Unterscheidung ist wichtig. Möglicherweise haben Sie unfertige Arbeit in Ihrem Arbeitsverzeichnis, die Sie noch nicht testen möchten. Der Commit sollte nach dem beurteilt werden, was gestaged ist.

Erstellen Sie .githooks/pre-commit:

#!/usr/bin/env bash
set -u

changed_files=$(git diff --cached --name-only --diff-filter=ACMR)

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

if git diff --cached --check; then
  :
else
  echo "Beheben Sie Whitespace-Fehler vor dem Commit."
  exit 1
fi

secret_matches=$(git diff --cached --name-only --diff-filter=ACMR | xargs grep -nE 'AKIA[0-9A-Z]{16}|BEGIN RSA PRIVATE KEY' 2>/dev/null || true)
if [ -n "$secret_matches" ]; then
  echo "Mögliches Secret in gestagten Dateien gefunden:"
  echo "$secret_matches"
  exit 1
fi

python_files=$(printf '%s\n' "$changed_files" | grep '\.py$' || true)
if [ -n "$python_files" ]; then
  printf '%s\n' "$python_files" | while IFS= read -r file; do
    [ -f "$file" ] || continue
    python3 -m py_compile "$file" || exit 1
  done
fi

exit 0

Dieser Hook macht drei bescheidene Dinge: Er lässt Git Whitespace-Fehler erkennen, überprüft gestagte Dateien auf ein paar offensichtliche Secret-Muster und kompiliert geänderte Python-Dateien. Er ist kein Ersatz für einen echten Secret-Scanner oder eine Testsuite. Er ist eine schnelle Stolperfalle.

Ein häufiger Fehler ist die Verwendung von grep gegen Dateinamen anstelle von Dateiinhalten. Dieses kaputte Muster prüft nur, ob der Pfad TODO enthält, nicht, ob die Datei es enthält:

git diff --cached --name-only | grep TODO

Wenn Sie TODO-Kommentare blockieren möchten, überprüfen Sie stattdessen den gestagten Diff:

if git diff --cached -U0 | grep -E '^\+.*TODO:'; then
  echo "Gestagte TODO-Kommentare gefunden."
  exit 1
fi

Aber auch hier ist Vorsicht geboten. Einige Teams verwenden TODO-Kommentare verantwortungsvoll. Jedes TODO zu blockieren, kann mehr nervig als hilfreich sein.

Commit-Nachrichten-Hooks

Ein commit-msg-Hook erhält den Pfad zur temporären Commit-Nachrichtendatei als erstes Argument. Das macht ihn nützlich für Regeln wie "Jeder Commit muss mit einer Ticket-ID beginnen" oder "Verwende Conventional Commits."

Ein kleines Beispiel:

#!/usr/bin/env bash
set -u

message_file="$1"
first_line=$(head -n 1 "$message_file")

if printf '%s' "$first_line" | grep -Eq '^(feat|fix|docs|test|refactor|chore)(\(.+\))?: .+'; then
  exit 0
fi

echo "Commit-Nachricht sollte so aussehen: fix(api): handle empty token"
exit 1

Dies ist hilfreich, wenn Release-Notes oder Changelogs aus Commits generiert werden. Es ist weniger hilfreich, wenn Ihr Team Squash-Merges durchführt und PR-Titel ohnehin umschreibt. Passen Sie den Hook an den Workflow an, den Sie tatsächlich verwenden.

Post-Merge-Hooks

Ein post-merge-Hook eignet sich am besten für lokale Aufräumarbeiten, nachdem sich Ihr Arbeitsverzeichnis geändert hat. Das klassische Beispiel ist das Aktualisieren von Abhängigkeiten, nachdem sich eine Lockfile geändert hat.

#!/usr/bin/env bash
set -u

previous_head="HEAD@{1}"

if git diff --name-only "$previous_head" HEAD | grep -Eq '(^package-lock\.json$|^pnpm-lock\.yaml$|^yarn\.lock$)'; then
  if command -v npm >/dev/null 2>&1 && [ -f package-lock.json ]; then
    echo "Lockfile geändert; führe npm install aus."
    npm install
  fi
fi

if git diff --name-only "$previous_head" HEAD | grep -q '^\.gitmodules$'; then
  echo "Submodule-Konfiguration geändert; synchronisiere Submodule."
  git submodule sync --recursive
  git submodule update --init --recursive
fi

Dieser Hook sollte keine überraschenden Änderungen vornehmen. Wenn er Abhängigkeiten installiert, geben Sie aus, was er tut. Wenn die Installation fehlschlägt, teilen Sie dem Entwickler mit, wie er sich erholen kann. Ein Hook, der stillschweigend den Arbeitsbaum ändert, ist schwer zu vertrauen.

Hooks Teilen, Ohne Ein Chaos zu Verursachen

Es gibt drei gängige Methoden, um Hooks zu teilen.

Die einfachste ist core.hooksPath, bei der das Repository .githooks/ enthält und die Einrichtung Git anweist, es zu verwenden. Dies ist transparent und erfordert keinen weiteren Paketmanager.

JavaScript-Projekte verwenden oft Husky, weil es in npm-, pnpm- oder yarn-Installationsabläufe integriert ist. Das kann eine gute Wahl sein, wenn jeder Mitwirkende bereits die Node-Toolchain verwendet.

Viele gemischte Sprachteams verwenden das pre-commit-Framework. Es installiert und führt Hooks aus, die in .pre-commit-config.yaml definiert sind, mit festgelegten Versionen für Tools wie Formatierer, Linter und Dateiprüfungen. Es fügt ein weiteres Tool hinzu, löst aber das Problem "Wie installieren wir überall dieselben Hooks?" besser als eine Wiki-Seite.

Was ich vermeide, ist das manuelle Kopieren großer Skripte in .git/hooks. Niemand überprüft sie, niemand weiß, welche Version installiert ist, und das Debuggen wird zu persönlicher Archäologie.

Debuggen von Hooks

Wenn ein Hook nicht läuft, überprüfen Sie diese in der Reihenfolge:

git config --get core.hooksPath
ls -l .git/hooks .githooks 2>/dev/null

Wenn core.hooksPath gesetzt ist, ignoriert Git .git/hooks und verwendet das konfigurierte Verzeichnis. Wenn die Hook-Datei unter macOS oder Linux nicht ausführbar ist, führt Git sie nicht aus:

chmod +x .githooks/pre-commit

Wenn ein Hook läuft, aber auf mysteriöse Weise fehlschlägt, fügen Sie temporäre Ablaufverfolgung hinzu:

set -x
pwd
env | sort

Hooks werden bei normaler Git-Nutzung vom Repository-Stammverzeichnis aus ausgeführt, aber GUI-Clients und IDEs können Pfad- oder Umgebungsunterschiede aufdecken. Verwenden Sie command -v toolname innerhalb des Hooks, bevor Sie annehmen, dass ein Linter oder Paketmanager verfügbar ist.

Denken Sie auch an den Bypass-Schalter:

git commit --no-verify

Dies ist an sich kein Sicherheitsloch; es ist, wie Git funktioniert. Es ist ein weiterer Grund, warum ernsthafte Durchsetzung zu CI oder geschützten Branch-Regeln gehört.

Eine Sinnvolle Hook-Richtlinie

Verwenden Sie Hooks für Prüfungen, die schnell, deterministisch und leicht zu erklären sind. Das Formatieren gestagter Dateien, das Erkennen von Whitespace-Fehlern, das Validieren von Commit-Nachrichten und das Erinnern von Entwicklern an die Installation von Abhängigkeiten sind gute Kandidaten. Vermeiden Sie Hooks, die Netzwerkzugriff erfordern, lange dauern oder von fragwürdigem lokalem Zustand abhängen.

Wenn ein Hook einen Commit blockiert, sollte seine Nachricht genau sagen, was fehlgeschlagen ist und wie es zu beheben ist. "Hook fehlgeschlagen" ist nicht genug. Ein Entwickler mitten in einem Merge oder einem Produktions-Hotfix benötigt einen klaren nächsten Befehl.

Client-seitige Git-Hooks funktionieren am besten, wenn sie sich wie eine hilfreiche Leitplanke anfühlen, nicht wie eine lokale Bürokratie. Halten Sie sie klein, versionieren Sie sie und behalten Sie die endgültige Autorität in CI.

Halten Sie Hooks Während Notfällen Freundlich

Hooks sollten während der normalen Arbeit helfen, ohne jemanden während einer dringenden Reparatur zu behindern. Das bedeutet, dass jeder blockierende Hook eine klare Fehlermeldung und eine realistische Ausweichmöglichkeit benötigt. Git bietet bereits --no-verify für Commit- und Push-Hooks, aber Ihr Team sollte dennoch entscheiden, wann das Umgehen akzeptabel ist. Ein Produktions-Hotfix unterscheidet sich vom Überspringen der Formatierung, weil ein Entwickler es eilig hat.

Eine gute Hook-Nachricht sagt, was fehlgeschlagen ist, wo es fehlgeschlagen ist und was als nächstes ausgeführt werden soll:

echo "ESLint ist bei gestagten JavaScript-Dateien fehlgeschlagen."
echo "Führen Sie aus: npm run lint -- --fix"
exit 1

Eine schlechte Nachricht sagt nur fehlgeschlagen oder gibt Seiten von Tool-Ausgaben ohne Kontext aus. Die Leute lernen, diese Art von Hook zu ignorieren.

Wenn der Hook Dateien ändert, seien Sie besonders vorsichtig. Formatierer können in pre-commit nützlich sein, aber sie können auch Verwirrung stiften, wenn sie ungestagte Teile einer Datei ändern. Viele Teams ziehen es vor, die Formatierung im Hook zu überprüfen und den Entwickler den Formatierer manuell ausführen zu lassen. Andere verwenden Tools, die nur gestagte Hünchen formatieren. Wählen Sie ein Verhalten und dokumentieren Sie es im Repository, nicht in einem Chat-Thread, der verschwindet.

Für Teams: Überprüfen Sie Hook-Änderungen wie Anwendungscode. Ein Hook kann jeden Commit verlangsamen, Umgebungsdetails in Protokolle durchsickern lassen oder Mitwirkende unter Windows beschädigen, wenn er Bash-only-Verhalten voraussetzt. Wenn Ihr Projekt Windows-Mitwirkende hat, testen Sie Hooks in Git Bash oder verwenden Sie einen plattformübergreifenden Hook-Runner. Wenn Ihr Projekt Container oder Dev-Shells hat, erwägen Sie, Hooks in derselben Umgebung wie die App auszuführen, damit alle dieselben Tool-Versionen verwenden.

Die besten Hooks sind fast unsichtbar, wenn alles in Ordnung ist, und sehr spezifisch, wenn etwas falsch ist. Das ist der Standard, den Sie anstreben sollten.

Versionieren Sie Hooks Wie Produktcode

Ein Hook-Skript wird Teil der Entwicklererfahrung. Wenn es kaputt geht, spürt es jeder Mitwirkende. Halten Sie die Skripte klein, benennen Sie Hilfsfunktionen klar und vermeiden Sie clevere Shell-Tricks, wenn ein einfacher Befehl ausreicht. Wenn ein Hook über einen oder zwei Bildschirme hinauswächst, verschieben Sie die eigentliche Logik in ein getestetes Projektskript und lassen Sie den Hook dieses Skript aufrufen.

Zum Beispiel, anstatt eine lange Lint-Routine in .githooks/pre-commit einzubetten, rufen Sie auf:

./scripts/check-staged-files.sh

Dieses Skript kann von Entwicklern, Hooks und CI ausgeführt werden. Es bedeutet auch, dass ein Entwickler den Fehler reproduzieren kann, ohne so zu tun, als würde er einen Commit machen. Reproduzierbarkeit ist der Unterschied zwischen einem hilfreichen Hook und einem mysteriösen lokalen Hindernis.

Legen Sie Tool-Versionen fest, wo Sie können. Ein Hook, der das aufruft, was auch immer black, eslint oder prettier gerade zuerst im PATH ist, kann sich auf verschiedenen Rechnern unterschiedlich verhalten. Projektlokale Abhängigkeiten, Lockfiles, Container oder Versionsmanager machen die Hook-Ausgabe vorhersagbarer.

Halten Sie Hooks schließlich auf das Repository beschränkt. Globale Hooks klingen praktisch, aber sie überraschen Sie oft Monate später, wenn ein nicht verwandtes Repository aufgrund einer alten persönlichen Regel fehlschlägt. Verwenden Sie globale Hooks nur für wirklich persönliche Vorlieben, nicht für Teamrichtlinien.

Eine letzte praktische Regel: Lassen Sie Hooks niemals der einzige Ort sein, an dem ein Befehl existiert. Wenn der Hook gestagte Python-Dateien überprüft, halten Sie diesen Befehl auch in einem Skript oder Task-Runner. Entwickler sollten in der Lage sein, dieselbe Prüfung absichtlich auszuführen, bevor Git sie unterbricht.

Für Open-Source-Projekte: Gehen Sie davon aus, dass Mitwirkende möglicherweise noch nicht Ihre vollständige Toolchain installiert haben. Ein Hook, der mit einer freundlichen Setup-Nachricht fehlschlägt, ist in Ordnung. Ein Hook, der einen Stacktrace von einer fehlenden lokalen Binärdatei wirft, fühlt sich kaputt an. Überprüfen Sie Voraussetzungen, bevor Sie schwerere Befehle ausführen, und weisen Sie die Leute auf den Setup-Befehl hin, der vom Projekt verwendet wird.

Denken Sie auch an partielle Commits. Viele erfahrene Entwickler stagen nur einen Teil einer Datei. Hooks, die die gesamte Datei formatieren, können versehentlich ungestagte Arbeit in den Commit ziehen. Wenn Ihr Team häufig partielle Commits verwendet, bevorzugen Sie Prüfungen, die den gestagten Diff lesen, oder Tools, die für gestagte Inhalte entwickelt wurden.

Wenn ein Hook ständig umgangen wird, behandeln Sie das als Feedback. Entweder ist die Prüfung zu langsam, die Fehlermeldung ist unklar oder die Regel gehört in CI statt in den lokalen Commit-Pfad. Beheben Sie die Reibung, anstatt Entwicklern die Schuld zu geben, dass sie den von Git bereitgestellten Bypass verwenden.