Les hooks git

Bonjour tout le monde, aujourd’hui je vais vous présenter les Git Hooks (ou crochets). Dans ce tuto, vous devez bien sûr avoir quelques connaissances sur ce qu’est Git (cela va sans dire). Si c’est la première fois que vous rencontrez ce mot, déjà, j’aimerai savoir où vous avez vécu (réponse acceptée : Mars), deuxièmement je vous invite à lire cette petite bible: Git Book

Après les semaines de lecture que je viens de vous procurer, passons aux choses sérieuses. Git nous propose de développer des scripts afin d’adapter le comportement de celui-ci et gérer plus facilement le versioning de votre projet. Si vous avez regardé le lien précédent, vous avez sûrement vu une rubrique “crochets git”, je garderai la structure de cet article en y ajoutant un exemple (DU CODE YOUPIIIIII).

Ces hooks s’installent dans .git/hooks. Si vous êtes curieux, vous verrez que des scripts y sont déjà présent. Ce sont des exemples qui vous permettent de voir comment utiliser et coder ces fameux hooks.

Il existe deux types de hook :

  • Les hooks client
  • Les hooks serveur

On regardera brièvement les hooks serveurs, la pratique se basera sur les hooks clients. Il faut savoir que ces hooks sont codés en bash, mais vous pouvez tout aussi utiliser votre langage de script préféré pour les développer, la seule contrainte est d’avoir l’environnement d’execution installée sur la machine disposant du hook. Cela peut être embêtant si vous développez un hook client pour tout une équipe en python et que personne ne dispose d’une installation de python sur ces machines :( Enfin bref, voyons ce que git nous propose :

Les hooks serveurs

Les hooks “pre-receive” et “post-receive”

Ce sont des hooks s’activant lors de la poussée de code d’un client. On peut s’en servir pour notifier les clients connectés ou les contributeurs du projet d’une nouvelle version du projet (ceci n’est qu’une idée).

Les hooks “update”

Ce sont des hooks qui s’execute à chaque mise à jour de branches, contrairement à pre-receive qui ne s’executera qu’une fois, update s’executera n-fois pour les n branches. (Bon là par contre j’ai aucune idée, peut être de notifier les colaborateurs de la branche ?)

C’est à peu près tout pour les hooks côtés serveurs =)

Les hooks clients

Ces hooks se référeront aux actions que le client git peut executer. On peut retrouver plusieurs catégories :

  • Les hooks liés au commit.
  • Les hooks liés au mail.
  • Les hooks liés au merge et rebase.

Les hooks liés au commit :

Grâce à ces hooks vous pourrez configurer les messages liés aux commit ou exécuter un script avant de valider votre commit.

Le hook “pre-commit”

Git exécutera ce script avant même que vous ayez écrit un message pour le commit en question. Vous pouvez donc vous servir de ce hook pour inspecter les fichiers indéxés ou modifié lors du commit (on y reviendra plus tard). Si le script renvoie une valeur non nulle, le commit est annulé. Dans le cas où votre équipe vous a fait une blague en installant un hook un peu bizarre vous pouvez éviter cette étape en forçant le commit avec la commande git commit –no-verify.

Le hook “prepare-commit-msg”

Ce hook vous permet de modifier le message par défaut des commits. (Servez-vous en pour spoiler le nouvel épisode de GOT)

Le hook “commit-msg”

Ce hook vous permet de spécifier des standards pour les messages des commits.

Le hook “post-commit”

Ce hook s’active en dernière phase du commit, vous pouvez vous en servir pour définir des notifications.

Ces hooks s’executent dans l’ordre dont je les ai énoncés (pre-commit->prepare-commit-msg->commit-msg->post commit). Si vous gérez une équipe et que vous comptez vous servir de ce genre de hook, vous devez savoir que chaque développeur devra installer votre hook sur sa machine, ainsi vous ne pouvez les obliger à s’en servir (snif, snif).

ASSEZ TERGIVERSÉ, PASSONS A LA PRATIQUE

