Opérations avancées sur les commits Git

GitBeginner
Pratiquer maintenant

Introduction

Bon retour, aventurier de Git ! Vous avez fait vos premiers pas dans le monde de la gestion de versions, et il est maintenant temps de passer au niveau supérieur. Dans cet atelier, nous allons explorer certaines des opérations de commit les plus avancées de Git. Ces techniques vous donneront encore plus de contrôle sur l'historique de votre projet, vous permettant de corriger des erreurs, de réorganiser votre travail et de collaborer plus efficacement.

Considérez cet atelier comme une mise à niveau de votre machine à remonter le temps. Non seulement vous pouvez voyager dans le temps, mais vous allez maintenant apprendre à modifier la chronologie elle-même ! Ne vous inquiétez pas si cela semble intimidant : nous vous guiderons à chaque étape, en expliquant non seulement comment effectuer ces opérations, mais aussi pourquoi elles sont utiles dans des scénarios réels.

À la fin de cet atelier, vous serez capable de modifier des commits (amend), d'annuler des changements (revert), de sélectionner des commits spécifiques (cherry-pick), d'effectuer un rebasage interactif (interactive rebase) et de fusionner des commits (squash). Ce sont des outils puissants que les développeurs professionnels utilisent quotidiennement pour maintenir des historiques de projet propres et organisés. Plongeons dans le vif du sujet et propulsons vos compétences Git vers de nouveaux sommets !

Configuration de votre espace de travail

Avant de commencer nos opérations avancées, configurons un nouvel espace de travail. Nous allons créer un nouveau répertoire et y initialiser un dépôt Git. Cela nous donnera un espace propre pour expérimenter sans affecter vos projets existants.

Ouvrez votre terminal et tapez ces commandes, en appuyant sur Enter après chaque ligne :

cd ~/project
mkdir git-advanced-lab
cd git-advanced-lab
git init

Analysons ce que font ces commandes étape par étape :

  1. cd ~/project : cd signifie "change directory" (changer de répertoire). ~/project est un chemin qui pointe généralement vers un dossier "project" dans votre répertoire personnel (~). Cette commande navigue votre terminal dans ce répertoire. Si le répertoire "project" n'existe pas, vous pourriez rencontrer une erreur. Si c'est le cas, créez-le d'abord avec mkdir ~/project puis réessayez cd ~/project.
  2. mkdir git-advanced-lab : mkdir signifie "make directory" (créer un répertoire). Cette commande crée un nouveau dossier nommé "git-advanced-lab" à l'intérieur de votre répertoire actuel. Ce nouveau répertoire sera la racine de notre dépôt Git pour cet atelier.
  3. cd git-advanced-lab : Cette commande change à nouveau votre répertoire actuel pour vous déplacer à l'intérieur du dossier "git-advanced-lab" fraîchement créé. Désormais, toutes les commandes que vous lancerez seront exécutées dans ce répertoire.
  4. git init : C'est la commande Git cruciale pour initialiser un dépôt. git init configure toutes les structures Git nécessaires à l'intérieur du répertoire actuel, le transformant en un dépôt Git. Vous verrez un dossier caché nommé .git créé à l'intérieur. Ce dossier .git est le cœur de votre dépôt et stocke tout l'historique des versions et la configuration.

Maintenant que nous avons un dépôt Git initialisé, créons un fichier simple pour travailler et effectuons notre premier commit :

echo "Hello, Advanced Git" > hello.txt
git add hello.txt
git commit -m "Initial commit"

Voici ce qui se passe dans ces commandes :

  1. echo "Hello, Advanced Git" > hello.txt : echo est une commande qui affiche du texte. > est un opérateur de redirection. Il prend la sortie de la commande echo et la redirige vers un fichier nommé "hello.txt". Si le fichier n'existe pas, il est créé. S'il existe, il est écrasé.
  2. git add hello.txt : Avant que Git puisse suivre les modifications d'un fichier, vous devez lui dire explicitement de commencer à le suivre. git add place le fichier dans la zone de transit (staging area). Considérez cette zone comme une zone de préparation pour votre commit.
  3. git commit -m "Initial commit" : git commit prend toutes les modifications actuellement en zone de transit et les enregistre comme un nouveau commit dans l'historique. -m ajoute un message de commit, qui doit être une description courte des changements effectués.

Super ! Nous avons maintenant un dépôt avec un commit. Vérifions l'état pour confirmer que tout est correctement configuré :

git status

Après avoir exécuté git status, vous devriez voir un message ressemblant à ceci :

On branch master
nothing to commit, working tree clean

Ce message indique que votre arbre de travail est propre. "Working tree" fait référence au répertoire où se trouvent vos fichiers. "Clean" signifie qu'il n'y a aucune modification non commitée. Cela confirme que nous sommes prêts à commencer nos opérations avancées sur une base saine !

Modifier votre dernier commit

