Git, en toute simplicité : modifier l’historique avec rebase — partie I
Or Kamara
9 juin 2020
0 minutes de lectureRécemment, je travaillais sur une fonctionnalité importante qui impliquait plusieurs modifications dans différentes parties du code. Pour que la PR de cette fonctionnalité reste facile à lire, j’ai réparti le travail en plusieurs commits. Mais j’ai vite compris que ce découpage ne suffisait pas : je devais apporter de nombreuses modifications à chacun de ces commits (pour corriger des problèmes que j’avais repérés ou intégrer les retours de la code review).
Je connaissais déjà les bases de la commande magique git rebase interactive, qui permet de « réécrire votre historique ». Mais les changements que je devais apporter étaient plus complexes que ce que je savais faire. J’ai donc commencé à explorer les options de cette commande.
Pour celles et ceux qui ne connaissent pas du tout cette commande, voici le résultat de l’exécution de git rebase -i BRANCH :
Je me souviens très bien de la première fois où j’ai exécuté cette commande. Je me suis dit : Bon sang, qu’est-ce qui se passe ? On m’avait promis un moyen simple et interactif de modifier mes commits, mais je ne voyais qu’une interface très compliquée… J’ai vraiment dû prendre le temps de lire la description de chacune des options, car en quittant cet écran, je risquais de perdre tout mon travail (#ScaredDeveloper).
Même en essayant de quitter l’écran et d’annuler, l’expérience n’a pas été simple… J’ai dû chercher sur Google comment faire et j’ai découvert qu’il suffisait de supprimer toutes les lignes de commit pour qu’aucune modification ne soit apportée. Facile (et moche). Je me suis d’abord dit : « Mais attendez, je ne fais pas vraiment confiance à tout ce que je lis sur Google. Et si je fais ça et que tout mon travail disparaît ? ? »
J’ai alors compris que je n’avais pas d’autre choix et je me suis mis à étudier ces commandes.
Dans cet article, je vais détailler les différents problèmes que les développeurs doivent résoudre lorsqu’ils travaillent sur des PR comportant plusieurs commits. Je ne vais pas expliquer chaque aspect de ces commandes, car de nombreux articles expliquent ce qui se passe en coulisses. Je vais plutôt vous présenter quelques situations auxquelles nous pouvons tous être confrontés lorsque nous travaillons avec git, et vous proposer la solution git rebase -i adaptée.
Comme le sujet est assez vaste, je vais diviser cet article en deux parties. Dans cette première partie, je présenterai les bases de Rebase ainsi que deux situations courantes. Dans la deuxième partie, je vous présenterai quatre autres situations courantes et quelques conclusions.
Sans plus attendre, abordons cette première partie !
Rebase en bref
Commençons par examiner la situation suivante : vous et votre ami travaillez sur un nouveau projet passionnant. Vous venez de créer le dépôt (et d’effectuer un premier commit appelé A) et il est maintenant temps de coder. Pour gagner en efficacité, vous avez décidé de vous répartir le travail. Vous êtes chargé de la fonctionnalité principale et votre ami de créer le framework de l’application.
Vous partez donc tous les deux du même commit : A. Après 5 heures de travail intensif, vous êtes prêts à terminer et à fusionner les modifications dans master. Votre ami est le premier à fusionner, en créant 3 nouveaux commits au-dessus de A : B + C + D. C’est maintenant à votre tour de fusionner dans master tout votre travail sur FeatureBranch : les commits E + F + G. Voici à quoi cela ressemble :
Pour tester la fonctionnalité que vous avez créée, vous devez utiliser certaines fonctions du framework, plus précisément celles du commit D. Autrement dit, vous devez changer la base de votre branche du commit A au commit D, comme si vous aviez créé votre branche à partir de la dernière version :
C’est précisément pour cela que git rebase est nécessaire : déplacer une suite de commits vers un nouveau commit de base. En coulisses, git crée de nouveaux commits et les applique à la nouvelle base spécifiée.
Plus précisément, dans cet article, nous allons parler du mode interactif.
L’exécution de rebase avec l’option -i vous permet de modifier chaque commit individuellement dans le cadre du processus de rebase classique : c’est là que la magie opère ! En mode interactif, vous pouvez choisir l’action à effectuer sur chacun de vos commits : fusionner, scinder, renommer, réordonner, et bien plus encore.
Nous allons examiner chacune de ces options à travers des situations courantes où git rebase peut s’avérer utile.
Situations courantes (et comment les gérer !)
Examinons quelques situations courantes dans lesquelles vous pourriez vouloir modifier l’historique de votre code, et voyons comment procéder avec Git :
1. Modifier le message d’un ancien commit (reword)
Vous avez apporté plusieurs modifications au code, parfaitement réparties en différents commits, mais vous remarquez ensuite une faute de frappe dans l’un des messages de commit :
Comme vous pouvez le constater, le message du deuxième commit est 3nd commit (oups) : corrigeons-le.
Si la faute de frappe se trouvait dans le dernier commit, nous aurions pu utiliser la commande git commit --amend. Mais comme elle se trouve au milieu de notre graphe git, il faut procéder autrement.
Dans ce cas, nous allons utiliser la commande git rebase -i master (master peut être une autre branche) et choisir l’option reword pour le deuxième commit (a21bd1d) :
Après avoir enregistré le fichier, un nouvel éditeur s’ouvrira pour vous permettre de modifier le message du commit :
Il ne nous reste plus qu’à remplacer la première ligne par le nouveau message de commit, feat: 2nd commit, puis à enregistrer à nouveau.
Le graphe git est maintenant corrigé :
2. Modifier le contenu d’un ancien commit (squash, fixup)
Vous venez de recevoir des commentaires de code review de votre collègue et devez apporter quelques petites modifications : ajouter un nouveau fichier et modifier le contenu d’un fichier existant.
Comme vous le savez probablement, chaque commit doit correspondre à un bloc de logique, et pas simplement à des modifications de fichiers. Il faut notamment éviter les messages de commit comme CR fixes, ou même really small CR fixes dans notre cas. Il est préférable de regrouper tous les commits afin que chacun contienne, au final, toutes les modifications pertinentes (y compris les corrections demandées lors de la code review).
Comment faire ? C’est là que les options squash et fixup entrent en jeu.
Commençons par créer des commits temporaires contenant les modifications nécessaires :
Dans l’exemple ci-dessus, nous avons créé 2 nouveaux commits :
5641a86: modifie le contenu du fichier créé dans le premier commit17a73c1: ajoute un nouveau fichier au deuxième commit
Mais nous ne voulons pas conserver un graphe git tel quel. Nous voulons regrouper 5641a86 avec 271c66e, et 17a73c1 avec e2952e7. Pour cela, exécutons la commande git rebase -i master. Comme la dernière fois, voici le premier écran qui s’affiche :
Réorganisons maintenant les lignes selon l’ordre souhaité des commits, puis utilisons l’option squash pour les commits 5641a86 et 17a73c1 :
À la suite de ces modifications, git va traiter les commits (de 271c66e à fdb5f71) et intégrer le contenu des commits marqués avec squash dans le commit précédent. Le graphe git correspond maintenant exactement à ce que nous voulions !
Nous pouvons aussi utiliser squash plutôt que fixup. Ces deux options font quasiment la même chose, à ceci près que fixup supprime le message du commit.
Un point important : il arrive que le regroupement de commits entraîne des conflits dans le code. Lorsque git détecte des conflits, vous devez les corriger, indexer les modifications et poursuivre le rebase en exécutant git rebase --continue. N’oubliez pas que vous pouvez toujours exécuter git rebase --abort si vous ne savez pas quoi faire. Cette commande arrête le rebase et rétablit l’état initial de votre dépôt (celui d’avant l’exécution de git rebase -i).
Pour conclure
Voilà pour cette première partie sur la modification de l’historique git avec Rebase ! Dans la deuxième partie, nous examinerons quatre autres situations courantes et vous proposerons des solutions pour les gérer.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.
