git log : qui suis-je ? D’où viens-je ? Où vais-je ?
Par Delicious Insights • Publié le 18 juin 2019

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 ?

  1. Afficher un historique pour se resituer
    1. Limiter la profondeur/le nombre de commits affichés
    2. Limiter le suivi à certains fichiers et répertoires
    3. Voir les modifications introduites par chaque commit
  2. Voir l’historique récent des branches
    1. cibler une branche dédiée
    2. cibler un motif de branche (feat/*)
  3. Vérifier le travail terminé
    1. Voir les fusions effectuées
    2. Lister les commits entre 2 versions
  4. Rechercher
    1. Dans les métadonnées du commit : dates, auteur, message…
    2. Dans les modifications introduites par les commits :
      1. les ajouts et suppressions du texte recherché
      2. les changements survenus autour d’un texte connu (sur la même ligne)
    3. Dans un fichier précis, les évolutions d'un fragment (typiquement le corps d’une fonction/méthode)
  5. 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 :

  1. le suivi par défaut des renommages des fichiers à travers l’historique ;
  2. 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 🤩 ?

Log arborescent avec commits mono-ligne et infos utiles (auteur, date, branches, HEAD)

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 ;

Log des 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.

Log de la totalité : branches locales, distantes, tags, stashes

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*'.

Log des branches locales filtrées par motif de nom

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

Afficher les commits de fusion uniquement

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

Afficher les commits de la version 0.0.1 à 1.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'

Exemple d’affichage des commits des 21 dernières heures

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'

Log filtré par auteur

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.

Commits groupés par auteur

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

Recherche dans les messages de commit

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 :

4 commits de modifications du fichier `my-module.js`

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.

Résultat du `log -S` : 1 commit

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 : \.).

Résultat du `log -G` : 4 commits

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'

Résultat du grep après le log : on liste uniquement les lignes voulues

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 a une fonction `logUppercased` qui commence à la ligne 5 et fini à la ligne 11

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…

Résultat du log : on trace l’évolution du bloc à travers les commits

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

Stats des modifications par fichier des 11 derniers commits de Maxime

À titre de comparaison voici ce qu’on aurait comme résultat unifié en passant par :

git diff --author=Maxime --stat HEAD~10

Stats unifiées des modifications par fichier pour les 11 derniers commits de Maxime

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.

Envie d’en savoir plus et de maitriser Git, venir découvrir notre formation Git Total.