Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 714 minDébutant

Merge ou rebase ? git rebase, squash et cherry-pick

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • expliquer ce que fait git rebase et le comparer à git merge ;
  • appliquer la règle d'or du rebase ;
  • nettoyer et regrouper tes commits avec le rebase interactif ;
  • récupérer un commit avec git cherry-pick et pousser avec --force-with-lease.

4 exercices corrigés1 schéma

git merge et git rebase intègrent tous deux le travail d'une branche dans une autre, mais pas de la même façon : merge réunit les deux historiques par un commit de fusion sans rien modifier, alors que rebase rejoue tes commits un par un au-dessus de l'autre branche et produit un historique linéaire, en créant de nouveaux commits. Ce chapitre explique quand choisir l'un ou l'autre, la règle qui évite les catastrophes, le rebase interactif pour nettoyer ses commits (squash, reword, fixup), git cherry-pick et le push forcé sans danger.

Ce que fait git rebase

Lina a créé feature/export à partir de main et y a fait deux commits, D et E. Pendant ce temps, Hugo a poussé le commit C sur main. Lina veut que sa branche parte du main actuel.

bash
git switch feature/export
git rebase main
text
Successfully rebased and updated refs/heads/feature/export.

Git a mis de côté les commits D et E, déplacé le point de départ de la branche sur C, puis rejoué D et E par-dessus. Comme leur parent a changé, ce ne sont plus les mêmes commits : ils ont de nouvelles empreintes, notées D' et E'.

AvantABCDEmainfeature/exportAprès git rebase mainABCD'E'mainfeature/exportDEanciens commits,retrouvables par le reflog
git rebase rejoue tes commits au-dessus de l'autre branche : l'historique devient linéaire, les commits changent d'empreinte

Si Lina fusionne ensuite sa branche dans main, l'avance rapide suffit : l'historique de main reste une ligne droite, sans commit de fusion.

Merge ou rebase : comparaison

Critèregit mergegit rebase
Ce qu'il faitCrée un commit de fusion qui réunit les deux lignesRejoue tes commits au-dessus de l'autre branche
Commits existantsInchangésRecréés, avec de nouvelles empreintes
HistoriqueFidèle à la chronologie réelle, avec ses bifurcationsLinéaire, plus facile à lire
ConflitsRésolus une fois, dans le commit de fusionRésolus commit par commit, parfois plusieurs fois
Sans danger sur une branche partagéeOuiNon : il réécrit l'historique
Usage typiqueIntégrer une fonctionnalité terminée dans mainMettre à jour ta branche personnelle avec main avant de proposer ton travail

Les deux donnent le même code à la fin. Le choix porte sur l'historique que tu veux lire ensuite, et sur la sécurité de l'équipe.

La règle d'or du rebase

Pourquoi ? Si Hugo a déjà récupéré tes commits D et E et a construit dessus, ton rebase les remplace par D' et E'. Pour Git, ce sont des commits différents. Au prochain pull de Hugo, son dépôt contient D, E, D' et E' : des doublons, des conflits sur des modifications déjà intégrées, et un historique incompréhensible.

Concrètement, dans un projet de BTS en équipe :

  • rebase ta branche feature/… sur main tant qu'elle n'existe que chez toi, ou que tu es seul à la pousser ;
  • ne rebase jamais main ni une branche sur laquelle un coéquipier travaille aussi ;
  • en cas de doute, utilise git merge : il ne détruit rien.

Résoudre un conflit pendant un rebase

Le rebase rejoue les commits un par un. Si l'un d'eux entre en conflit, Git s'arrête sur ce commit :

text
Auto-merging config.js
CONFLICT (content): Merge conflict in config.js
error: could not apply 9e1c4a7... Fixe le plafond repas à 25
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".

Tu résous comme d'habitude (chapitre 6), puis :

bash
git add config.js
git rebase --continue     # passe au commit suivant

git rebase --skip abandonne le commit en cours (s'il est devenu inutile), et git rebase --abort remet tout dans l'état d'avant le rebase. Pendant un rebase, tu es en HEAD détaché : c'est normal, la branche est replacée à la fin.

git pull --rebase

git pull récupère les commits du dépôt distant et les intègre à ta branche. Quand tu as commité de ton côté pendant que quelqu'un poussait, il y a deux façons d'intégrer : une fusion, qui ajoute à chaque pull un commit Merge branch 'main' of github.com:… qui n'apporte rien à la lecture, ou un rebase.

git pull --rebase rejoue plutôt tes commits locaux, pas encore poussés, au-dessus des commits récupérés. L'historique reste linéaire. C'est sans danger, puisque les commits rejoués n'ont encore été vus par personne. C'est ce que fait le réglage pull.rebase true proposé au chapitre 1.

Depuis 2021, si les deux historiques ont divergé et que tu n'as rien configuré, Git refuse de choisir à ta place :

text
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
...
fatal: Need to specify how to reconcile divergent branches.

Choisis une fois pour toutes avec git config --global pull.rebase true (ou false si ton équipe préfère les fusions), et le message disparaît.

Nettoyer ses commits avec le rebase interactif

Avant de proposer ton travail, ta branche ressemble souvent à ceci :

text
a41f9e2 wip
c09d7b1 Ajoute l'export CSV des notes
7e2b8f3 oups faute de frappe
55c1a90 Ajoute les tests de l'export

Le rebase interactif permet de réécrire ces commits : les regrouper, les renommer, les réordonner, en supprimer. Pour retravailler les quatre derniers :

bash
git rebase -i HEAD~4

Git ouvre l'éditeur avec la liste, du plus ancien au plus récent (l'inverse de git log) :

