1. Livres & vidéos
  2. Git
  3. Consultation et manipulation de l’historique
Extrait - Git Maîtrisez la gestion de vos versions (concepts, utilisation et cas pratiques) (5e édition)
Extraits du livre
Git Maîtrisez la gestion de vos versions (concepts, utilisation et cas pratiques) (5e édition) Revenir à la page d'achat du livre

Consultation et manipulation de l’historique

Lister les commits avec git log

L’une des fonctionnalités les plus importantes d’un VCS est de retrouver rapidement un certain nombre d’informations, par exemple les modifications qui ont été effectuées depuis une date précise ou encore les modifications en cours dans le répertoire de travail.

La commande git log va nous permettre d’afficher beaucoup d’informations sur les commits. Cette commande est très puissante et possède de nombreuses options. Il suffit de consulter l’aide de cette commande pour mesurer les possibilités qu’elle offre.

Par défaut, git log affiche la liste des commits du dépôt en ordre chronologique décroissant. La sortie peut être très importante (voire monstrueuse selon la taille du projet), car tous les commits effectués pendant toute la vie du dépôt sont affichés.

Pour tester les commandes permettant de naviguer dans un dépôt facilement sans devoir créer un dépôt et y ajouter beaucoup de contenu, il peut être opportun de cloner un dépôt accessible sur GitHub. La commande suivante va télécharger le dépôt du framework web Django et le placer dans le dossier django :

git clone https://github.com/django/django.git 

Cette commande ne va pas uniquement télécharger le code, mais tout le dépôt avec tout l’historique et donc avec tous les commits (plus de détails sur cette commande dans le chapitre Partager un dépôt, à la section Cloner un dépôt distant).

La commande affiche une sortie confirmant la bonne exécution du clone :

Cloning into 'django'... 
remote: Counting objects: 324353, done. 
remote: Compressing objects: 100% (4/4), done. 
remote: Total 324353 (delta 0), reused 0 (delta 0), pack-reused 324349 
Receiving objects: 100% (324353/324353), 138.51 MiB | 1.01 MiB/s, done. 
Resolving deltas: 100% (228976/228976), done. 
Checking connectivity... done. 
Checking out files: 100% (5199/5199), done. 

Cette sortie indique que tous les objets Git ont correctement été téléchargés et que les fichiers ont correctement été insérés dans le répertoire de travail.

Les exemples présents dans ce dépôt...

Afficher les différences de contenu

Il est intéressant d’être en mesure de récupérer des commits, mais parfois ce que le développeur cherche se trouve dans le contenu même des modifications ajoutées par le commit, dans le répertoire de travail ou encore dans l’index. Pour cela, il doit utiliser la commande git diff.

La commande git diff affiche une liste des ajouts et des suppressions de lignes entre deux commits ou alors entre les différentes zones (répertoire de travail, index).

Pour que les exemples prennent du sens, il faut modifier au moins un fichier du dépôt. Les données avec lesquelles le fichier sera modifié ne sont pas importantes, elles serviront juste à l’affichage des sorties.

Dans le cas du dépôt de Django, il est possible de modifier le fichier README.rst en y ajoutant une ligne à la fin, par exemple : Je teste la commande git diff.

Il faut ajouter ensuite cette modification à l’index.

git add README.rst 

Puis, il faut de nouveau modifier le fichier README.rst, en modifiant la dernière ligne, par exemple comme suit : Je teste la commande diff de git.

1. Différences en cours dans le répertoire

L’un des cas où git diff est très utilisé est celui où le développeur veut visualiser les modifications présentes dans son répertoire de travail par rapport à son index. Prendre connaissance de ces modifications lui permet de ne pas ajouter dans l’index celles qu’il ne souhaite pas. Pour afficher les différences entre le répertoire de travail et l’index, il faut utiliser la commande suivante :

git diff 

Cette commande affiche la sortie suivante :

