Réécrire l'historique

Réécrire l'historique

Introduction

Ce tutoriel aborde diverses méthodes de réécriture et de modification de l'historique Git. Git utilise plusieurs méthodes différentes pour enregistrer les changements. Nous aborderons les atouts et les faiblesses de chacune d'elles, et nous expliquerons comment les utiliser. Ce tutoriel développe certaines des raisons les plus courantes pour lesquelles les instantanés commités sont écrasés et vous explique comment éviter les pièges associés à cette opération.

La principale tâche de Git est de s'assurer que vous ne perdiez jamais un changement commité. Git est également conçu pour vous donner le contrôle total de votre workflow de développement. Il vous permet notamment de définir avec exactitude l'apparence de votre historique de projet, mais il crée également des conditions pouvant entraîner la perte de commits. Git fournit ses commandes de réécriture de l'historique en se dégageant de toute responsabilité en cas de perte de contenu découlant de leur utilisation.

Git comprend plusieurs mécanismes de stockage de l'historique et d'enregistrement des changements, parmi lesquels : commit --amend, git rebase et git reflog. Ces options permettent de personnaliser le workflow. À l'issue de ce tutoriel, vous maîtriserez des commandes qui vous permettront de restructurer vos commits Git et vous serez en mesure d'éviter les pièges courants lors de la réécriture de l'historique.

Modification du dernier commit : git commit --amend

La commande git commit --amend permet de modifier facilement le commit le plus récent. Elle vous permet de combiner les changements stagés avec l'ancien commit au lieu de créer un commit totalement nouveau. Elle peut également être utilisée pour modifier le message de commit sans changer son instantané. Cependant, la modification ne se contente pas de changer le dernier commit. Elle le remplace totalement, ce qui signifie que le commit modifié constitue une toute nouvelle entité qui dispose de sa propre réf. Pour Git, elle ressemblera à un tout nouveau commit, marqué par un astérisque (*) dans le schéma ci-dessous. Voici quelques cas d'usage courants de git commit --amend. Nous aborderons les cas d'usage dans les sections suivantes.

git commit --amend

Modifier le dernier message de commit Git

git commit --amend

Imaginons que vous venez de faire un commit et que vous avez fait une erreur dans votre message. L'exécution de cette commande lorsqu'aucun élément n'est stagé vous permet de modifier le message du commit précédent sans modifier son instantané.

Des commits prématurés apparaissent en permanence au cours de votre développement quotidien. On peut facilement oublier de stager un fichier ou formater le message de commit de manière incorrecte. Le flag --amend est un moyen pratique de corriger ces erreurs mineures.

git commit --amend -m "Message de commit mis à jour"

En ajoutant l'option -m, vous pouvez transmettre un nouveau message à partir de la ligne de commande sans être invité à ouvrir un éditeur.

Modifier des fichiers commités

L'exemple suivant illustre un scénario Git commun. Imaginons que nous avons édité des fichiers que nous souhaitons commiter dans un instantané unique, mais que nous avons oublié d'ajouter l'un des fichiers lors de la première tentative. Pour réparer l'erreur, il suffit de stager l'autre fichier et de faire un commit avec le flag --amend :

# Vous modifiez hello.py et main.py
git add hello.py
git commit

# Vous réalisez que vous avez oublié d'ajouter les changements de main.py
git add main.py
git commit --amend --no-edit

Le flag --no-edit vous permet de modifier votre commit sans changer son message. Le commit obtenu remplacera le commit incomplet. Il sera structuré comme si nous avions commité des changements apportés à hello.py et main.py dans un instantané unique.

Ne pas modifier les commits publics

Les commits modifiés sont en réalité des commits entièrement nouveaux. Les anciens commits ne se trouveront plus dans votre branche courante. Les conséquences associées sont les mêmes que pour la réinitialisation d'un instantané public. Évitez de modifier un commit sur lequel repose le travail d'autres développeurs. Cette situation est déroutante, et il est difficile de revenir en arrière.

Récapitulatif

Pour résumer, git commit --amend vous permet de sélectionner le dernier commit afin d'y ajouter de nouveaux changements stagés. Vous pouvez ajouter ou supprimer des changements à partir de la zone de staging afin de les appliquer avec commit --amend. Si aucun changement n'est stagé, --amend vous invite tout de même à modifier le dernier message de log du commit. Utilisez --amend avec précaution dans les commits partagés avec d'autres membres de l'équipe. Lorsque vous modifiez un commit partagé avec un autre utilisateur, vous devrez peut-être résoudre des conflits de merge, une opération chronophage.

Modifier des commits plus anciens ou plusieurs commits

Pour modifier des commits plus anciens ou plusieurs commits, vous pouvez utiliser git rebase afin de combiner une séquence de commits dans un nouveau commit de base. Dans le mode standard, git rebase vous permet de réécrire l'historique, c'est-à-dire d'appliquer automatiquement des commits de votre branche de travail courante à la branche head transmise. Puisque vos nouveaux commits vont remplacer les anciens, il est important de ne pas utiliser git rebase sur des commits publics. Sinon, votre historique de projet disparaîtra.

Lorsqu'il est important de conserver un historique de projet propre, vous pouvez ajouter l'option -i à la commande git rebase pour exécuter un rebase interactif. Vous avez ainsi la possibilité de modifier des commits individuels du processus plutôt que de déplacer tous les commits. Pour de plus amples informations sur le rebase interactif et des commandes rebase supplémentaires, reportez-vous à la page git rebase.

Modifier des fichiers commités

