31/10/2018 Tech
Comment “squasher” efficacement ses commits avec Git
Lors de ma première Pull Request pour Sonata, j’ai rapidement eu une remarque : “squash your commits”. Après des recherches, voici le résultat.
Lors de ma première Pull Request pour Sonata, j’ai rapidement eu une remarque : “squash your commits”. Ne maitrisant pas le sujet, j’ai fait des recherches, et ai trouvé beaucoup d’informations. Mais il manquait toujours un bout et notamment le cas d’utilisation auquel j’était confronté : le squash après un merge. Du coup, plutôt que de garder cela pour moi je le partage avec vous dans cet article.
Avant tout, qu’est-ce qu’une Pull Request (PR) ? Une PR est une méthode utilisée pour contribuer à un projet en proposant les modifications que le développeur souhaite apporter sur le dépôt principal. C’est le moyen de contribution le plus utilisé avec les outils de gestion de versions décentralisé (tel que Git) et le plus répandu grâce à GitHub et aux projets Open Source. En effet, dans le monde Open Source, les PR sont très fréquemment utilisées lorsqu’un développeur souhaite contribuer à un projet, que ce soit pour l’améliorer et/ou aider dans son développement. Par exemple, certains ajouteront de la documentation, de la traduction, des tests, tandis que d’autres proposeront des optimisations de code ou l’ajouts de fonctionnalités.
L’objectif principal d’une PR est de discuter autour des modifications proposées avec les personnes responsables du dépôt d’origine pour être certain que la qualité de leur travail soit conservée. On soumet ses modifications à l’avis de tous et les responsables du dépôt peuvent accepter ces modifications en les mergeant et fermer la PR.
Note – Il est important de bien comprendre que les PR sont des méthodes de travail, il ne s’agit pas d’une fonctionnalité du système de versionning.
Un “squash” est un regroupement de plusieurs commits. Le but est de les fusionner en un seul pour avoir un historique Git plus propre. Il est notamment conseillé de le faire dans le cadre des Pull Request pour simplifier la relecture des modifications effectuées.
Concrètement, lorsqu’on travaille sur une PR, on fait souvent beaucoup de commits, que ce soit pour développer la fonctionnalité, ou pour corriger les retours que l’on nous fait, on se retrouve facilement avec une dizaine de commits. L’intérêt principal du squash de commits est de conserver un historique propre malgré les nombreux commits qui ont été faits auparavant. On va ainsi pouvoir les regrouper tous en un seul commit contenant la totalité des modifications. Notre PR sera ainsi plus simple et plus claire.
Cas simple
Pour le cas simple, on souhaite squasher nos commits qui se suivent dans l’historique, sur la même branche.
Prenons un exemple concret, voici notre dépôt. git (on visualise le repo à l’aide de tig) :
Comme vous pouvez le constater, je suis sur ma branche “master”, sur laquelle j’ai effectué quatre commits pour modifier le même fichier ‘Readme.md’.
Tout d’abord, il faut récupérer le sha1 du commit précédant celui dans lequel on va squasher, puis on l’utilise pour lancer la commande :
git rebase -i fbde9fd9c14c9f449f9461b6d3c17c92923b97f0
On entre alors en mode interactif (on utilise l’éditeur de git). Les commits sont affichés du plus ancien au plus récent.
pick fbde9fd Add Readme.md
pick 70aaed5 Update Readme
pick ccf2779 Update Readme again
pick 8c706b5 Update Readme again and again
Le premier commit correspond à celui de base, celui que l’on souhaite conserver ; on laisse donc l’instruction “pick” devant. On souhaite squasher tous les commits suivants, on met donc l’instruction “squash” devant. (vous pouvez aussi utiliser l’alias “s” à la place de “squash”)
On obtient alors :
pick fbde9fd Add Readme.md
squash 70aaed5 Update Readme
squash ccf2779 Update Readme again
squash 8c706b5 Update Readme again and again
Concrètement, on dit à GIT de se baser sur le premier commit et on lui applique tous les suivants pour n’en faire qu’un seul.
Lorsque l’on valide le squash (on quitte le mode intéractif), Git vas ré-appliquer les commits dans le même ordre qu’ils ont été configuré juste avant. On met alors un message de commit qui concerne le regroupement de nos commits et c’est tout.
On peut ensuite revérifier toujours à l’aide de la commande tig que nos commits ont bien été squashés. Il ne vous reste plus qu’a pousser vos modifications :
git push origin master --force
Note – Pensez à bien utiliser l’option “–force” car le rebase doit écraser l’ancien historique des commits.
Attention, l’option —force ne doit être utilisée que sur votre propre fork !
Cas complexe
Pour le cas complexe, on a mergé une autre branche entre nos commits à squasher.
Prenons un second exemple concret, voici notre dépôt git :
Comme vous pouvez le constater, je suis sur ma branche “develop”, sur laquelle j’ai effectué cinq commits. Puis j’ai récupéré master, et enfin j’ai fait un dernier commit.
Tout d’abord, il faut déterminer le sha1 du commit à la base de notre branche. Pour cela, on utilise un git merge-base :
On effectue ensuite notre squash :
git rebase -i 12e70fef8ee24bfaa99defcaf68b29c378008380
Vous remarquerez ici qu’on ne voit pas les commits du merge de master apparaître, grâce au sha1 que l’on a récupéré précédemment ; on travaille uniquement sur les commits de notre branche.
Note – Cette méthode d’identification du commit peut fonctionner aussi dans les cas “simples”.
Comme vu précédemment, on modifie les instructions devant chaque commit grâce au mode interactif.
pick 296b2bf Commit number 1
s 2c8bee6 Commit number 2
s 50af987 Commit number 3
s d971e58 Commit number 4
s 0c9d788 Commit number 5
s 007ccf8 Commit number 6
De la même manière que pour le cas simple, on indique le commit à ‘pick’ (c’est-à-dire à utiliser comme référence) et ceux à squasher, puis on met le message de commit à celui que l’on conserve.
On peut ensuite revérifier, à l’aide de la commande tig, que nos commits ont bien été squashés et que le merge de la branche master a bien été conservé. Il ne vous reste plus qu’à pusher vos modifications, en “–force”, comme vu précédemment.
git push origin master --force
Astuce – Si vous souhaitez faire un rebase depuis le tout premier commit de votre projet, vous devez utiliser l’option --root
Le squash de commit rend vos PR beaucoup plus propres et plus lisibles pour les autres, il simplifie aussi le revert en cas de problème. N’hésitez pas à squasher vos PR autant que possible lorsque vous travaillez sur vos projets.
Photos par Nat Welch
Sources :