Imaginez que vous venez de faire un commit, mais que vous réalisez ensuite que vous avez oublié d'inclure une modification dans un fichier, ou que vous avez fait une petite faute de frappe dans votre message de commit. Au lieu de créer un tout nouveau commit pour une correction aussi mineure, Git vous permet de corriger le commit le plus récent à l'aide de l'option --amend. C'est comme remonter légèrement dans le temps pour ajuster votre dernière action.

Essayons cela. Tout d'abord, modifions notre fichier hello.txt en ajoutant une autre ligne :

echo "This is an important file." >> hello.txt

Cette commande ajoute une nouvelle ligne à notre fichier. Comprenons l'opérateur >> :

  • > écraserait tout le fichier.
  • >> ajoute le nouveau contenu à la fin du fichier existant, préservant le contenu original.

Ainsi, "hello.txt" contiendra désormais deux lignes. Disons maintenant que nous réalisons que nous aurions dû inclure cette note "important file" dans notre commit initial. Nous pouvons modifier notre commit précédent pour inclure ce changement et mettre à jour le message.

git add hello.txt
git commit --amend -m "Initial commit with important note"

Détail des commandes :

  1. git add hello.txt : Nous avons modifié le fichier, nous devons donc le remettre en zone de transit.
  2. git commit --amend -m "Initial commit with important note" : C'est ici que la magie opère.
    • git commit --amend : Le drapeau --amend indique à Git qu'au lieu de créer un nouveau commit, nous voulons modifier le dernier.
    • -m "..." : Fournit un nouveau message de commit. Si vous omettez -m, Git ouvrira votre éditeur de texte par défaut pour vous permettre de modifier le message original.

Que s'est-il passé ?

Au lieu de créer un nouveau commit, Git a effectivement remplacé le précédent "Initial commit" par une version améliorée. L'ancien commit a disparu, et le nouveau intègre les modifications et le message mis à jour.

Vérifions cela en consultant le journal des commits :

git log --oneline

Vous ne devriez voir qu'un seul commit, mais avec le nouveau message.

<commit_hash> Initial commit with important note

Appuyez sur q pour quitter la vue du journal si elle ne se ferme pas automatiquement.

Considérations importantes lors de l'utilisation de --amend :

  • Uniquement pour les commits non poussés : Vous ne devriez modifier que les commits que vous n'avez pas encore envoyés (push) vers un dépôt distant partagé (comme GitHub). Modifier un commit déjà poussé réécrit l'historique sur lequel d'autres pourraient s'appuyer, ce qui peut causer de graves problèmes de collaboration.
  • Nettoyage de l'historique local : L'amendement est principalement utile pour nettoyer votre historique local avant de partager votre travail.

Annuler un commit avec Revert

Parfois, vous effectuez un commit et réalisez plus tard qu'il a introduit un bug ou que vous voulez simplement annuler ces changements. Vous pourriez penser à utiliser git reset pour revenir en arrière, mais git reset peut être destructeur. Une manière plus sûre et plus collaborative d'annuler des changements est d'utiliser git revert.

git revert crée un nouveau commit qui annule les changements introduits par un commit spécifique. Il n'efface pas le commit original de l'historique ; au lieu de cela, il ajoute une nouvelle entrée qui inverse les effets du commit indésirable.

Créons un nouveau commit que nous allons ensuite annuler :

echo "This line will be reverted" >> hello.txt
git add hello.txt
git commit -m "Add line to be reverted"

Maintenant, supposons que l'ajout de cette ligne était une erreur. Nous pouvons annuler ce dernier commit avec :

git revert HEAD

Analyse de la commande :

  • git revert : La commande pour inverser les changements.
  • HEAD : Dans Git, HEAD pointe vers le commit actuel de votre branche. git revert HEAD signifie donc "annuler le commit le plus récent".

Lorsque vous lancez cette commande, Git va :

  1. Analyser les changements du commit cible.
  2. Calculer l'inverse de ces changements (ici, supprimer la ligne ajoutée).
  3. Créer un nouveau commit appliquant cette inversion.
  4. Ouvrir l'éditeur de texte pour le message de commit (souvent pré-rempli avec "Revert '...'").

Gérer l'éditeur de texte pendant un git revert :

Si votre éditeur s'ouvre (souvent Vim ou Nano) :

  • Avec Vim : Appuyez sur Esc, tapez :wq puis Enter.
  • Avec Nano : Appuyez sur Ctrl + X, puis Y, puis Enter.

Vérifions l'historique :

git log --oneline

Vous devriez voir trois commits (du plus récent au plus ancien) :

  1. Le commit de "Revert".
  2. Le commit "Add line to be reverted".
  3. Le commit "Initial commit with important note".

Pourquoi préférer git revert à git reset dans un projet partagé ?

  • Historique non destructif : On ne réécrit pas le passé, on ajoute une correction. Tout le monde voit l'erreur et sa correction.
  • Évite les "Force Push" : git reset nécessite souvent de forcer la mise à jour du serveur (push --force), ce qui peut écraser le travail des collègues. git revert s'envoie comme n'importe quel commit normal.

Le Cherry-picking de commits

