Les Crash-Devs d'un Passionné

/Catégorie/git

Merge vs Rebase

Temps de lecture : 12 minutes

C'est un sujet

git
pour lequel je rencontre pas mal de méconnaissance, j'aimerais vous partager les bonnes pratiques sur les usages d'un rebase ou d'un merge

Le rebase et le merge sont tous les 2 des commandes

git
, ils permettent de ramener du code d'une branche source vers une branche de destination.

Considérons que nous avons 2 branches master et feat

schema_de_base_avec_2_branches
  • master est la branche finale, celle de la production
  • feat est la branche de travail, celle que l'on va vouloir mettre dans master

Pour ajouter le code de travail de feat vers master on va voir qu'il existe 2 manières de faire

git merge

Le merge consiste à fusionner l'historique de la branche source (

master
) dans votre branche de travail (
feat
).
git
va chercher le point commun entre les deux branches et créer un nouveau commit, dit « commit de merge », qui possède deux parents (B et D).

Cette commande

git rebase
ramène le commit B de la branche master (source) au bout du commit de la branche de travail feat (destination) via E

git_merge

Afin de réaliser cette action il faut exécuter les commandes suivantes

clipboard
Copier le code
1
2
git checkout feat # feat étant le branche de destination
git merge origin/master # master étant la branche source

Le mot origin fait référence au serveur distant, vous pouvez connaître sa valeur avec

git remote get-url origin

Il est important de récupérer le code distant à jour par

git fetch origin master
. En effet le
git merge
ne fait une fusion que de branches locales. On peut sinon faire un
git pull origin master
qui est le raccourci d'un
git fetch
+
git merge

S'il y a des conflits que

git
n'est pas capable de gérer, vous devez les valider à la main. Soit en ligne de commande soit via une UI. Je passe à chaque fois par PyCharm ou VSCode pour le faire afin de ne pas commettre d'erreur.

git_diff_rebase_pycharm

