Git Rebase ou Merge ? La bonne stratégie pour votre équipe

1. Aperçu

git merge et git rebase servent tous les deux à intégrer les modifications entre deux branches, mais ils créent un historique de commits très différent. Choisir la mauvaise stratégie rend le log confus, difficile à relire, ou pire — cela peut détruire l’historique de vos collègues. Cet article vous aide à comprendre le fonctionnement de chaque commande à travers des exemples concrets, à savoir quand utiliser merge, quand utiliser rebase, et à éviter les erreurs les plus courantes quand vous utilisez rebase en équipe.

2. Contexte de l’exemple

Supposons que vous travaillez sur la branche feature/payment créée à partir de main. Pendant que vous codez, vos collègues ont fusionné plusieurs commits dans main.

git log --oneline --graph --all
* c3f9a1d (main) Ajouter PaymentValidator
* 7b2e4aa (main) Corriger bug OrderService
| * 9d1a0ef (feature/payment) Ajouter PaymentService.processPayment()
| * 4f3c2bb (feature/payment) Initialiser PaymentService
|/
* a1b2c3d Base commit commun

Les deux branches se sont séparées à partir de a1b2c3d. Vous voulez maintenant mettre feature/payment à jour avec main avant de créer une pull request. Il y a deux façons de le faire : merge ou rebase.

3. Git Merge : préserver l’historique, créer un merge commit

3.1. Utilisation

git checkout feature/payment
git merge main

Cette commande crée un nouveau merge commit avec deux parents : le dernier commit de feature/payment et le dernier commit de main.

*   e5f6a7b (HEAD -> feature/payment) Merge branch 'main' into feature/payment
|\
| * c3f9a1d (main) Ajouter PaymentValidator
| * 7b2e4aa (main) Corriger bug OrderService
* | 9d1a0ef Ajouter PaymentService.processPayment()
* | 4f3c2bb Initialiser PaymentService
|/
* a1b2c3d Base commit commun

3.2. Caractéristiques

  • L’historique d’origine des deux branches est préservé à 100%, aucun commit n’est modifié.
  • Un merge commit supplémentaire, semblable à du « déchet », enregistre le moment où les deux branches se rencontrent.
  • Absolument sûr pour une branche déjà poussée — personne n’est affecté négativement.

Remarque : git merge ne réécrit pas le SHA d’aucun commit existant. C’est la raison pour laquelle c’est toujours sûr avec une branche partagée.

4. Git Rebase : réécrire l’historique, créer une branche linéaire

4.1. Utilisation

git checkout feature/payment
git rebase main

Rebase récupère chaque commit de feature/payment depuis le point de divergence (a1b2c3d), puis les « rejoue » au-dessus de main. Le résultat est des commits complètement nouveaux avec un SHA différent.

* 2a9e1cc (HEAD -> feature/payment) Ajouter PaymentService.processPayment()
* d8b4f0a Initialiser PaymentService
* c3f9a1d (main) Ajouter PaymentValidator
* 7b2e4aa (main) Corriger bug OrderService
* a1b2c3d Base commit commun

L’historique est maintenant une ligne droite (linear history), sans aucun merge commit.

4.2. Caractéristiques

  • 9d1a0ef et 4f3c2bb n’existent plus — remplacés par 2a9e1cc et d8b4f0a avec un nouveau SHA.
  • Le log est propre, facile à lire, facile à utiliser avec git bisect.
  • Dangereux si la branche a été pulée par d’autres, car ils conservent les commits anciens qui ont « disparu ».

5. Résoudre les conflits : la différence majeure lors de la manipulation

Avec merge, tout conflit n’a besoin d’être résolu qu’une seule fois au moment du merge commit :

git merge main
# CONFLICT (content): Merge conflict in PaymentService.java
# ... corriger le fichier, résoudre ...
git add PaymentService.java
git commit

Avec rebase, s’il y a plusieurs commits, un conflit peut survenir à chaque commit pendant la relecture :

git rebase main
# CONFLICT dans le commit 4f3c2bb
# corriger le fichier, résoudre, puis :
git add PaymentService.java
git rebase --continue
# si le commit suivant a aussi un conflit, répétez

Si vous voulez arrêter et revenir à l’état avant le rebase :

git rebase --abort

Remarque : Rebaser une branche avec 10 commits signifie parfois résoudre des conflits 10 fois — une raison pour laquelle faire un squash avant le rebase (voir section 7) est très utile.

6. La règle d’or : ne pas rebaser une branche partagée

C’est le principe le plus important quand vous utilisez rebase.

# La branche main a été pulée par 5 autres personnes
git checkout main
git rebase feature/old-stuff   # ❌ EXTRÊMEMENT DANGEREUX
git push --force origin main

Comme rebase crée un SHA nouveau pour tous les commits, après push --force, quiconque a pulé l’ancien main aura un historique divergé avec le remote. Leur prochaine tentative de pull échouera ou créera une multitude de commits dupliqués.

error: failed to push some refs to 'origin'
hint: Updates were rejected because the tip of your current branch is behind