text
pick 55c1a90 Ajoute les tests de l'export
pick 7e2b8f3 oups faute de frappe
pick c09d7b1 Ajoute l'export CSV des notes
pick a41f9e2 wip

Tu remplaces pick par l'action voulue sur chaque ligne :

ActionEffet
pick (ou p)Garde le commit tel quel
reword (r)Garde le commit et ouvre l'éditeur pour changer son message
edit (e)S'arrête sur ce commit pour le modifier (git commit --amend, puis git rebase --continue)
squash (s)Fusionne le commit avec le précédent et te laisse combiner les messages
fixup (f)Fusionne le commit avec le précédent et jette son message
drop (d)Supprime le commit

Tu peux aussi changer l'ordre des lignes. Pour obtenir deux commits propres, l'export puis ses tests :

text
pick c09d7b1 Ajoute l'export CSV des notes
fixup a41f9e2 wip
pick 55c1a90 Ajoute les tests de l'export
fixup 7e2b8f3 oups faute de frappe

Enregistre et ferme : Git rejoue la liste. Résultat, deux commits avec des messages clairs. Si un réordonnancement provoque un conflit, Git s'arrête comme pour un rebase normal.

Squash : un commit par fonctionnalité

Squash veut dire écraser : regrouper plusieurs commits en un seul. Tu le fais de trois façons :

  • avec le rebase interactif et les actions squash ou fixup, comme ci-dessus ;
  • avec git reset --soft vers le point de départ, puis un seul commit (chapitre 4) ;
  • au moment de la fusion, avec git merge --squash ou le bouton Squash and merge d'une pull request (chapitre 9).

Regrouper n'est pas toujours souhaitable : trois commits qui racontent trois étapes distinctes (la migration de la base, la route, l'écran) valent mieux qu'un commit énorme. On regroupe les commits de bricolage (« wip », « oups »), pas les étapes.

Récupérer un commit précis avec git cherry-pick

Hugo a corrigé un bug d'arrondi sur sa branche feature/tableau-de-bord, qui ne sera pas fusionnée avant deux semaines. La correction est urgente pour main. git cherry-pick copie un commit d'une autre branche sur la branche courante :

bash
git switch main
git cherry-pick 3b7e0d4
text
[main 8f21c6a] Arrondit le total au centime
 Date: Tue Oct 13 10:12:44 2026 +0200
 1 file changed, 1 insertion(+), 1 deletion(-)

Le nouveau commit a les mêmes modifications et le même message, mais une autre empreinte. Les options utiles : -x ajoute au message la ligne (cherry picked from commit 3b7e0d4…), pratique pour la traçabilité ; git cherry-pick A..B copie une série de commits (de A exclu à B inclus). À utiliser avec parcimonie : si la branche de Hugo est fusionnée plus tard, la même modification arrive deux fois, ce qui se passe bien en général, mais complique la lecture de l'historique.

Push forcé et force-with-lease

Après un rebase ou un --amend sur une branche que tu avais déjà poussée, l'historique local et l'historique distant ont divergé : GitHub refuse un push normal.

text
 ! [rejected]        feature/export -> feature/export (non-fast-forward)
error: failed to push some refs to 'github.com:ton-pseudo/sorbier-frais.git'

Si la branche est la tienne seule, tu peux forcer. Utilise toujours la forme prudente :

bash
git push --force-with-lease
CommandeComportement
git push --forceRemplace la branche distante par la tienne, sans aucune vérification ; les commits poussés entre-temps par d'autres sont perdus
git push --force-with-leaseRemplace la branche distante seulement si elle est restée dans l'état où tu l'as vue lors de ta dernière récupération ; sinon, refus

--force-with-lease te protège du cas où un coéquipier a poussé sur ta branche sans que tu le saches. Ne force jamais sur main ou sur une branche partagée : c'est ce que les règles de protection de branche interdisent.

Exercices

