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 mergene 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
9d1a0efet4f3c2bbn’existent plus — remplacés par2a9e1ccetd8b4f0aavec 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
squashavant 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 pasgit pullordinaire 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
--continueaprès la résolution de conflit : le rebase reste « suspendu »,git statusvous rappellera clairement que vous êtes au milieu d’un rebase. - Utiliser
mergecontinuellement 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 fusionnermaindans votre branche plusieurs fois. - Squasher trop agressivement : fusionner de nombreuses modifications non liées en un seul commit rend
git bisectet 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.