octocat

Qu’est-ce qui est le plus désagréable ?

  1. Relire une Pull Request principalement composée de changements d’indentation et de formattage ?
  2. Pousser son code et s’apercevoir après quelques minutes que les tests unitaires ont échoué sur la CI ?

Grâce aux Git Hooks, ne rencontrez plus jamais ces problèmes.

Les avantages de cette solution :

  • Git est a priori déjà installé sur votre projet, il n’y a donc pas de nouvel outil ou dépendance à ajouter.
  • Ce système fonctionne quels que soient les langages utilisés sur votre projet (tant qu’ils disposent d’outils en ligne de commande)

Les Git Hooks

Les hooks de Git permettent d’exécuter automatiquement des scripts sur certaines actions Git. Par exemple : avant un commit, après un commit, avant un push, avant un rebase… Des exemples sont créés lors de l’initialisation d’un repo Git, dans le dossier .git/hooks, et vous pouvez aussi trouver la liste complète ici : https://git-scm.com/docs/githooks.

Nous allons nous en servir pour faire tourner automatiquement les formatteurs avant chaque commit, puis linter et tester le code avant chaque push. Prenons l’exemple d’un projet monorepo avec un frontend en React et un backend en Go.

Formatter le code avant chaque commit

Commençons par créer un fichier pre-commit dans le dossier .git/hooks

#!/bin/bash
echo "Pre commit hook!"

## This will retrieve all of the .go files that have been changed since the last commit
STAGED_GO_FILES=$(git diff --cached --diff-filter=AM --name-only -- '*.go')

echo "Formatting backend files..."
for file in $STAGED_GO_FILES; do
gofmt -l -w -s "$file"
git add "$file"
done

## This will retrieve all of the frontend files that have been changed since the last commit
STAGED_FRONT_FILES=$(git diff --cached --diff-filter=AM --name-only -- '*.ts' '*.tsx' '*.css' '*.js')

echo "Formatting frontend files"
for file in $STAGED_FRONT_FILES; do
./webapp/node_modules/.bin/prettier --write "$file"
git add "$file"
done

Il doit être exécutable, on va donc modifier ses permissions d’accès avec la commande chmod +x pre-commit. Pour tester nous pouvons essayer de casser le formattage d’un fichier de notre projet, lors du commit le formatteur devrait le remettre en place.

Exécuter le formatteur avant le commit nous permet de garantir que tout code modifié respecte les règles du projet.

Linter et jouer les tests unitaires avant un git push

Créer un fichier pre-push dans le dossier .git/hooks

#!/bin/bash
echo "Pre-push hook!"

cd back || exit 1

# Run backend unit tests
echo "Running backend unit tests"
go test -short ./...
status=$?
if test $status -eq 0; then
  echo "backend test success"
else
  echo "backend test failed"
  exit 1
fi

# Run backend linter
if ! command -v golangci-lint &>/dev/null; then
  echo "Linter golangci-lint not found in PATH"
  exit 1
else
  echo "Running Go linter"
  golangci-lint run -E gosec -E gofmt -E gocognit
  status=$?
  if test $status -eq 0; then
    echo "Golang lint success"
  else
    echo "Golang lint failed"
    exit 1
  fi
fi

# Run frontend linter
echo "Lint webapp"
cd ../webapp || exit 1
npm run lint
status=$?
if test $status -eq 0; then
  echo "npm lint success"
else
  echo "npm lint failed"
  exit 1
fi

Le lint et les tests unitaires sont un peu plus longs à exécuter, on ne le fait donc que sur un push. Cela nous permet néanmoins de vérifier l’absence d’erreurs dans le code avant même qu’il soit envoyé sur le dépôt Git.

En cas d’urgence, il est toujours possible de ne pas effectuer ces vérifications en ajoutant l’option --no-verify à la commande git.

On pourrait aussi ajouter les validations suivantes :

Partager les hooks

Le dossier .git ne peut pas être commité, pour partager les githooks nous allons donc créer un dossier .githooks, y copier les hooks créés ci-dessus, puis le committer.

Il suffira ensuite à chaque développeur d’executer la commande suivante pour que Git utilise les hooks présents dans le dossier .githooks :

git config core.hooksPath .githooks

Documentation