Dans votre UI vous allez avoir a minima 2 panneaux de code, la version de la branche source master et la version de la branche de destination feat. Des fois (comme PyCharm) vous aurez un 3e panneau qui vous indique le résultat de la gestion du conflit entre les branches feat et master (dans l'image ci-dessus il faut remplacer dev par feat)

Sous VSCode on a une représentation avec les 2 branches seulement

git_diff_vscode

La branche source master peut avoir plusieurs dénominations :

  • Incoming change
  • Theirs

La branche de destination feat peut avoir plusieurs dénominations :

  • Current change
  • Ours
  • HEAD

Il est très important d'être vigilant quand vous résolvez les conflits car si vous choisissez la mauvaise version vous allez potentiellement perdre les données d'un développeur

Après avoir résolu les conflits il faut penser à ajouter les fichiers impactés

git add .
et de pousser le commit de merge
git push

Si vous ou

git
n'avez pas résolu de conflit il n'y aura pas besoin de pousser votre branche. Cela veut dire que votre branche travaillait sur des fichiers qui n'ont pas été modifié depuis la création de votre branche

Il est courant d'utiliser un merge si :

  • Rapidité d'exécution : l'historique de commit n'est pas un sujet important pour vous
  • Pertinence : vous travaillez sur une branche publique

Ses avantages :

  • Non destructif : L'historique n'est jamais modifié.
  • Traçabilité : On voit exactement quand une branche a été intégrée.

git rebase

Contrairement au merge, le rebase ne crée pas de commit de fusion. Il va venir "déplacer" vos commits de travail (feat) pour les remettre à la suite de la branche source (master).

git rebase

On prend nos commits C et D de la branche de travail

feat
,
git
les met de côté temporairement, puis met à jour la branche avec le dernier commits de
master
, puis on "rejoue" C et D par-dessus.

Important : les commits C et D ne sont pas les commits initiaux mais bien des copies (comme l'historique est ré-écrit)

Pour cela on applique

clipboard
Copier le code
1
2
git checkout feat   # On se place sur la branche de travail
git rebase origin/master   # On rejoue nos modifs par dessus master

Important : ne jamais faire de

git pull
si on veut faire un rebase car derrière git crée un merge

C'est ici que l'expérience change du merge. S'il y a des conflits,

git
s'arrêtera commit par commit.

  1. Vous réglez le conflit du commit C (si nécessaire)
  2. Vous faites
    git add .
    puis
    git rebase --continue
  3. Vous réglez ensuite le conflit du commit D (si nécessaire)

L'interface de PyCharm ou VSCode est là aussi une aide précieuse. Elle vous permet plus facilement de voir vos changements

Dans l'interface de comparaison des versions on retrouve des nommages différents.

La branche source master peut avoir plusieurs dénominations :

  • Current change
  • Ours
  • HEAD

La branche de destination feat peut avoir plusieurs dénominations :

  • Incoming change
  • Theirs

Vous avez sûrement remarqué que les noms sont inversés par rapport à un merge.

C'est tout à fait normal,

git
est sur master (Ours) et ce sont tes commits de feat qui arrivent comme des modifications externes (Theirs).

C'est pour cette raison que le HEAD est sur master. Le HEAD étant l'endroit où se trouve le travail de

git
en cours, il est bien sur master et plus précisément en train d'appliquer C et D de la branche feat. Le HEAD est dynamique il bouge en cours de rebase, il sera sur C' puis D'

Si vous avez de nombreux commit sur feat, vous allez potentiellement résoudre les mêmes conflits à chaque fois car

git
rejoue tous vos commits un à un sur la branche master

Attention : Comme on réécrit l'historique,

git
refusera votre prochain
push
classique. Il faudra utiliser la commande
git push --force
pour réécrire l'historique de la branche distante.

Cependant je vous conseille plutôt cette option

git push --force-with-lease
. C'est une sécurité pour éviter d'écraser le travail d'un collègue qui aurait aussi travaillé sur cette branche.

Il est courant d'utiliser un rebase si :

  • La clarté de l'historique est une priorité : Vous voulez une ligne droite, facile à lire et à suivre (adieu commits de merge). Il faut néanmoins être rigoureux dans le nommage et le nombre de vos commits
  • Vous voulez nettoyer vos commits : Avant de pousser votre code, pour supprimer les commits de type "fix", "typo" ou "test" (souvent couplés à un rebase interactif). Ou encore modifier un commit antérieur (toujours avec un rebase interactif)

Ses avantages :

  • Historique linéaire : La navigation dans les log est beaucoup plus simple. On comprend l'ordre logique des fonctionnalités sans les bruits de fusion.
  • Bisect facilité : La commande
    git bisect
    (pour trouver un bug) est bien plus efficace sur une ligne droite que dans un labyrinthe de branches entremêlées.

Résumé

Nous venons de parcourir les cas d'usage pour un rebase et merge, ce sont 2 commandes à absolument maîtriser pour faire le bon choix en fonction de vos besoins.

Le point d'attention est de comprendre qu'un rebase se place temporairement sur master et donc les notions de theirs et ours sont inversées par rapport au merge
Et je vais conclure dans quel cas nous ne devons pas les utiliser :

Rebase :

  • Sur une branche partagée (comme master), comme les commits sont réécrit le hash aussi.
  • Quand tu as beaucoup de commits sur ta branche feat,
    git
    devra appliquer chaque commit et tu devras résoudre autant de conflits que
    git
    trouvera et cela sur chaque commit
  • En cas de règle de conformité, pendant un audit tu dois prouver quand ta modification a été faite (date du commit), le rebase mets la date du dernier rebase et non celle de quand tu as fait cela (cela est vrai sur de longue période de travail ou si la rigueur est très stricte)

Merge :

  • Quand tu veux mettre à jour régulièrement ta branche locale, à chaque git merge master git va créer un commit de merge

La règle d'or est de ne surtout pas faire un rebase sur une branche publique