Stratégies merge vs rebase avec git

1er août 2020

Adem Usta

Merge vs Rebase

Introduction

Lorsqu'on développe un logiciel (application web, logiciel desktop, site internet, etc.), une des bonnes pratiques à ne surtout pas négliger est la gestion des versions du code source.

C'est en réponse à ce besoin que le logiciel libre git est né.

Il permet de versionner son logiciel et de garder un historique précis de ces versions.

Il permet aussi à plusieurs personnes de travailler sur le même projet, sans qu'ils (les développeurs) ne se marchent dessus, du moins en théorie.

Nous allons exposer dans cet article une situation classique de développement d'un logiciel par plusieurs personnes.

Voici le contexte:

  • un projet dont le code source est hebergé sur GitHub.
  • plusieurs développeurs travaillent sur le même fichier, et potentiellement modifient la même partie de celui-ci.

Contexte

La branche principale du projet est nommée master, comme dans la plupart des projets.

La branche master contient déjà quelques commits:

1

Modification du code source par plusieurs développeurs

Supposons deux développeurs A et B, voulant modifier des fichiers sources du projet.

La bonne pratique est pour chacun de créer une branche et d'effectuer leurs modifications dessus.

On les nommera branch-A et branch-B.

À un moment donné, les deux développeurs seront satisfaits de leur boulot et voudront pousser leurs changements sur la branche master.

C'est à ce moment là où il existe deux façons de faire: via un merge ou un rebase.

Voici la situation des branches:

2

Dans la suite, on suppose que A va pousser ses changements sur master en premier.

Il crée donc une pull-request, et après validation, il la merge sur master.

Le schéma devient donc le suivant:

3

Jusque là, tout va bien, rien de bien méchant.

Là où les choses deviennent intéressantes, c'est pour B, lorsqu'il va vouloir pousser ses modifications sur master.

Comme on le voit dans le réseau des branches, master a bougé (grâce aux modifications de A) et donc la branche de B n'a pas les dernières modifications apportées sur master.

C'est à ce moment précis que le choix entre un merge et un rebase doit se faire.

Nous allons donc voir les deux cas, et voir ce que cela implique sur l'arborescence, la gestion des éventuels conflits, etc.

Stratégie de merge

En choisissant la stratégie de merge, git va tout simplement prendre les commits de branch-B et les ajouter sur master, on a alors ce graphique:

4

Note: les couleurs ne représentent pas les mêmes branches à chaque fois ... merci GitHub !

Note 2: un développeur C s'est incrusté en cours de route, il servira d'exemple pour le rebase !

On voit ici que l'historique des commits n'est pas lisible, et là nous n'avons que deux développeurs !

C'est dans ce contexte que nous allons voir en quoi la strategie de rebase peut rendre l'historique plus lisible.

Stratégie de rebase

Un nouveau développeur s'est incrusté au projet, avec toute sa bonne volonté.

Il a donc sa branche branch-C, sur laquelle il effectue ses modifications.

Vient le moment où il veut pousser ses changements sur master.

Sa pull-request étant validée, il décide de merge mais en effecutant un rebase dans un premier temps.

Note: pour effectuer un rebase, vous pouvez le faire via la CLI de git sur votre environnement de travail local, en effectuant:

git fetch && git rebase origin/master

Il se peut que vous aillez des conflits, vous pourrez les régler localement avant de pousser votre code.

Il faudra push force, cela peut être problématique dans des cas précis, notamment quand vous bossez à plusieurs sur la même branche.

Sinon, vous pouvez effectuer le rebase via GitHub directement.

En effectuant un "rebase and merge" sur l'interface de github, nous obtenons ceci:

5

Dans ce cas, github ne montre plus la branch-C, mais les commits de celle-ci se retrouve sur master, plus précisemment en dernier.

Pour montrer ce que fait un rebase en CLI, nous incluons une nouvelle branche branch-D, l'arbre des branches est alors:

6

Le rebase depuis la CLI + push force nous donne:

7

Nous voyons ce que fait le rebase ici: git remonte notre branche branch-D au dessus de master, puis rejoue nos commits un à un.

S'il y a des conflits, c'est à ce moment là qu'ils doivent être réglés.

Il nous reste plus qu'à merge depuis l'interface.

On a alors:

8

Ici, on voit bien notre branch-D (en bleu en haut) qui est "remonté".

L'intérêt de cette dernière stratégie est d'avoir un historique des branches qui suit une forme de simples boucles.

L'historique est alors plus lisible.

Un seul bémol: le push force à effectuer après un rebase (il est nécessaire car on ré-écrit les commits de notre branche, il y a donc mismatch avec la branche sur le serveur).

Cela peut être problématique si vous êtes plusieurs à développer sur la même branche (il faudra faire attention et pull les changements de vos collègues avant d'effectuer le rebase).

Conclusions

Comme on l'a vu dans cet article, la stratégie de merge est la plus simple: elle ne nécessite pas de workflow particulier:

  1. on développe sur une branche séparée notre nouvelle fonctionnalité
  2. une fois prête, on ouvre une pull-request sur GitHub
  3. une fois validée, on merge le tout.

Mais cela implique un historique des branches qui n'est pas assez lisible (à mon sens).

A l'inverse, effectuer un rebase implique un peu plus de discipline:

  1. on développe sur une branche séparée notre nouvelle fonctionnalité
  2. une fois prête, on ouvre une pull-request sur GitHub
  3. une fois les modifications validées, on effectue un rebase
  4. On merge la pull-request.

Le workflow est plus compliqué, et nécessite un peu plus de discipline de la part des développeurs.

Mais le résultat est un historique beaucoup plus lisible.

Voilà pour ce premier billet de blog, si vous avez des questions, n'hésitez pas à m'envoyer un email !

A tres vite,

Adem Usta.