Git pratique : le squashing
Avant-propos
Le squashing désigne une technique git permettant la fusion de plusieurs commits en un seul.
Un squashing doit être effectué avant toute pull-request afin que la demande soit la plus claire possible à lire et à comprendre. L’intérêt est aussi de garder l’historique le plus propre possible, car cela diminue le nombre de commits non-significatifs.
Il est utile de rappeler qu’une pull-request n’est pas une fonctionnalité de git, mais une fonctionnalité disponible sur la plupart des plateformes collaboratives telles que Github, Gitlab ou encore Bitbucket. Par exemple, si vous contribuez à un projet libre, vous devrez d’abord en faire un fork (une copie du projet dont vous êtes propriétaire) et soumettre une pull-request lorsque vous estimez que votre contribution ou une sous-partie exploitable est terminée. Dès lors, votre pull-request sera revue par vos pairs en attente d’approbation qui jugeront si votre modification peut être intégrée au projet d’origine ou non.
Mise en pratique
Assurez-vous que les commandes que vous entrez soient pertinentes dans votre contexe. En cas de doute, faites une sauvegarde de votre .git avant toute manipulation.
Pour commencez, assurez-vous d’avoir correctement initialisé votre dépôt via la procédure de création d’un projet. Vous devriez avoir une branche master
ainsi qu’une branche develop
.
L’idée est de réaliser tous les développements sur la branche develop
jusqu’à obtenir une version testée pouvant être mise en production. Il conviendra alors de squasher les commits de la branche develop
vers la branche master
tout en conservant l’historique de développement.
Cas concret
La situation est la suivante : vous avez effectué plusieurs commits sur votre branche de développement et vous souhaitez créer une version de production, vous allez donc devoir squasher vos commits de la branche develop
vers la branche master
.
Assurez vous que votre branche develop
soit synchronisée avec le dépôt distant.
# on se déplace sur la branche devant recevoir le squash
git checkout master
# on effectue le squash de la branche de développement
git merge develop -Xtheirs --squash
L’argument -Xtheirs
est très important, car il permet de résoudre automatiquement les conflits en choisissant la branche de référence, ici develop
. Pour en savoir plus : Git Advanced merging : Our or Theirs Preference.
# on place tous les fichiers en staged pour prochain commit
git add --all
# on commit avec le message de production (pertinent dans votre contexte)
git commit -m "Version de production"
# on pousse les modifications sur le dépôt distant
git push origin master
Si tout s’est bien passé, il est souvent temps de créer un tag afin de marquer une version de production.
Que faire en cas de conflit lors du squashing ?
Voici un exemple de conflit pouvant se produire suite à une commande telle que git merge develop --squash
.
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Squash commit -- not updating HEAD
Automatic merge failed; fix conflicts and then commit the result.
Le fichier README.md de la branch master
est en conflit avec celui de la branche develop
. En l’ouvrant, on obtient le résultat suivant.
# symfony-blank
Commit 1
Commit 2
Commit 3
<<<<<<< HEAD
=======
Commit 4
>>>>>>> develop
Vous devez régler vos conflits de manière à revenir à une version strictement identique à votre branche develop
. Pour le résoudre, il suffit de corriger manuellement le fichier, puis d’effectuer un commit comportant la correction.
Autrement dit, vous n’avez aucune autre commande à entrer par rapport à la procédure de squashing habituelle, il suffit simplement de résoudre les conflits en s’assurant bien que votre choix soit identique aux fichiers de la branche develop
.
Comment annuler des modifications locales ?
Si vous avez débuté votre procédure de squashing mais que vous souhaitez l’annuler, il est possible de s’assurer que le dépôt local de la branche master
soit réinitialisé au dernier commit.
# on se déplace sur la branche master
git checkout master
# on annule le merge en cours
git merge --abort
# on réinitialise le dépôt à partir du dernier commit
git reset --hard HEAD
Bonus : les différentes méthodes de squashing
Il n’y a pas qu’une seule façon de réaliser un squashing, il est également possible d’utiliser la commande git rebase
qui est capable de réaliser le squashing de manière interactive, comportant des avantages et de inconvénients, tout est une question de contexte.
Généralement, voici les principales différences entre les deux opérations.
Merging :
- opération non-destructive, ne ré-écrit pas les commits
- historique moins propre, peut générer des commits automatiques de fusion
Rebasing :
- opération destructive, ré-écrit les commits
- historique plus propre, seuls les commits explicits restent
Il est possible d’obtenir le même résultat que pour la procédure de squashing via git merge --squash
, mais de manière plus contrôlée.
# on repasse sur la branche master
git checkout master
# on se déplace sur une nouvelle branche de transition clonée depuis master afin de travailler dans un espace sécurisé
git checkout -b squash
# on merge la branche develop vers la branche squash, mais sans fusionner les commits
git merge develop
Dans notre exemple, la branche develop
était 3 commits au dessus de master
, on obtient donc le log suivant.
$ git log --graph --decorate --pretty=oneline --abbrev-commit
* 38b2513 (HEAD -> squash, origin/develop, develop) Commit 3
* fe0e51b Commit 2
* 923cbd6 Commit 1
* 869c8be (origin/master, master) Add README.md
* d1f55ea Initial commit
On utilise la commande git rebase -i <commit hash précédent>
pour fusionner nos 3 commits, on se place donc sur le commit précédant le premier commit qui nous intéresse, c’est-à-dire 869c8be
.
# rebase en mode interactif sur le commit 94053db
git rebase -i 869c8be
A partir de là, git ouvrira une fenêtre où l’on devra définir la façon dont se comportera notre rebase.
Chaque commit est à pick
par défaut, il faut donc passer les commits suivants le premier sur squash
ou s
afin de les fusionner tout en conservant leur historique. Dans ce cas, il sera possible d’écrire un message personnalisé dans la fenêtre suivante.
Sachant que l’on a demandé un squash
, chaque message des différents commits sera concaténé dans le nouveau commit automatique de fusion, vous pouvez les modifier par cette étape.
Il est également possible d’utiliser fixup
pour les fusionner sans conserver le message du commit.
Tout cela aura pour effet de créer un nouveau commit de fusion.
$ git log --graph --decorate --pretty=oneline --abbrev-commit
* 02d385a (HEAD -> squash) Commit 1
* 94053db (origin/master, master) Add README.md
* ec113b4 Initial commit
Il suffit de repasser sur la branche master
et de push
# on repasse sur la branche master
git checkout master
# on merge la branche squash qui contient le commit de la nouvelle version de production
git merge squash --ff
# on push la version de production sur le dépôt distant
git push origin master --force
# on supprime la branche de transition
git branch -D squash
Pour en savoir plus : Merging vs. Rebasing.