Wie ich lernte, nicht mehr zu grübeln und Git Hooks zu lieben

Avatar of Darrell Huffman
Darrell Huffman am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Die Vorzüge von Git als Versionskontrollsystem sind schwer zu bestreiten, aber während Git hervorragende Arbeit leistet, um die Commits zu verfolgen, die Sie und Ihre Teamkollegen in einem Repository vorgenommen haben, garantiert es allein nicht die Qualität dieser Commits. Git hindert Sie nicht daran, Code mit Linting-Fehlern zu committen, noch hindert es Sie daran, Commit-Nachrichten zu schreiben, die keinerlei Informationen über die Natur der Commits selbst vermitteln, und es hindert Sie ganz gewiss nicht daran, schlecht formatierten Code zu committen.

Glücklicherweise können wir diesen Zustand mit Hilfe von Git Hooks mit nur wenigen Codezeilen beheben. In diesem Tutorial führe ich Sie durch die Implementierung von Git Hooks, die nur einen Commit zulassen, wenn dieser alle Bedingungen erfüllt, die für einen akzeptablen Commit festgelegt wurden. Wenn eine oder mehrere dieser Bedingungen nicht erfüllt sind, wird eine Fehlermeldung angezeigt, die Informationen darüber enthält, was getan werden muss, damit der Commit die Prüfungen besteht. Auf diese Weise können wir die Commit-Historien unserer Codebasen sauber und ordentlich halten und damit das Leben unserer Teamkollegen und nicht zuletzt unser eigenes zukünftiges Leben erheblich einfacher und angenehmer gestalten.

Als zusätzlichen Bonus sorgen wir dafür, dass Code, der alle Tests besteht, vor dem Commit formatiert wird. Was spricht gegen diesen Vorschlag? Nun, fangen wir an.

Voraussetzungen

Um diesem Tutorial folgen zu können, sollten Sie grundlegende Kenntnisse von Node.js, npm und Git haben. Wenn Sie noch nie von package.json gehört haben und git commit -m [message] wie Code für etwas Super-Duper-Geheimes klingt, dann empfehle ich Ihnen, diese und diese Website zu besuchen, bevor Sie weiterlesen.

Unser Aktionsplan

Zuerst installieren wir die Abhängigkeiten, die die Implementierung von Pre-Commit-Hooks zum Kinderspiel machen. Sobald wir unser Werkzeug haben, richten wir drei Prüfungen ein, die unser Commit bestehen muss, bevor er vorgenommen wird.

  • Der Code sollte frei von Linting-Fehlern sein.
  • Alle zugehörigen Unit-Tests sollten erfolgreich sein.
  • Die Commit-Nachricht sollte einem vordefinierten Format entsprechen.

Wenn der Commit dann alle oben genannten Prüfungen besteht, sollte der Code vor dem Commit formatiert werden. Wichtig ist, dass diese Prüfungen nur auf Dateien angewendet werden, die für den Commit vorgemerkt wurden. Das ist gut, denn wenn das nicht der Fall wäre, würde das Linting der gesamten Codebasis und die Ausführung aller Unit-Tests zeitaufwändig sein.

In diesem Tutorial implementieren wir die oben genannten Prüfungen für eine Front-End-Boilerplate, die TypeScript und dann Jest für die Unit-Tests und Prettier für die Code-Formatierung verwendet. Das Verfahren zur Implementierung von Pre-Commit-Hooks ist unabhängig von Ihrem Stack gleich, also fühlen Sie sich keineswegs gezwungen, auf den TypeScript-Zug aufzuspringen, nur weil ich ihn fahre; und wenn Sie Mocha gegenüber Jest bevorzugen, führen Sie Ihre Unit-Tests mit Mocha durch.

Installation der Abhängigkeiten

Zuerst installieren wir Husky, das Paket, das uns erlaubt, vor dem Commit beliebige Prüfungen durchzuführen. Führen Sie im Stammverzeichnis Ihres Projekts aus:

npm i husky --save-dev

Wie bereits besprochen, wollen wir die Prüfungen jedoch nur auf Dateien anwenden, die für den Commit vorgemerkt wurden, und damit dies möglich ist, müssen wir ein weiteres Paket installieren, nämlich lint-staged.

npm i lint-staged --save-dev

Zu guter Letzt installieren wir commitlint, das uns erlaubt, ein bestimmtes Format für unsere Commit-Nachrichten zu erzwingen. Ich habe mich für eines ihrer vorgefertigten Formate entschieden, nämlich das konventionelle, da ich denke, dass es Commit-Nachrichten fördert, die einfach und prägnant sind. Mehr dazu können Sie hier lesen.

npm install @commitlint/{config-conventional,cli} --save-dev

## If you are on a device that is running windows
npm install @commitlint/config-conventional @commitlint/cli --save-dev

Nachdem die commitlint-Pakete installiert sind, müssen Sie eine Konfiguration erstellen, die commitlint mitteilt, das konventionelle Format zu verwenden. Dies können Sie über Ihr Terminal mit dem folgenden Befehl tun:

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

Großartig! Jetzt können wir zum spaßigen Teil übergehen, nämlich der Implementierung unserer Prüfungen!

Implementierung unserer Pre-Commit-Hooks

