Git avance - 02 - Rebase vs merge : comprendre la différence

La différence entre git rebase et git merge. Quand utiliser chaque approche, avec des schemas et des commandes concrètes.

02 - Rebase vs merge : comprendre la différence

Ce que tu vas apprendre

  • Ce que font rebase et merge sous le capot
  • Le rebase interactif pour nettoyer tes commits
  • La regle d'or a ne jamais enfreindre

Prerequisites


Le problème de depart

Tu bosses sur feat/add-search. Pendant ce temps, un collegue a merge sa branche dans main. Ta branche diverge. Tu dois récupérer les changements de main dans ta branche avant de pouvoir merger proprement.

Deux options : merge ou rebase. Les deux integrent les modifications. Le résultat final (les fichiers) est identique. Mais l'historique est radicalement différent.

Merge : on fusionne

git merge créé un commit de merge qui a deux parents. Il preserve la topologie des branches.

          A---B---C  feat/add-search
         /         \
    D---E---F---G---M  main

M est le merge commit. Il dit "a ce moment, j'ai intégré feat/add-search dans main". L'historique montre exactement ce qui s'est passe.

bashgit checkout main
git merge feat/add-search

L'avantage : tu vois quand la branche a ete créée, quand elle a ete mergee, et quels commits en faisaient partie. L'inconvenient : si tout le monde merge sans rebase, le graphe devient un enchaînement de losanges illisible. Avec git log --graph, ca ressemble a un metro aux heures de pointe.

Le vrai problème dans mon équipe, c'est quand les juniors font git pull sur leur branche de feature pour "se mettre à jour". Par défaut, git pull fait un git fetch + git merge. Ca créé un merge commit "Merge branch 'main' into feat/add-search" a chaque synchronisation. Trois fois par jour. L'historique explose.

Rebase : on rejoue

git rebase prend tes commits et les rejoue un par un sur la pointe de la branche cible. Pas de commit de merge. L'historique est lineaire.

                   A'--B'--C'  feat/add-search
                  /
    D---E---F---G  main

A', B', C' sont des copies de A, B, C, mais avec un parent différent. Ils ont un nouveau hash (un nouveau SHA). C'est comme si tu avais créé ta branche depuis G au lieu de E.

bashgit checkout feat/add-search
git rebase main

L'avantage : l'historique est propre, lineaire, facile a lire. git log --oneline te donne une liste de commits qui raconte une histoire claire. L'inconvenient : tu reecris l'historique. Les commits A, B, C sont remplaces par A', B', C'. Si quelqu'un d'autre travaille sur la meme branche, ses commits vont pointer vers des commits qui n'existent plus. C'est le chaos.

Le rebase interactif : l'outil de chirurgie

Le rebase interactif te permet de modifier tes commits avant de les pousser. Squasher, reordonner, reformuler, supprimer.

bashgit rebase -i HEAD~5

Ca ouvre un éditeur avec la liste de tes 5 derniers commits :

pick a1b2c3d feat: add search input
pick e4f5g6h fix: typo in search
pick i7j8k9l feat: add search results
pick m0n1o2p fix: search results layout
pick q3r4s5t chore: remove console.log

Tu peux changer pick en :

  • squash (ou s) : fusionne avec le commit précédent
  • fixup (ou f) : comme squash mais jette le message
  • reword (ou r) : change le message de commit
  • drop (ou d) : supprime le commit
  • Reordonne les lignes pour changer l'ordre

En pratique, je fais souvent ca avant d'ouvrir une merge request :

pick a1b2c3d feat: add search input
fixup e4f5g6h fix: typo in search
pick i7j8k9l feat: add search results
fixup m0n1o2p fix: search results layout
drop q3r4s5t chore: remove console.log

Résultat : deux commits propres au lieu de cinq. Le reviewer te remerciera. Et le message de commit "fix: typo in search" n'a pas besoin d'exister dans l'historique de main pour l'eternite.

Pour les conventional commits, reword est pratique si tu as oublie le format.

La regle d'or

Ne rebase jamais une branche sur laquelle d'autres personnes travaillent.

Si tu as push ta branche et que quelqu'un a fait un checkout dessus, ne rebase pas. Tu reecris l'historique, et l'autre dev va se retrouver avec des conflits incomprehensibles au prochain pull.

En pratique, ca veut dire :

  • Rebase tes branches de feature tant qu'elles sont a toi (pas de review en cours, personne n'a checkout dessus)
  • Une fois que tu as push et ouvert une merge request, ajoute des commits normaux. Ne rebase plus
  • Merge pour intégrer dans main

L'exception : si tu es le seul a travailler sur la branche et que tu n'as pas encore ouvert de merge request, tu peux rebase et force push. C'est safe parce que personne n'a base de travail dessus.

bash# Tu es seul sur ta branche, tu veux rebase sur main
git checkout feat/add-search
git rebase main
git push --force-with-lease

--force-with-lease au lieu de --force : ca refuse le push si quelqu'un d'autre a push sur la branche entre-temps. Toujours utiliser --force-with-lease.

Mon workflow recommande

Voici ce que j'impose dans mon équipe de trois devs. Simple, propre, sans surprise.

bash# 1. Tu crees ta branche depuis main a jour
git checkout main
git pull
git checkout -b feat/add-search

# 2. Tu bosses, tu commites (conventional commits)
git add src/search.ts
git commit -m "feat: add search input component"

# 3. Avant d'ouvrir la MR, tu rebase sur main
git fetch origin
git rebase origin/main

# 4. Tu push
git push -u origin feat/add-search

# 5. Tu ouvres la merge request sur GitLab

# 6. Apres approbation, merge dans main avec --ff-only
git checkout main
git merge feat/add-search --ff-only
git push

Le --ff-only (fast-forward only) refuse le merge s'il nécessité un merge commit. Ca force a avoir rebase avant. L'historique de main reste lineaire.

Sur GitLab, tu peux configurer ca dans Settings > Merge requests > "Fast-forward merge". La merge request ne passera que si la branche est à jour avec main.

Quand rebase tourne mal

Si un rebase créé des conflits incomprehensibles ou si tu t'es trompe, pas de panique :

bashgit rebase --abort

Ca annule tout et te ramene a l'état d'avant le rebase. Comme si rien ne s'etait passe. J'ai appris ca a mes juniors le premier jour. Avant de tenter quoi que ce soit d'autre en cas de problème : --abort. On discute apres.

Si tu as deja fini le rebase et que le résultat est mauvais, le reflog te permettra de récupérer l'état d'avant.

Et si les conflits pendant le rebase te font peur, on en parle en détail dans un article dédié.


Article précédent : 01 - Branching stratégies Article suivant : 03 - Conventional commits

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.