Le "cherry-picking" (cueillette) dans Git consiste à choisir un commit spécifique sur une branche et à l'appliquer sur votre branche actuelle. C'est utile pour récupérer une correction de bug ou une fonctionnalité précise sans fusionner toute la branche d'origine.

Simulons un scénario où une fonctionnalité est développée sur une branche séparée, et nous voulons n'en ramener qu'une partie sur master.

D'abord, créons une nouvelle branche et faisons un commit :

git checkout -b feature-branch
echo "This is a new feature" >> feature.txt
git add feature.txt
git commit -m "Add new feature"

Revenons maintenant sur la branche principale master :

git checkout master

Nous voulons appliquer uniquement le commit "Add new feature" sur master. Utilisons git cherry-pick :

git cherry-pick feature-branch

Ici, feature-branch désigne le dernier commit de cette branche. Si vous aviez plusieurs commits et n'en vouliez qu'un seul au milieu, vous utiliseriez son identifiant (hash) que vous trouveriez avec git log.

Git va alors :

  1. Identifier le commit.
  2. Appliquer les mêmes changements sur master.
  3. Créer un nouveau commit sur master.

Vérifiez avec git log --oneline. Vous verrez le nouveau commit sur master. Notez qu'il a un hash différent du commit original sur feature-branch, car c'est une nouvelle entité dans cette branche, même si le contenu est identique.

Rebasage interactif (Interactive Rebasing)

Le rebasage interactif est l'une des fonctionnalités les plus puissantes de Git. Il permet de réécrire l'historique de manière très flexible : réordonner les commits, les combiner (squash), modifier les messages ou en supprimer.

Attention : Ne rebasez jamais des commits qui ont déjà été poussés sur un dépôt partagé. Cela réécrit l'historique et perturbera tous vos collaborateurs.

Créons une série de commits pour s'exercer :

echo "First change" >> hello.txt
git commit -am "First change"
echo "Second change" >> hello.txt
git commit -am "Second change"
echo "Third change" >> hello.txt
git commit -am "Third change"

L'option -am est un raccourci pour ajouter les modifications des fichiers déjà suivis et faire le commit en une seule étape.

Supposons que nous voulions fusionner "First change" et "Second change" en un seul commit, et améliorer le message de "Third change".

Lancez la session interactive :

git rebase -i HEAD~3

HEAD~3 signifie que nous travaillons sur les 3 derniers commits à partir du sommet actuel. Un éditeur s'ouvre avec une liste de commits précédés du mot pick.

Le plan d'action :

  1. Changer pick par squash (ou s) devant "Second change" pour le fusionner dans le premier.
  2. Changer pick par reword (ou r) devant "Third change" pour modifier son message.

Modifiez le fichier pour qu'il ressemble à ceci :

pick abc1234 First change
squash def5678 Second change
reword ghi9101 Third change

(Note : gardez vos propres hash de commits)

Étapes suivantes :

  1. Après avoir enregistré, Git ouvrira un éditeur pour fusionner les messages des deux premiers commits. Écrivez par exemple : Combined first and second changes.
  2. Ensuite, Git ouvrira un autre éditeur pour le troisième commit. Changez le message en : Improved third change.

Une fois terminé, vérifiez avec git log --oneline. Vous verrez que votre historique est maintenant plus propre et plus concis. Si vous faites une erreur pendant le processus, vous pouvez toujours annuler avec git rebase --abort.

Rebasage depuis la racine (Root Rebase)

Le rebasage interactif peut aller encore plus loin. git rebase -i --root permet de rebaser tous les commits depuis le tout premier commit du dépôt.

Prudence extrême : C'est une opération radicale qui change l'identité de chaque commit du projet. À ne faire que localement sur un projet personnel ou avant le premier push.

Essayons :

git rebase -i --root

L'éditeur affiche maintenant l'intégralité de l'historique depuis le début.

Exercice :

  1. Utilisez reword sur le tout premier commit pour changer son message en Initial setup and file creation.
  2. Utilisez squash sur le deuxième commit pour le fusionner dans le premier.

Enregistrez et suivez les instructions de l'éditeur pour valider les messages. Cette technique est parfaite pour "nettoyer" totalement un projet avant de le rendre public ou de le soumettre à une revue de code, en transformant une série de tâtonnements en une progression logique et propre.

Résumé

Félicitations, maître Git ! Vous venez de franchir une étape majeure. Récapitulons les techniques apprises :

  1. Modifier des commits (Amend) : Pour corriger immédiatement le dernier commit sans en créer un nouveau.
  2. Annuler des commits (Revert) : La méthode sûre et collaborative pour faire marche arrière.
  3. Cherry-picking : Pour extraire chirurgicalement des modifications d'une branche à une autre.
  4. Rebasage interactif : L'outil ultime pour sculpter, fusionner et clarifier votre historique local.
  5. Rebasage à la racine : Pour une refonte totale de la chronologie de votre projet.

Ces opérations avancées ne sont pas seulement esthétiques ; elles facilitent grandement la relecture du code, le débogage et la compréhension de l'évolution d'un projet sur le long terme. Utilisez-les avec discernement, et votre historique Git deviendra une documentation précieuse en soi.