On va donc développer nos fonctionnalités sur des branches connexes et les tester jusqu’à ce qu’on soit sûrs qu’il n’y a pas de problème et on va finalement réintégrer ces fonctionnalités développées au sein de notre ligne de développement principale.
Pour faire cela, il va falloir rapatrier le contenu des branches créées dans la branche principale. On peut faire ça de deux manières avec Git : en fusionnant les branches ou en rebasant.
Fusionner des branches
Commençons par nous concentrer sur sur la fusion de branches.
Dans la leçon précédente, on avait fini avec deux branches master
et test
divergentes. On parle de divergence car les deux branches possèdent un ancêtre commit en commun mais pointent chacune vers de nouveaux commits qui peuvent correspondre à des modifications différentes d’un même fichier du projet.
Revenons un peu en arrière pour commencer avec un cas plus simple et imaginons que notre projet soit dans cet état :
Ici, on a une branche test
qui pointe sur un commit commitN+1 et une branche master
qui pointe sur un commit commitN. commitN est l’ancêtre direct de commitN+1 et il n’y a donc pas de problème de divergence.
Pour fusionner nos deux branches, on va se placer sur master
avec une commande git checkout
puis taper une commande git merge
avec le nom de la branche qu’on souhaite fusionner avec master
.
Dans ce cas, “fusionner” nos deux branches revient finalement à faire avancer master
au niveau du commit pointé par test
. C’est exactement ce que fait Git et c’est ce qu’on appelle un “fast forward” (avance rapide).
Il ne nous reste alors plus qu’à effacer notre branche test
. On peut faire cela en utilisant la commande git branch -d
.
Reprenons maintenant la situation précédente avec deux branches dont les historiques divergent. On peut représenter cette situation comme cela :
Pour fusionner deux branches ici on va à nouveau se placer dans la branche dans laquelle on souhaite fusionner puis effectuer un git merge
.
Ici, comme la situation est plus complexe, il me semble intéressant d’expliquer comment Git fait pour fusionner les branches. Dans ce cas, Git réalise une fusion en utilisant 3 sources : le dernier commit commun aux deux branches et le dernier commit de chaque branche.
Cette fois-ci, plutôt que de simplement faire un fast forward, Git crée automatiquement un nouvel instantané dont le contenu est le résultat de la fusion ainsi qu’un commit qui pointe sur cet instantané. Ce commit s’appelle un commit de fusion et est spécial puisqu’il possède plusieurs parents.
Notez que dans le cas d’une fusion à trois sources, il se peut qu’il y ait des conflits. Cela va être notamment le cas si une même partie d’un fichier a été modifiée de différentes manières dans les différentes branches. Dans ce cas, lors de la fusion, Git nous alertera du conflit et nous demandera de le résoudre avant de terminer la fusion des branches.
On peut utiliser une commande git status
pour voir précisément quels fichiers sont à l’origine du conflit. Imaginons par exemple que nos deux branches possèdent un fichier LISEZMOI.txt et que les deux fichiers LISEZMOI.txt possèdent des textes différents. Git va automatiquement “fusionner” les contenus des deux fichiers en un seul qui va en fait contenir les textes des deux fichiers de base à la suite l’un de l’autre avec des indicateurs de séparation.
On peut alors ouvrir le fichier à la main et choisir ce qu’on conserve (en supprimant les parties qui ne nous intéressent pas par exemple). Dès qu’on a terminé l’édition, on va taper une commande git add
pour marquer le conflit comme résolu. On n’aura alors plus qu’à effectuer un git commit
pour terminer le commit de fusion.
Rebaser
Git nous fournit deux moyens de rapatrier le travail effectué sur une branche vers une autre : on peut fusionner ou rebaser. Nous allons maintenant nous intéresser au rebasage, comprendre les différences entre la fusion et le rebasage et voir dans quelle situation utiliser une opération plutôt qu’une autre.
Reprenons notre exemple précédent avec nos deux branches divergentes.
Plutôt que d’effectuer une fusion à trois sources, on va pouvoir rebaser les modifications validées dans commitN+1 dans notre branche master
. On utilise la commande git rebase
pour récupérer les modifications validées sur une branche et les rejouer sur une autre.
Dans ce cas, Git part à nouveau du dernier commit commun aux deux branches (l’ancêtre commun le plus récent) puis récupère les modifications effectuées sur la branche qu’on souhaite rapatrier et les applique sur la branche vers laquelle on souhaite rebaser notre travail dans l’ordre dans lequel elles ont été introduites.
Le résultat final est le même qu’avec une fusion mais l’historique est plus clair puisque toutes les modifications apparaissent en série même si elles ont eu lieu en parallèle. Rebaser rejoue les modifications d’une ligne de commits sur une autre dans l’ordre d’apparition, alors que la fusion joint et fusionne les deux têtes.
Gardez cependant à l’esprit que rebaser équivaut à supprimer des commits existants pour en créer de nouveaux (qui sont similaires de par leur contenu mais qui sont bien des entités différentes). Pour cette raison, vous ne devez jamais rebaser des commits qui ont déjà été poussés sur un dépôt public.
En effet, imaginons la situation suivante :
- Vous poussez des commits sur un dépôt public ;
- Des personnes récupèrent ces commits et se basent dessus pour travailler ;
- Vous utilisez un
git rebase
pour “réécrire” ces commits et les poussez à nouveau.
Dans ce cas, des problèmes vont se poser puisque les gens qui ont travaillé à partir des commits de départ ne vont pas les retrouver dans le projet si il veulent récupérer les mises à jour et lorsqu’ils vont pousser leur modification sur le dépôt public les commits effacés vont être réintroduits ce qui va créer un historique très confus et potentiellement des conflits.