Durant un rebase, la commande edit ou e interrompt la lecture du rebase à ce commit, ce qui vous permet d'apporter des changements supplémentaires avec git commit --amend. Git interrompt la lecture et affiche le message suivant :

Stopped at 5d025d1... formatting
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue

Messages multiples

Chaque commit Git normal comprend un message de log qui explique ce qu'il s'est passé dans le commit. Ces messages fournissent de précieux renseignements qui sont ajoutés à l'historique de projet. Durant un rebase, vous pouvez exécuter quelques commandes dans des commits pour modifier leurs messages.

  • Reword ou r arrête la lecture du rebase et vous permet de réécrire le message de commit.
  • Si vous lancez la commande squash ou s durant la lecture du rebase, tous les commits marqués par s seront interrompus, et vous serez invité à modifier les messages de commit séparés dans un message combiné. Pour plus de détails, reportez-vous à la section sur le squash de commits ci-dessous.
  • Fixup ou f entraîne le même effet de combinaison que squash. Cependant, un fixup de commits n'interrompt pas la lecture du rebase pour ouvrir un éditeur dans lequel les messages de commit seront combinés. Les messages des commits marqués par f seront supprimés et remplacés par le message du commit précédent.

Squasher des commits pour nettoyer l'historique

La commande squash ou s montre la véritable utilité du rebase. Squash vous permet de spécifier les commits à merger dans les commits précédents. C'est ainsi que vous obtenez un « historique propre ». Durant la lecture du rebase, Git exécute la commande rebase spécifiée pour chaque commit. Dans le cas du squash de commits, Git ouvre l'éditeur de texte que vous avez configuré et vous invite à combiner les messages de commit spécifiés. Le processus global peut être illustré comme suit :

Tutoriel Git : exemple de commande git rebase -i

Notez que les commits modifiés à l'aide d'une commande rebase possèdent un ID différent des commits d'origine. Les commits marqués d'une coche recevront un nouvel ID si les commits précédents ont été réécrits.

Les solutions d'hébergement Git modernes telles que Bitbucket proposent désormais des fonctionnalités de « squash automatique » lors du merge. Ces fonctionnalités effectuent automatiquement un rebase et un squash des commits d'une branche lorsque vous utilisez l'interface utilisateur des solutions d'hébergement. Pour de plus amples informations, reportez-vous à l'article « Squasher des commits lors d'un merge de branche Git avec Bitbucket »

Récapitulatif

git rebase vous donne la possibilité de modifier votre historique, alors que le rebase interactif vous permet de le faire sans laisser de traces. Vous avez ainsi la liberté de faire des erreurs, de les corriger et d'affiner votre travail tout en conservant un historique de projet propre et linéaire.

Le filet de sécurité : git reflog

Les logs de référence ou reflogs constituent un mécanisme utilisé par Git pour enregistrer les mises à jour appliquées aux pointes des branches et à d'autres références de commit. Le reflog vous permet de revenir sur les commits même s'ils ne sont pas référencés par une branche ou un tag. Une fois l'historique réécrit, le reflog contient des informations sur l'ancien état des branches et vous permet d'y revenir si nécessaire. À chaque mise à jour de la pointe de votre branche quelle qu'en soit la raison (en changeant les branches, en faisant un pull de nouveaux changements, en réécrivant l'historique ou simplement en ajoutant de nouveaux commits), une nouvelle entrée est ajoutée au reflog. Dans cette section, nous verrons brièvement la commande git reflog et nous étudierons quelques cas d'usage courants.

Utilisation

git reflog

Cette commande affiche le reflog pour le dépôt local.

git reflog --relative-date

Cette commande affiche le reflog avec les informations de date relative (par ex., deux semaines plus tôt).

Exemple

To understand git reflog, let's run through an example.

0a2e358 HEAD@{0}: reset: moving to HEAD~2 0254ea7 HEAD@{1}: checkout: moving from 2.2 to master c10f740 HEAD@{2}: checkout: moving from master to 2.2

The reflog above shows a checkout from master to the 2.2 branch and back. From there, there's a hard reset to an older commit. The latest activity is represented at the top labeled HEAD@{0}.

Si vous êtes revenu en arrière sans le vouloir, le reflog contiendra un master commit qui pointait vers (0254ea7) avant que vous ne déplaciez accidentellement les deux commits.

git reset --hard 0254ea7

Avec git reset, il est possible de changer la branche master et de rétablir le commit à son état précédent. C'est votre filet de sécurité en cas de modification accidentelle de votre historique.

Attention : le reflog ne constitue un filet de sécurité que si les changements ont été commités dans votre dépôt local et qu'il suit seulement les déplacements de la pointe de la branche du dépôt. De plus, les entrées reflog ont une date d'expiration. Par défaut, les entrées reflog expirent au bout de 90 jours.

Pour de plus amples informations, reportez-vous à notre page git reflog

Summary

Dans cet article, nous avons vu plusieurs méthodes pour modifier l'historique et annuler des changements dans Git. Nous avons étudié brièvement le processus git rebase. Voici les enseignements clés de ce module :

  • Vous disposez de plusieurs méthodes pour réécrire l'historique avec Git.
  • Utilisez git commit --amend pour modifier votre dernier message de log.
  • Utilisez git commit --amend pour apporter des changements au dernier commit.
  • Utilisez git rebase pour combiner des commits et modifier l'historique d'une branche.
  • À l'inverse d'une commande git rebase standard, git rebase -i vous offre un contrôle plus fin sur les changements apportés à l'historique.

Pour en savoir plus sur les commandes abordées, reportez-vous aux pages correspondantes :