Unten sehen Sie eine Übersicht über die Skripte, die ich in der package.json meines Boilerplate-Projekts habe. Wir werden zwei dieser Skripte direkt vor einem Commit ausführen, nämlich die Skripte lint und prettier. Sie fragen sich wahrscheinlich, warum wir das test-Skript nicht auch ausführen, da wir eine Prüfung implementieren werden, die sicherstellt, dass alle zugehörigen Unit-Tests erfolgreich sind. Die Antwort ist, dass Sie bei Jest etwas spezifischer sein müssen, wenn Sie nicht möchten, dass alle Unit-Tests ausgeführt werden, wenn ein Commit vorgenommen wird.

"scripts": {
  "start": "webpack-dev-server --config ./webpack.dev.js --mode development",
  "build": "webpack --config ./webpack.prod.js --mode production",
  "test": "jest",
  "lint": "tsc --noEmit",
  "prettier": "prettier --single-quote --print-width 80 "./**/*.{js,ts}" --write"
}

Wie Sie dem untenstehenden Code entnehmen können, den wir der package.json-Datei hinzugefügt haben, ist die Erstellung der Pre-Commit-Hooks für die Lint- und Prettier-Skripte nicht komplizierter als die Anweisung an Husky, dass vor einem Commit lint-staged ausgeführt werden muss. Dann weisen Sie lint-staged an, die Lint- und Prettier-Skripte auf allen vorgemerkten JavaScript- und TypeScript-Dateien auszuführen, und das war's!

"scripts": {
  "start": "webpack-dev-server --config ./webpack.dev.js --mode development",
  "build": "webpack --config ./webpack.prod.js --mode production",
  "test": "jest",
  "lint": "tsc --noEmit",
  "prettier": "prettier --single-quote --print-width 80 "./**/*.{js,ts}" --write"
},
"husky": {
  "hooks": {
    "pre-commit": "lint-staged"
  }
},
"lint-staged": {
  "./**/*.{ts}": [
    "npm run lint",
    "npm run prettier"
  ]
}

Wenn Sie zu diesem Zeitpunkt den TypeScript-Compiler verärgern, indem Sie einen String an eine Funktion übergeben, die eine Zahl erwartet, und dann versuchen, diesen Code zu committen, stoppt unsere Lint-Prüfung Ihren Commit und informiert Sie über den Fehler und wo Sie ihn finden können. Auf diese Weise können Sie Ihre Fehler korrigieren, und obwohl ich denke, dass das an sich schon ziemlich mächtig ist, sind wir noch nicht fertig!

Durch das Hinzufügen von "jest --bail --coverage --findRelatedTests" zu unserer Konfiguration für lint-staged stellen wir auch sicher, dass der Commit nicht vorgenommen wird, wenn verwandte Unit-Tests fehlschlagen. Gepaart mit der Lint-Prüfung ist dies das Code-Äquivalent zum Tragen von zwei Sicherheitsgurten, während man defekte Dachziegel repariert.

Was ist mit der Sicherstellung, dass alle Commit-Nachrichten dem konventionellen Commitlint-Format entsprechen? Commit-Nachrichten sind keine Dateien, daher können wir sie nicht mit lint-staged behandeln, da lint-staged seine Magie nur auf vorgemerkte Dateien anwendet. Stattdessen müssen wir zu unserer Konfiguration für Husky zurückkehren und einen weiteren Hook hinzufügen. In diesem Fall wird unsere package.json wie folgt aussehen:

"scripts": {
  "start": "webpack-dev-server --config ./webpack.dev.js --mode development",
  "build": "webpack --config ./webpack.prod.js --mode production",
  "test": "jest",
  "lint": "tsc --noEmit",
  "prettier": "prettier --single-quote --print-width 80 "./**/*.{js,ts}" --write"
},
"husky": {
  "hooks": {
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",  //Our new hook!
    "pre-commit": "lint-staged"
  }
},
"lint-staged": {
  "./**/*.{ts}": [
    "npm run lint",
    "jest --bail --coverage --findRelatedTests", 
    "npm run prettier"
  ]
}

Wenn Ihre Commit-Nachricht nicht dem konventionellen Commitlint-Format folgt, können Sie Ihren Commit nicht durchführen: Auf Wiedersehen, schlecht formatierte und obskure Commit-Nachrichten!

Wenn Sie Ihr Haus in Ordnung bringen und Code schreiben, der sowohl die Linting- als auch die Unit-Test-Prüfungen besteht und Ihre Commit-Nachricht ordnungsgemäß formatiert ist, führt lint-staged das Prettier-Skript auf den vorgemerkten Dateien aus, bevor der Commit vorgenommen wird, was sich wie das i-Tüpfelchen anfühlt. Zu diesem Zeitpunkt denke ich, dass wir uns ziemlich gut fühlen können; sogar ein wenig eingebildet.

Die Implementierung von Pre-Commit-Hooks ist nicht schwieriger als das, aber die Vorteile sind enorm. Obwohl ich immer skeptisch bin, meinem Workflow einen weiteren Schritt hinzuzufügen, haben mir Pre-Commit-Hooks eine Welt von Ärger erspart, und ich würde meine Commits nie wieder im Dunkeln machen, wenn ich dieses Tutorial mit einer etwas pseudo-poetischen Note beenden darf.