diff --git a/README.rst b/README.rst 
index 6e9bc5f..9f4dcc9 100644 
--- a/README.rst 
+++ b/README.rst 
@@ -41,4 +41,4 @@ To run Django's test suite: 
 * Follow the instructions in the "Unit tests" section of 
   docs/internals/contributing/writing-code/unit-tests.txt,   
published online at https://docs.djangoproject.com/en/dev/internals 
/contributing/writing-code/unit-tests/#running-the-unit-tests  
-Je teste la commande...

Identifier l’auteur d’une ligne de code

Lorsqu’un développeur travaille sur un code, il peut parfois se demander lequel de ses collègues a retravaillé sur le code en question ou lequel a modifié en dernier une ligne particulière. Le fait de savoir qui a modifié une ligne permettra au développeur de savoir qui interroger pour avoir plus de détails concernant la partie sur laquelle il travaille.

Par exemple, il arrive très régulièrement de se demander quand, par qui et pourquoi une ligne de code a été ajoutée ou modifiée dans un fichier. Toutes ces informations sont centralisées dans le commit, la seule complication étant de retrouver le commit ayant modifié cette ligne particulière.

Pour utiliser git blame sur un fichier, il faut utiliser la syntaxe suivante :

git blame nom_fichier 

Testons cette commande sur le fichier README.rst de Django :

git blame README.rst 

Cette commande affiche la sortie suivante tronquée (les deux premières lignes sont affichées) :