Exercice 1 sur 4 Merge ou rebase ? Réussi À revoir

Pour chaque situation, choisis entre git merge et git rebase, et justifie.

  1. Lina a créé feature/export ce matin, n'a encore rien poussé, et veut récupérer les deux commits que Hugo vient de pousser sur main.
  2. Lina et Hugo travaillent tous les deux sur feature/tableau-de-bord, déjà poussée. Hugo veut la mettre à jour avec main.
  3. La fonctionnalité de feature/export est terminée et relue. Il faut l'intégrer à main.
  4. Hugo veut mettre à jour main avec un rebase sur feature/export pour « avoir un historique propre ».
Voir la réponseMasquer la réponse de l'exercice 1
  1. git rebase main depuis feature/export : la branche n'existe que chez Lina, réécrire ses commits ne gêne personne, et l'historique restera linéaire.
  2. git merge main depuis feature/tableau-de-bord : la branche est partagée, un rebase réécrirait des commits que Lina a déjà récupérés.
  3. git merge (ou la fusion d'une pull request) : main reçoit la fonctionnalité sans que ses commits soient réécrits. Si feature/export a été rebasée juste avant, ce sera une avance rapide.
  4. Jamais : main est partagée par toute l'équipe. On ne rebase pas main.

Tu avais trouvé ?

Exercice 2 sur 4 Après le rebase Réussi À revoir

Avant le rebase, git log --oneline main affiche C B A (du plus récent au plus ancien) et git log --oneline feature/export affiche E D B A. Depuis feature/export, Lina lance git rebase main sans conflit.

  1. Qu'affiche maintenant git log --oneline feature/export ?
  2. main a-t-elle changé ?
  3. Les commits D et E existent-ils encore ?
  4. Lina avait déjà poussé feature/export avant le rebase. Que se passe-t-il au git push, et que doit-elle faire si elle est seule sur la branche ?
Voir la réponseMasquer la réponse de l'exercice 2
  1. E' D' C B A : D et E rejoués au-dessus de C, avec de nouvelles empreintes.
  2. Non : le rebase ne modifie que la branche courante.
  3. Oui, mais ils ne sont plus sur aucune branche. On les retrouve avec git reflog jusqu'à leur nettoyage automatique.
  4. Le push est refusé (non-fast-forward), car la branche distante contient D et E, que la branche locale n'a plus. Seule sur la branche, elle lance git push --force-with-lease.

Tu avais trouvé ?

Exercice 3 sur 4 Rebase interactif Réussi À revoir

git log --oneline -5 affiche, du plus récent au plus ancien :

text
e5f6a7b Corrige une faute dans le message d'erreur
d4e5f6a wip validation
c3d4e5f Ajoute la validation du montant
b2c3d4e ajoute route
a1b2c3d Initialise l'API

Tu veux obtenir trois commits : « Initialise l'API » inchangé, « Ajoute la route POST /notes » (le commit b2c3d4e renommé), et un seul commit « Ajoute la validation du montant » qui regroupe c3d4e5f, d4e5f6a et e5f6a7b, avec ce message-là. Quelle commande lances-tu, et quelle liste écris-tu dans l'éditeur ?

Voir la réponseMasquer la réponse de l'exercice 3

git rebase -i HEAD~4 (les quatre commits qui suivent a1b2c3d, qui reste intact), puis :

text
reword b2c3d4e ajoute route
pick c3d4e5f Ajoute la validation du montant
fixup d4e5f6a wip validation
fixup e5f6a7b Corrige une faute dans le message d'erreur

reword ouvre l'éditeur pour écrire « Ajoute la route POST /notes ». Les deux fixup fusionnent leurs modifications dans « Ajoute la validation du montant » en gardant son message. La liste s'écrit du plus ancien au plus récent.

Tu avais trouvé ?

Exercice 4 sur 4 Force ou force-with-lease ? Réussi À revoir

Lina a rebasé feature/export et doit la pousser. Sans qu'elle le sache, Hugo a poussé ce matin un commit de correction sur cette même branche. Que se passe-t-il avec git push --force ? Et avec git push --force-with-lease ?

Voir la réponseMasquer la réponse de l'exercice 4

Avec --force, la branche distante est remplacée par celle de Lina : le commit de Hugo disparaît de GitHub, sans avertissement. Avec --force-with-lease, Git constate que la branche distante n'est plus dans l'état que Lina a vu lors de sa dernière récupération et refuse le push. Lina fait alors git fetch, voit le commit de Hugo, l'intègre (par exemple avec git rebase origin/feature/export), puis pousse de nouveau. Et elle en parle avec Hugo : deux personnes sur une branche, c'est une branche partagée, qu'il ne fallait pas rebaser.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : GitHub : clé SSH, git push, git pull et git cloneRevoir le sommaire du cours