git log : qui suis-je ? D’où viens-je ? Où vais-je ?
Par Maxime Bréhin • Publié le 18 juin 2019
• 9 min
Le log de Git est un outil formidable pour analyser l’historique des commits et resituer un contexte. Il nous permet aussi bien de suivre un projet dans sa globalité que dans ses détails : fonctionnalités, correctifs, fichiers et répertoires, auteurs, dates…
Même si de nombreuses interfaces graphiques proposent ce type d’analyse, aucune n’exploite le potentiel complet de la commande. C’est pourquoi cet article décrira exclusivement l’utilisation de git log
dans le terminal.
Mais avant de rentrer dans les descriptions techniques, concentrons-nous sur les cas d’utilisations.
Le log pour quoi faire ?
- Afficher un historique pour se resituer
- Limiter la profondeur/le nombre de commits affichés
- Limiter le suivi à certains fichiers et répertoires
- Voir les modifications introduites par chaque commit
- Voir l’historique récent des branches
- cibler une branche dédiée
- cibler un motif de branche (
feat/*
)
- Vérifier le travail terminé
- Voir les fusions effectuées
- Lister les commits entre 2 versions
- Rechercher
- Dans les métadonnées du commit : dates, auteur, message…
- Dans les modifications introduites par les commits :
- les ajouts et suppressions du texte recherché
- les changements survenus autour d’un texte connu (sur la même ligne)
- Dans un fichier précis, les évolutions d’un fragment (typiquement le corps d’une fonction/méthode)
- Obtenir des statistiques pour le projet
Cette liste n’est pas exhaustive et il existe de nombreuses règles d’affichage et de filtrage que nous n’explorerons pas ici.
Notez pour la suite que la plupart des options décrites sont cumulables.
Configuration : définir un comportement optimal par défaut
Avant de commencer à explorer les détails de la commande, il nous faut configurer deux choses importantes :
- le suivi par défaut des renommages des fichiers à travers l’historique ;
- un alias pour un log graphique, essentiel à une utilisation agréable dans le terminal.
Suivi des renommages
Vous aurez parfois besoin d’analyser les évolutions d’un fichier à travers votre historique et ce indépendamment des éventuels renommages ou déplacements qu’il aurait pu subir.
Ce suivi peut s’effectuer avec l’option git log --follow
, mais autant ne pas avoir à y penser et la configurer pour être systématique :
git config --global log.follow true
Un affichage agréable et pratique : git lg
L’affichage par défaut du log est compliqué, difficilement exploitable. C’est un frein à l’utilisation de Git en ligne de commande.
On va donc définir un alias git lg
qu’on utilisera la plupart du temps à la place du git log
standard et qui nous présentera un historique arborescent et synthétique, facile et agréable à lire :
git config --global alias.lg "log --graph --date=relative --pretty=tformat:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%an %ad)%Creset'"
Et voilà le résultat, plaisant non 🤩 ?
Alternative sans alias
Si vous préférez redéfinir le comportement d’affichage par défaut du log, vous pouvez renseigner les clés de configuration suivantes :
git config --global format.pretty 'tformat:%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%an %ad)%Creset'
git config --global log.date relative
Ça ne vous dispensera cependant pas de l’option --graph
. C’est pourquoi je pense que l’alias reste plus avantageux.
Maintenant qu’on a un log tout beau tout propre on va pouvoir s’attaquer aux aspects utiles de la commande.
Afficher un historique pour se resituer
Le comportement du log par défaut consiste à afficher l’historique de la référence courante depuis son tout premier commit. C’est loin d’être idéal, aussi voudra-t-on souvent restreindre cet affichage aux X derniers commits. Voici un exemple qui n’affiche que les 10 derniers commits depuis la position courante de HEAD :
git lg -10 # ou `-n 10` ou encore `--max-count=10`
Il est fréquent de ne vouloir afficher que les commits modifiant un ou plusieurs fichiers ou répertoires :
git lg mon-fichier mon-répertoire un-autre-répertoire
Ça marche même avec des globs 🤘 :
git lg *.html *.js
Et pour voir les contenus modifiés par chacun des commits :
git log -p # ou `--patch`
L’affichage vous paraîtra peut-être long/volumineux car il présente le contexte des modifications. Heureusement la commande log
bénéficie des options d’affichage de sa cousine diff
. Vous pouvez par exemple :
- n’afficher que les noms des fichiers :
--name-only
(--name-status
pour savoir s’il s’agit d’un·e ajout/modification/suppression). - afficher les modifications sur la même ligne (plutôt que sur deux lignes) avec l’option
--word-diff
ou--word-diff-regex=…
; - filtrer sur les modifications (
M
), ajouts (A
), suppressions (D
)… avec par exemple--diff-filter=AD
; - restreindre les lignes de contexte en ajoutant l’option
--unified=0
(0
indique ici qu’on ne souhaite pas de ligne de contexte) ;
Mon conseil pour l’affichage des patches/diffs : installez diff-so-fancy, cet utilitaire vous fournira un affichage concis et pratique.
Et les branches dans tout ça ?
Le comportement du log par défaut n’affiche que l’historique de la référence courante. On peut cependant cibler des branches particulières :
git lg ma-branche [mon-autre-branche] [encore-une-branche…]
On reste cependant dans un listing explicite, qui ne permet du reste pas de globbing. Pour afficher toute une catégorie de branches nous pouvons utiliser au choix :
- l’option
--branches
pour afficher les branches locales ;
- l’option
--all
pour afficher en plus les branches distantes, les tags, les stashes et d’une façon générale toute référence nommée connue du dépôt.
La première option nous permet d’aller plus loin et de choisir d’afficher les branches dont le nom répond au motif recherché : git lg --branches='*demo*'
.
Vérifier le travail terminé
Voir les fusions réalisées
Si vous souhaitez vérifier que vous avez bien fusionné certaines branches, et ce même après avoir supprimé leurs étiquettes, il vous suffit de faire :
git lg --merges
On constate dans cet exemple que les branches feat/first-demo
et feat/second-demo
on bien été fusionnées 🤝.
Lister un intervalle explicite de commits
Il arrive qu’on ait besoin d’afficher les commits entre deux endroits d’un historique. Voici quelques cas de figures concrets.
On veut par exemple afficher les commits d’une branche dev
depuis sa création à partir de master
pour connaître l’état actuel de ce chantier :
git lg master..dev
Note : on pourrait penser qu’un git lg dev
aurait fait le même travail. En l’occurence cela nous aurait affiché en plus tout l’historique de master
antérieur à la création de dev
, puisque master
cela fait bien partie aussi de l’historique de dev
.
On peut vouloir l’inverse de la situation précédente : les nouveaux commits sur master
depuis la création de dev
. Si par exemple master
a fusionné une branche d’une fonctionnalité qui nous est nécessaire pour finir notre travail sur dev
, on saurait alors qu’il est sans doute opportun de mettre à jour (rebaser) dev
pour quelle démarre depuis la dernière version de master
.
git lg dev..master
Si vous utilisez des tags pour « versionner » votre projet, vous voudrez parfois analyser les nouveautés d’une version à l’autre comme pour un changelog (préférez quand même un vrai changelog) :
git lg v1.0.0 v2.0.0
Rechercher dans le log
De nombreuses options de filtrage sont disponibles. Toutes ne sont pas utiles pour une utilisation régulière, aussi nous n’en verrons qu’une sélection.
Filtrer par date(s)
On peut vouloir chercher dans notre log le travail effectué depuis une date donnée, ou jusqu’à cette date.
Par exemple si je souhaite afficher les commits de ma branche courante pour les 24h écoulées je pourrais écrire :
git lg --since='1 day ago'
Un chef d’équipe qui souhaiterait connaître le travail partagé par son équipe sur les 7 jours écoulés ferait :
git lg --since='1 week ago' --all
La date peut être renseignée en anglais (ex. yesterday
, 5 minutes ago
, 1 month 2 weeks 3 days 1 hour 1 second ago
) ou au format ISO8601 (2019-06-07 18:30:00
).
On peut pousser cette analyse en renseignant un intervalle fermé avec l’option --until=…
.
Si cette fois notre chef d’équipe souhaite voir le travail effectué sur la semaine précédente il fera :
git lg --since='2 weeks ago' --until='1 week ago' --all
Des alias sont disponibles pour ces options since
=> after
, until
=> before
.
Filtrer par auteur de commit
Admettons qu’une de vos collègues soit partie en congés sans avoir pu vous indiquer les tâches qu’elle a terminé. Vous pourriez analyser son historique récent pour en déterminer une partie (ce qu’elle aura partagé/pushé) :
git lg -20 --all --author='Anna'
Attention, cette option recherche les auteurs dont le nom contient le motif donné. Donc si j’ai plusieurs Annas dans mon projet, je devrai préciser ma recherche.
Sur le même principe on peut rechercher parmi les committers :
git lg -20 --all --committer='Christophe'
On peut se retrouver dans une situation où l’auteur et le committer sont différents lorsqu’on fait du pair-programming et qu’on affecte un commit explicitement :
git commit --author='Maxime'
Il existe une alternative intéressante au log et qui regroupe les commits récents par auteur : git shortlog
.
Filtrer par message de commit
Si vous écrivez des messages de commits utiles (ce que j’espère 😅), vous pourriez vouloir retrouver certains d’entre eux. Admettons que vous utilisiez une syntaxe telle que celle de GitHub ou GitLab pour référencer vos tâches (issues). Nous pourrions ainsi lister l’ensemble des commits liés à la tâche 42 :
git log --branches --grep='#42'
Cette option prend en paramètre une expression rationnelle qui peut être une expression étendue si on ajoute l’option -E
. On peut également lui indiquer d’ignorer la casse avec l’option -i
. Voici un exemple plus complet :
git log --grep='(clos(e[sd]?|ing)|fix(e[sd]?|ing)?)\s*#42' -E -i
Vous aurez probablement remarqué que je n’utilise pas l’alias lg
mais log
. La raison est simple : je souhaite afficher les messages en entier puisque ma recherche s’effectue bien sur tout le message et pas seulement sa première ligne. Les afficher en entier me permet de retrouver le texte recherché.
Petite astuce supplémentaire : si votre affichage est paginé par un outil comme less
, vous pouvez taper /
(slash) pour rechercher dans le texte affiché.
Rechercher les modifications introduites par les commits
On se concentre ici sur les modifications de contenu. On a alors deux approches :
- soit on veut voir quels commits ont ajouté ou modifié/supprimé le texte recherché,
- soit on veut voir les changements survenus sur la même ligne qu’un texte connu.
Prenons un exemple concret : j’ai un fichier my-module.js
qui contient une fonction qui fait un console.log(…)
. Cet appel a été introduit à la création du module et plusieurs modifications ont été effectuées pour améliorer le message affiché.
Si je veux analyser les changements de la chaine console.log
je ne devrais trouver qu’un commit (sa création). Si je veux analyser l’évolution du message (ce qui est passé dans les parenthèses du console.log(…)
) je devrais trouver plusieurs commits.
Voici le log actuel de l’ensemble des commits touchant au fichier :
Rechercher les ajouts/suppressions
git lg -S 'texte recherché' my-module.js
Cette option -S
s’attend à un texte exact. On peut cependant étendre son comportement et rechercher un motif d’expression rationnelle avec une option complémentaire --pickaxe-regex
(difficile à mémoriser 😨).
Dans notre exemple, pour récupérer tous les commits d’ajout/suppression de console.log
je peux faire :
git lg -p -S 'console.log' my-module.js
On obtient ici un seul commit : celui de création du module.
Rechercher sur des modifications sur la même ligne que le motif ciblé
On peut vouloir lister l’ensemble des commits ayant introduit des changements sur les lignes contenants le motif désigné.
Par exemple si je veux voir quelles modifications ont été introduites dans le message affiché par mon console.log
au fil des commits. Je sais donc que mon console.log
est censé être toujours sur la ligne, je vais donc l’utiliser comme motif recherché :
git lg -p -G 'console\.log' my-module.js
Le résultat nous fournira aussi bien les changements environnants que les ajouts/suppressions.
Notez que cette option -G
considère que l’argument est une expression régulière et ce sans option complémentaire (d’où l’échappement du point : \.
).
Astuce complémentaire
Lors de l’affichage des contenus des commits avec l’option -p
/--patch
les options de filtrages -S
et -G
ne contraignent pas l’affichage aux lignes contenant les motifs recherchés, aussi il s’avère parfois difficile de retrouver les lignes souhaitées.
Une solution consiste à filtrer sur le résultat de la commande, par exemple :
git lg -10 -p --color -G 'console\.log' | grep 'console\.log'
Afficher les évolutions d’un bloc de code
Ça c’est mon option chouchoutte ! Elle nous permet de suivre l’évolution d’un bloc dans un fichier commit par commit. Imaginez que vous avez une méthode et que du jour au lendemain un bug apparaît. Comment analyser ?
git lg -L 1,99:chemin-du-fichier
Cette option recherche au sein d’un fichier unique. Le principe est simple : on définit l’intervalle à suivre à travers les commits et Git nous affichera les évolutions de ce bloc. Nous vous conseillons de vous limiter à une syntaxe d’intervalle de numéros de lignes séparés par une virgule (ligne de début, ligne de fin ; exemple : 3,99
). Remarquez qu’il s’agit des numéros de lignes dans la version la plus récente du fichier, et Git va ajuster automatiquement en remontant le fil des modifications, selon les évolutions environnantes du fichiers (décalage suite à des insertions au dessus, ajout/suppression des lignes dans le bloc).
Prenons l’exemple du fichier my-module.js
dont le contenu actuel est le suivant :
On souhaite analyser l’évolution de la fonction logUppercased
, donc entre les lignes 5 et 11, car un bug est apparu (la fonction n’affiche plus en majuscules) et nous souhaitons connaître l’origine du problème :
git lg -L 5,11:my-module.js
Et la magie opère…
Le résultat obtenu nous montre que le commit 58462c4 test(log-l): add test for 'logUppercased'
a remplacé la ligne d’affichage au profit d’une autre qui ne convertit pas en majuscules 😱. C’est donc une erreur dans le commit et non un développement raisonné, on peut corriger !
Un mot sur git blame
La commande blame
est bien souvent utilisée à des fins de réprimande lorsqu’une erreur apparaît dans un projet. Sachez que cette commande n’a pour but que d’afficher l’état actuel du fichier ligne à ligne. Elle ne précise que la dernière personne à avoir modifié chaque ligne, par exemple en retirant de l’espace en fin de ligne ou en réindentant. Elle n’indique en rien qu’une personne donnée est responsable de l’introduction de l’erreur.
Je pense que cette commande est à proscrire pour deux raisons :
- elle est utilisée dans une optique agressive ;
- le résultat qu’elle apporte est rarement celui attendu : préférez
git log
pour une analyse pointue !
Statistiques du projet
Le log bénéficie des mêmes options de statistiques que la commande diff
:
stat
: statistiques par fichier (proportion ajout/suppression) ;shortstat
: statistiques globales du commit en une ligne ;dirstat
: statistiques en % de modification par répertoire ;numstat
: équivalent àstat
mais avec un affichage brut à tabulations, idéal pour scripter le résultat ou l’exporter vers un autre tableur.
Notez que git diff
vous fournira des statistiques unifiées alors que git log
vous fournira les statistiques commit par commit. Il est donc très probable que vous vous orientiez plus vers git diff
pour vos analyses.
Les serveurs Git (GitHub, GitLab and co.) fournissent également des statistiques graphiques avancées et qui vont au-delà de ce qu’on peut obtenir en ligne de commande.
Voici un exemple : je souhaite avoir une vue globale du travail pour les 11 derniers commits de Maxime :
git lg -11 --author=Maxime --stat
À titre de comparaison voici ce qu’on aurait comme résultat unifié en passant par :
git diff --author=Maxime --stat HEAD~10
Le mot de la fin
Les commandes Git log
et status
sont les deux commandes que j’utilise le plus pour analyser mon travail en cours et passé. Le log est un moyen précieux de resituer un contexte de travail (à condition bien sûr que vos messages de commits soient utiles).
Il existe de très nombreuses options non listées ici et qu’on retrouve en partie en options des commandes diff
et reflog
. Je vous invite donc à rester curieux·se et à suivre d’éventuelles mise à jour de cet article.