On va partir du principe que vous avez une équipe qui code extrêmement bien mais qui vous fournit des messages de commit digne d’enfants de 4 ans. Vous décidez donc de mettre en place une convention de nommage pour votre équipe. On utilisera la convention de nommage du projet Karma (big up Hubert Sablonnière et Cyril Lakech)

  • Première question : Quel type de hook devez-vous utiliser ?
  • Deuxième question : Comment l’implémenter ?

Pour la première question, la réponse est simple, on va utiliser les hooks “commit-msg”.

Pour la seconde, on va prendre un peu plus de temps pour y répondre. Je vous invite à aller dans un dossier .git, et regarder le fichier commit-msg.sample :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message.  The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit.  The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".

# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\\(.*>\\).*$/Signed-off-by: \\1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.

test "" = "$(grep '^Signed-off-by: ' "$1" |
	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
	echo >&2 Duplicate Signed-off-by lines.
	exit 1
}

Le code en soit est pas très important, on va plutôt regarder les commentaires. On y apprend que notre petit script prend un argument en paramètre qui est le nom du fichier qui a le message du commit et ce script permet d’accepter le commit s’il renvoie zéro, non sinon.

On va donc créer un parseur qui va mettre en place la convention de nommage et rejeter les messages ne la respectant pas (faut pas déconner non plus !) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/sh
# Git commit-msg hook which will reject the commit if the commit message is not
# formated like :
# [feat|refactor|test|merge|docs|fix](<scope>): <subject>
# <new line>
# <body>
# <!-- Optionnal part -->
# <new line>
# <footer>
#
LINE_READ=0
COMMIT_HEADER_REGEXP="(feat|refactor|test|merge|docs|fix)\\(*\\)\\s?: .+"
COMMIT_BLANK_LINE_REGEXP="^$"
COMMIT_BODY_REGEXP="^[A-Za-z].*$"
while read line;
do
    LINE_READ=$(($LINE_READ + 1))
    if [[ "$LINE_READ" == "1" ]]; then
        if ! [[ $line =~ $COMMIT_HEADER_REGEXP ]]; then
            echo -e "Votre première ligne du message de votre commit ne respecte pas la convention de nommage énoncée lors de la formation Git. Elle devrait être de la forme : [feat|refactor|test|merge|docs|fix](): . Cordialement, le responsable qualité."
            echo -e "Ligne:  $line"
            exit 1;
        fi
    fi
    if [[ "$LINE_READ" == "2" ]]; then
        if ! [[ $line =~ $COMMIT_BLANK_LINE_REGEXP ]]; then
            echo -e "Vous avez oublié de sauter une ligne entre le body et la première ligne de votre message de commit. Bonne chance pour le prochain message. Cordialement, le responsable qualité."
            echo -e "Ligne: $line"
            exit 1;
        fi
    fi
    if [[ "$LINE_READ" == "3" ]]; then
        if ! [[ $line =~ $COMMIT_BODY_REGEXP ]]; then
            echo -e "Vous devez insérer au moins une ligne expliquant comment vous avez développé la nouvelle fonctionnalité. Cordialement, le responsable qualité."
            echo "Ligne: $line"
            exit 1
        fi
    fi
done < $1

On va lire ligne par ligne le fichier spécifié en argument (boucle while). Pour la première ligne, on vérifie qu’elle respecte le format de la variable COMMIT_HEADER_REGEXP, si ce n’est pas le cas, on affiche un message à l’utilisateur et on renvoie 1 (donc rejet du commit). On vérifie qu’une ligne est sautée avant le body puis on vérifie que le body contient au moins une lettre.

Voilà, un petit exemple d’usage des hooks. Vous pouvez bien sûr laisser libre court à votre imagination pour personnaliser git à vos souhaits !!

J’espère que ce bref article vous a donné de quoi vous amuser ! De mon côté j’ai un repo sur bitbucket incluant tous mes git hooks, si jamais vous voulez y jeter un coup d’oeil, c’est ici : git hooks

En attendant le prochain article, codez bien !