b2cb66bf README   (Adrian Holovaty  2005-07-21 01:37:28 +0000  ==> Ligne 
226acf35 README   (Adrian Holovaty  2012-04-27 22:25:08 -0500  ==> Ligne 

Cette sortie est tronquée, car elle n’est pas très lisible. Les textes ==> Ligne remplacent en réalité...

Rechercher des commits avec le mode pick axe

Le mode pick axe n’est pas propre à une commande de Git, c’est en réalité un mode utilisable avec plusieurs commandes de Git en ajoutant un argument. Le mode pick axe va permettre de rechercher les commits en fonction des modifications qu’ils ajoutent. C’est-à-dire que l’option pick axe va permettre de savoir dans quels commits le contenu spécifié a été ajouté ou supprimé.

Le mode pick axe peut être utilisé avec git log, mais aussi avec git diff ou encore avec la commande git format-patch. Pour utiliser cette fonctionnalité, il faut ajouter l’argument -S suivi du mot ou de la chaîne à rechercher :

git log -S "chaîne à rechercher" 

Par exemple, pour rechercher les commits du projet Django ajoutant ou supprimant la chaîne "Web framework that", il faut utiliser la commande suivante :

git log -S "Web framework that" 

Cette commande trouve quatre commits qui ajoutent ou suppriment la chaîne recherchée.

Cette commande permet alors d’effectuer des recherches beaucoup plus précises, directement dans les lignes modifiées ou ajoutées. Il existe également le paramètre -G qui permet de rechercher les commits à partir de leurs modifications en fonction d’une expression régulière....

Supprimer les modifications du répertoire de travail

Il arrive régulièrement que lors d’un test ou d’un nouveau développement, le développeur souhaite revenir à l’état de la dernière version du dépôt (pointée par HEAD). Cela se produit fréquemment lorsque, pendant les tests, le développeur modifie de nombreux fichiers, en ajoute quelques-uns dans l’index et laisse ces modifications sans les commiter. À la fin, il se rend compte qu’aucune modification ne doit être commitée et décide donc de supprimer ses modifications en revenant à l’état de HEAD.

Pour cela, il doit utiliser la commande suivante :

git reset --hard HEAD 

Attention : cette commande supprime toutes les modifications du répertoire de travail et de l’index. Cette commande est irréversible, elle doit donc être utilisée avec beaucoup de prudence.

Lorsque aucune modification n’a été ajoutée à l’index ou qu’une modification doit être conservée dans l’index, il convient d’utiliser la commande git checkout qui permet d’appliquer la version de l’index sur les fichiers du répertoire de travail. 

La syntaxe à utiliser est la suivante :

git checkout --  fichiers 

Apparue avec la version 2.24 de Git, la commande...

Supprimer les modifications de l’index

Lorsque les modifications dans l’index ne présentent plus d’intérêt et doivent être supprimées (ou désindexées), il faut utiliser la commande suivante :

git reset HEAD 

Sans l’argument --hard, cette commande ne va pas modifier le répertoire de travail. Cette commande va juste remettre l’index dans le même état que la version la plus récente du dépôt. Les modifications présentes dans l’index seront supprimées de manière irréversible !

Revenir à un état antérieur

Plusieurs raisons peuvent nécessiter de se repositionner sur une version antérieure. L’une d’elles est de tester une version antérieure pour comparer certaines fonctions du logiciel. Il arrive également que les développeurs veuillent tester une ancienne version avant d’utiliser git bisect. La commande git bisect est un outil de recherche dichotomique dans le dépôt. Cette fonctionnalité est abordée dans le chapitre Les outils de Git, à la section Retrouver un commit erroné.

Pour revenir à un état antérieur en utilisant le hash du commit sur lequel le développeur veut se replacer, il faut utiliser la syntaxe suivante :

git reset hash_du_commit 

Par exemple, si le développeur désire revenir au projet tel qu’il l’était à la fin de l’année 2014, il recherche tout d’abord les commits effectués avant le 31 décembre 2014 :

git log --before="2014-12-31" -1 

Cette commande affiche la sortie suivante :

commit 013c2d8 
Author: Russell Keith-Magee <russell@keith-magee.com> 
Date:   Wed Dec 31 13:21:32 2014 +0800 
 
    Renamed variables to avoid name collision with import of 
django.db.models. 

Puis il utilise ensuite la commande git reset de cette manière...

Modifier le dernier commit

Lorsqu’on suit les versions d’un logiciel, il est normalement rare de vouloir modifier l’historique, mais il arrive malgré tout des moments où un développeur se rend compte qu’il a commité un peu trop vite.

Pour pouvoir modifier le dernier commit, il faut tout d’abord satisfaire à une exigence : ne pas avoir pushé (envoyé) le commit sur un dépôt distant (pour plus de détails sur les dépôts distants, il convient de se référer au chapitre Partager un dépôt). En effet, si le commit cible a été pushé et que le développeur le modifie, son identifiant (sous forme de hash) sera différent de celui du dépôt distant et l’ancien commit aura disparu. Le serveur ne pourra pas savoir ce qui se sera réellement passé. En revanche, si le commit n’a pas été envoyé sur un dépôt distant, il est possible de corriger les erreurs sans problème.

Il arrive à chaque développeur, à un moment ou à un autre, de se précipiter pour commiter une modification. Plein de confiance, le développeur commite ses modifications avant de se rendre compte qu’il a laissé de nombreuses lignes de débogage dans le code.

Il veut donc modifier le contenu de son dernier commit ainsi...

Afficher un résumé des commits

Git propose la commande shortlog permettant d’afficher un résumé des commits. Cette commande peut être intéressante dans de nombreux cas :

  • afficher une liste des messages de commits pour une période donnée ;

  • afficher une liste des messages de commits pour des branches spécifiques ;

  • afficher une liste des messages de commits pour des tags spécifiques ;

  • afficher le nombre de commits par développeur.

Dans le cadre d’un projet répondant aux normes Git-Flow (méthode détaillée au chapitre Git-Flow : workflow d’entreprise) et SemVer (méthode détaillée au chapitre Les branches et les tags, à la section Les tags), il est par exemple possible de visualiser tous les correctifs effectués pour la version 1.12. Un cas d’utilisation récurrent de la commande git shortlog est la génération de changelog.

Pour les projets comprenant de nombreux commits, la commande git shortlog utilisée sans argument est trop verbeuse et manque d’intérêt. En revanche, dans le cas d’un petit projet (comme celui du chapitre Scénario d’équipe), la commande git shortlog peut être représentative des incréments au projet de chaque développeur :

Benoît (3): 
     C7...

Fusionner (squash) des commits

Dans ce livre, toutes les bonnes pratiques sont expliquées pour que vous puissiez créer des commits parfaits, atomiques et clairs. Dans la vraie vie, les choses sont bien souvent différentes. Après un commit principal, de nombreux commits peuvent être créés à la suite d’une review ou d’un oubli. Parfois, il peut être utile de fusionner ces commits en un seul afin d’avoir un historique plus propre.

Fusionner des commits peut également être utile lors de l’utilisation d’un rebase. En effet, lors d’un rebase, plusieurs commits peuvent être appliqués sur une nouvelle base, ce qui peut générer des conflits pour chaque commit. Dans ce cas-là, il peut être préférable de fusionner des commits si cette fusion fait sens dans l’historique.

Pour tester cette fonctionnalité, il faut tout d’abord créer un dossier de test et un dépôt Git :

mkdir git-squash-base  
cd git-squash-base  
git init 

Il faut ensuite créer un premier fichier et un commit initial :

echo "ligne 1" > file.txt  
git add file.txt  
git commit -m "Commit initial" 

Puis, il s’agit de créer une nouvelle branche utile aux tests :

git checkout -b feature/test-squash 

Il faut ensuite créer les commits qui seront fusionnés :

echo "ligne 2" >> file.txt  
git commit -am "Commit 2"  
echo "ligne 3" >> file.txt  
git commit -am "Commit 3"  
echo "ligne 4" >> file.txt  
git commit -am "Commit 4" 

Il est possible de vérifier si la mise en place est correcte en utilisant l’alias git ls (expliqué dans le chapitre Productivité maximale avec Git).

git ls 

Si tout s’est bien passé, les commits suivants s’affichent :

samueldauzon@MacBook-Pro-de-Samuel git-squash-test % git ls  
303770e [2 seconds ago] (HEAD -> feature/test-squash) Commit 4 
[Samuel Dauzon]  
a0ffd95 [2 seconds ago] Commit 3 [Samuel Dauzon]  
f83b699 [2 seconds ago] Commit 2 [Samuel Dauzon]  
6b6eb76 [9 minutes ago] (main) Commit initial [Samuel Dauzon] 

Il faut ensuite créer les dépôts d’expérimentation...

Supprimer un commit

Vous vous demandez s’il faut supprimer tel ou tel commit ? Avant toute chose, prenez le temps de réfléchir, n’obéissez pas à une intuition (si vous avez lu Système 1/Système 2 - Les deux vitesses de la pensée de Daniel Kahneman, alors il est temps de faire appel au système 2, la méthode de pensée logique et réfléchie, qui s’oppose au système 1, la méthode de pensée plus intuitive mais plus souvent biaisée).

C’est une question qui amène beaucoup de débutants à commettre des erreurs susceptibles de pénaliser toute leur équipe et à lui faire perdre un temps considérable.

1. Quel est le réel besoin ?

Il existe différentes solutions en fonction du besoin. Pour le cerner, le développeur doit se poser deux questions :

  • « Ai-je besoin de supprimer le commit de l’historique et pas seulement les modifications qu’il apporte au projet ? »

Si le besoin est de supprimer le commit de l’historique, il est important de mesurer les implications de cette suppression. Ce type de besoin est légitime notamment lorsque des données qu’on ne souhaitait commiter l’ont été : données confidentielles ou encore mots de passe. Attention, si un mot de passe a fuité dans un commit envoyé sur un dépôt distant, il est prioritaire de le modifier immédiatement sur tous les services où ce mot de passe est utilisé. En effet, modifier le commit ne sortira pas ledit mot de passe de la mémoire d’éventuels assaillants.

  • « Ai-je pushé le commit sur une branche commune ? »

En d’autres termes, le développeur risque-t-il d’impacter l’historique des autres développeurs travaillant avec lui ? Si aucun autre développeur n’est impacté par une modification d’historique, alors il est possible de modifier l’historique. Si la suppression du commit impacte les autres développeurs, alors il faut leur en parler. Il est possible que la suppression du commit en tant que telle ne soit finalement pas si importante que ça et qu’il puisse rester dans l’historique. Dans le cas contraire, l’idéal...