Si vous devez absolument faire un force-push après un rebase, utilisez --force-with-lease au lieu de --force pour éviter d’écraser les commits des autres :

git push --force-with-lease origin feature/payment

--force-with-lease vérifie que le remote n’a pas reçu d’autres commits depuis votre dernier fetch ; si c’est le cas, la commande échoue au lieu d’écraser aveuglément.

La règle d’or : rebaser uniquement une branche personnelle, non partagée ou sur laquelle vous travaillez seul. Ne rebaser jamais main, develop, ou toute autre branche partagée.

7. Interactive rebase : nettoyer l’historique avant de fusionner

Une application très pratique du rebase est de nettoyer les commits « poubelle » sur votre branche personnelle avant d’ouvrir une pull request.

git checkout feature/payment
git rebase -i HEAD~3

Un éditeur s’ouvre :

pick 4f3c2bb Initialiser PaymentService
pick 9d1a0ef Ajouter PaymentService.processPayment()
pick 1c8f3aa fix typo

Changez les deux dernières lignes en squash pour les fusionner dans le premier commit :

pick   4f3c2bb Initialiser PaymentService
squash 9d1a0ef Ajouter PaymentService.processPayment()
squash 1c8f3aa fix typo

Sauvegardez, Git vous demandera d’écrire un nouveau message de commit. Résultat : 3 commits fragmentaires deviennent 1 commit propre et significatif — parfait avant d’ouvrir une PR pour que le reviewer n’ait pas à lire des commits comme « fix typo », « wip », « oups oublié import ».

8. Stratégie pragmatique : combiner les deux

La plupart des équipes efficaces ne choisissent pas exclusivement l’une ou l’autre, mais les combinent selon le contexte :

Situation À utiliser
Mettre à jour votre branche personnelle avec le dernier main rebase
Nettoyer les commits fragmentaires avant d’ouvrir une PR rebase -i (squash)
Fusionner une branche feature approuvée dans main merge (généralement via une PR, peut être squash-merge)
Branche sur laquelle plusieurs personnes travaillent/pullent merge, ne jamais rebase
Besoin de conserver une trace du moment de l’intégration pour l’audit merge

Processus courant dans la pratique :

# 1. Avant d'ouvrir une PR : rebaser sur main pour mettre à jour, éviter des conflits tardifs
git checkout feature/payment
git rebase main

# 2. Nettoyer les commits si nécessaire
git rebase -i main

# 3. Pousser votre branche personnelle (sûr car vous seul l'utilisez)
git push --force-with-lease origin feature/payment

# 4. Fusionner dans main via une PR (merge commit ou squash-merge, ne pas rebaser main)

Cette approche donne un log feature/payment propre, linéaire, facile à relire dans la PR, tandis que main reste sûr car il ne reçoit que des merge commits (ou squash commits) des PRs approuvées.

9. Squash merge : une solution intermédiaire

De nombreuses plateformes (GitHub, GitLab) offrent un bouton « Squash and merge » — qui fusionne tous les commits de la PR en un seul commit, puis le fusionne dans main.

git checkout main
git merge --squash feature/payment
git commit -m "Ajouter la fonctionnalité de traitement des paiements (#42)"

Résultat : main reçoit exactement un nouveau commit, sans merge commit, et l’historique de feature/payment (sur le remote) reste intact si vous n’avez pas supprimé la branche. C’est le choix le plus courant pour les équipes utilisant un workflow PR, car cela garde le log propre sur main sans toucher à l’historique d’origine.

10. Erreurs courantes

  • Rebaser puis pousser normalement (sans --force) : Git signalera une erreur car l’historique a divergé. N’utilisez pas git pull ordinaire pour « réparer » — cela créera un merge commit indésirable qui ruinera l’historique linéaire que vous venez d’obtenir.
  • Oublier que la branche que vous rebaser est une branche partagée : toujours vérifier git log origin/<branch> pour voir si quelqu’un d’autre dépend déjà des anciens commits.
  • Terminer un rebase mais oublier de --continue après la résolution de conflit : le rebase reste « suspendu », git status vous rappellera clairement que vous êtes au milieu d’un rebase.
  • Utiliser merge continuellement sur une branche personnelle remplit le log de petits merge commits éparpillés, difficiles à lire — préférez rebaser pour mettre à jour plutôt que de fusionner main dans votre branche plusieurs fois.
  • Squasher trop agressivement : fusionner de nombreuses modifications non liées en un seul commit rend git bisect et les revert partiels inutiles.

11. Conclusion

merge est sûr, préserve un historique fidèle, convient aux branches partagées. rebase crée un historique propre et linéaire, mais réécrit les SHA, donc ne peut être utilisé que sur des branches personnelles non partagées. Dans la pratique, vous n’avez pas besoin de choisir exclusivement un camp : rebaser pour mettre à jour et nettoyer votre branche personnelle, fusionner (ou squash-merge) pour intégrer dans main via une PR. Souvenez-vous de la Règle d’or — ne jamais rebaser une branche sur laquelle quelqu’un d’autre compte — et vous éviterez la plupart des problèmes Git en équipe.

gitrebasemergecontrôle-de-versionflux-de-travail