Hooks : automatisez et fiabilisez vos projets
Par Maxime Bréhin • Publié le 14 avril 2017
• 6 min
Au-delà des commandes et du cycle de gestion usuel des révisions, Git nous permet des traitements complémentaires à certaines commandes afin d’épauler les utilisateurs.
Cet article est un complément à la documentation officielle et à la page de manuel.
Le principe
L’objectif des hooks est simple : il s’agit de fournir des enrobages à certaines commandes pour permettre l’injection de scripts (Bash, Node.js, Perl, Python, PHP…). De cette manière nous pouvons automatiser et donc fiabiliser une partie du travail côté client (machine de l’utilisateur) mais aussi côté serveur.
- Ils sont “par défaut” présents dans chaque répertoire projet dans le sous-répertoire
.git/hooks
. - Leur nom est conventionnel et chaque fichier de script doit être exécutable (
chmod +x .git/hooks/…
). - Leur emplacement fait qu’ils peuvent être supprimés ou désactivés par l’utilisateur sur un poste local.
Ainsi les hooks locaux n’ont pas une vocation de contrôle, mais plutôt de soutien.
À cela s’ajoute le fait qu’un utilisateur peut demander à ce que certains hooks ne soient pas déclenchés, avec l’option --no-verify
(pre-commit
et commit-msg
seulement).
Git fournit à chaque initialisation de projet un ensemble de scripts de démonstration possédant l’extension .sample
(ex. : .git/hooks/pre-commit.sample
). Pour les activer, il vous suffit de retirer cette extension (ex. : .git/hooks/pre-commit
).
Bloquant … ou pas !
Un hook peut être bloquant, c’est-à-dire qu’il peut interrompre la commande qu’il enrobe.
Par convention, tout hook avant commande pre-[commande]
est bloquant. Comme décrit précédemment, certains sont cependant contournables en utilisant l’option --no-verify
: pre-commit
et commit-msg
.
Les seuls vraiment bloquants sont :
- Côté client :
prepare-commit-msg
,pre-rebase
,pre-apply-patch
,pre-push
,pre-auto-gc
- Côté serveur :
pre-receive
,update
Le code de sortie des scripts bloquants est analysé pour savoir si oui on non le traitement doit être interrompu. On ne réinvente pas la roue, on utilise les codes de sorties standards qu’on peut résumer ici à :
- 0 (zéro) : tout est OK, on ne bloque pas,
- ≥1 : erreur, on bloque le traitement.
Par exemple, si nous produisons une sortie exit 1
dans un script de pre-commit alors le commit ne sera pas effectué.
Voici un exemple concret d’utilisation :
Un script bloquant tout commit tant que
- des marqueurs de conflits persistent,
- des chaînes de caractères
TODO
ouFIXME
sont detectée.
#! /bin/bash
# Si vous rencontrez une erreur du type `declare: -A: invalid option`
# c’est qu’il vous faut mettre à jour votre version bash à v4.
# Pour Mac OS, regardez ici : http://clubmate.fi/upgrade-to-bash-4-in-mac-os-x/
# Hash utilisant la clé comme expression de recherche (Regex) et la valeur
# associée comme message d’erreur
declare -A PATTERNS;
PATTERNS['^[<>|=]{4,}']="Vous avez des marqueurs de conflits qui traînent";
PATTERNS['TODO|FIXME']="Vous avez des tâches non terminées (FIXME/TODO)";
# Déclare un tableau d’erreurs vide
declare -a errors;
# Boucle sur les noms de fichiers présents dans le `stage`/`index` et
# vérifie que leur contenu contient les chaînes listées dans PATTERNS.
# Filtre uniquement sur les fichiers ajoutés (A), copiés (C), modifiés (M).
for file in $(git diff --staged --name-only --diff-filter=ACM); do
for elem in ${!PATTERNS[*]} ; do
{ git show :0:"$file" | grep -Eq ${elem}; } || continue;
errors+=("${PATTERNS[${elem}]} in ${file}…");
done
done
# Affiche les erreurs dans la console.
# Utilise la synthèse vocale si disponible pour énoncer les messages.
author=$(git config --get user.name)
for error in "${errors[@]}"; do
echo -e "\[\033[1;31m\]${error}\[\033[0m\]"
# Seulement sur Mac OS : synthèse vocale
which -s say && say -v Samantha -r 250 "$author $error"
done
# S’il existe au moins une erreur, arrête la création du commit
if [ ${#errors[@]} -ne 0 ]; then
exit 1
fi
Côté client/machine locale
Il s’agit des hooks disponibles uniquement sur les machines des utilisateurs.
Ceux-ci ne sont pas partagés au sein d’un projet, et il n’existe malheureusement à l’heure actuelle aucune option de type hooks.shared=true
permettant ce comportement.
Deux alternatives permettent néanmoins de produire en partie ce comportement :
Initialisation du dépôt via un gabarit de projet
Lorsque vous initialisez votre dépôt projet, vous pouvez dire à Git d’utiliser un gabarit d’arborescence de projet type grâce à l’option --template=<template directory>
:
- Lors de la création du projet :
git init --template=<template directory>
- Lors du clone :
git clone --template=<template directory>
Cette approche vous permet de définir plusieurs gabarits et de charger celui qui vous intéresse à l’initialisation ou à la récupération d’un projet.
Utilisation d’un répertoire déporté de hooks
Depuis Git 2.9 on peut définir par projet ou au global un paramétre de configuration permettant de spécifier le chemin du répertoire contenant les hooks : core.hooksPath=<hooks directory>
.
Ainsi on peut gérer un projet de hooks centralisé et partagé et aussi gérer communément les hooks nécessaires à une équipe. On limite la maintenance et les erreurs, et on économise le temps que nous devions passer précedemment à copier/coller ceux-ci !
Les hooks disponibles sont les suivants :
- Autour des commits :
pre-commit
: avant la création du commit, avant même l’édition du message (ex. : linting, tests unitaires courts) ;prepare-commit-msg
: avant la création du commit, au moment où l’on va avoir la main sur l’édition du message (ex. : permet l’injection d’un message pré-calculé) ;commit-msg
: juste avant la création du commit, mais après l’édition du message (ex. : contrôle du contenu du message et réécriture à la volée) ;post-commit
: une fois le commit créé (ex. : notification) ;
- Autour de l’application d’un patch (hooks lancés autour de la commande
git am
) :applypatch-msg
: avant l’application du patch (vérification du message de patch) ;pre-applypatch
: après l’application du patch, mais avant la création du commit (ex. : validation du contenu du patch appliqué) ;post-applypatch
: une fois le patch appliqué et le commit créé (ex. : notification de l’auteur du patch) ;
- Autres opérations :
pre-rebase
: avant le démarrage de la commandegit rebase
(ex. : interdire le rebase de la branche master ou de commits déjà poussés sur le serveur) ;post-checkout
: après l’exécution d’ungit checkout
et ses appels implicites, par exemple lors d’unrebase
ou à la fin d’ungit clone
(ex. : paramétrage de l’environnement de travail associé, énoncé de la branche lors du déplacement sur une branche) ;post-merge
: après l’exécuton réussie degit merge
(ex. : vérification de la présence de marqueurs de conflits suite à une mauvaise fusion) ;post-rewrite
: invoqué par des commande de “ré-écriture” des révisions (git commit --amend
,git rebase
) ;pre-auto-gc
: lors de l’appel automatique au garbage collector (permet donc d’intervenir avant la suppression de certains objets et certaines références) ;pre-push
: intervient juste avant l’envoi de nouvelles révisions et/ou objets vers un serveur (ex. : lancement des tests unitaires et validation/invalidation de l’envoi).
Coté serveur
Nous n’avons pas toujours la main sur les hooks côté serveur.
En particulier si vous utilisez une solution graphique de serveur Git en mode SaaS (ex. : GitHub, GitLab, BitBucket) vous n’aurez pas la main sur votre serveur et devrez passer par des systèmes d’API ou de plugins.
Dans les autres cas de figures (vous avez la main sur le serveur), vous pouvez insérer vos propres scripts comme bon vous semble.
Les hooks disponibles sont les suivants :
pre-receive
: avant réception des références/objets (ex. : vérification des droits de l’utilisateurs sur le projet) ;update
: avant réception des références/objets par branche (ex. : vérification des droits par branche) ;post-update
: après réception des références par branche (ex. : notification d’utilisateurs tiers pour effectuer une revue de code) ;post-receive
: après réception de l’ensemble des références (ex. : lancement de l’intégration continues/tests d’intégration, déploiement automatique).
Préférez post-receive
, plus complet que post-update
: il possède les anciennes références et options liées au push
(notamment utilisé chez GitHub pour les notifications).
Schéma classique de hooks
Voici un schéma illustrant les hooks les plus fréquemment utilisés et leur cycle d’intervention entre client et serveur.
Exemple de workflow intégrant les hooks
Dans un contexte de création de révisions du code source d’un logiciel, nous souhaitons que certaines règles soient respectées :
- Qualité des commits :
- La syntaxe du code doit respecter des conventions (code linting) :
pre-commit
; - Les modifications ne doivent pas contenir de marqueurs de conflits (fusion mal traitée) :
pre-commit
; - Les tests unitaires ne doivent pas remonter d’erreur :
pre-commit
; - Les messages de commits doivent suivre une convention particulière :
prepare-commit-msg
,commit-msg
;
- La syntaxe du code doit respecter des conventions (code linting) :
- Respect des droits sur dépôts et branches : un utilisateur ne doit pas pouvoir publier ses révisions sur un dépôt ou une branche sur lesquels il n’a pas les droits :
update
; - Chaîne de build :
- Chaque branche, pour pouvoir être fusionnée, doit être soumise à :
- des validations automatiques (CI/Intégration Continue) :
update
; - des validations de non-régression (ex. : analyse du pourcentage minimum attendu de couverture de test) :
update
; - des validations manuelles (revue de code, tests fonctionnels) : non automatique, mais semblable à du
post-receive
;
- des validations automatiques (CI/Intégration Continue) :
- Le déploiement sur des environnements stables (“intégration”, “production”) doit être partiellement ou totalement automatisé :
post-update
oupost-receive
.
- Chaque branche, pour pouvoir être fusionnée, doit être soumise à :
En résumé…
Vous l’aurez compris, les hooks sont un mécanisme périphérique de Git vous ouvrant les portes de certaines automatisations.
De nombreux outils sont disponibles en “surcouche” et vous évitent de devoir gérer vos scripts manuellement (principalement côté serveur).
Soyez donc curieux et cherchez ce que proposent vos outils pour vous permettre de gagner en temps et en qualité sur vos projets.
En savoir plus…
Pour vous aider à aller plus loin, voici quelques liens vers des compléments de documentation et outils :
- Documentation généraliste : githooks.com ;
- Documentation sur le
pre-commit
: Learn pre-commit ; - Interfaces serveur :
- GitLab hooks et webhooks ;
- GitHub webhooks, repository webhooks, organisations webhooks (about page) ;
- Bitbucket webhooks ;
- Outils :
- Node.js precommit-hook, principalement pour de la qualité de code ;
- Node.js pre-commit, pour lancement des tests auto et plus… ;
- pre-commit.com, pour du pre-commit multi-langage ;
- validate-commit-msg par Kent C. Dodds et consorts.